factual-api 0.1.2 → 0.2.0
raw patch · 33 files changed
+870/−402 lines, 33 filesdep ~hoauth
Dependency ranges changed: hoauth
Files
- Data/Factual/Credentials.hs +0/−18
- Data/Factual/CrosswalkQuery.hs +0/−52
- Data/Factual/Query/CrosswalkQuery.hs +48/−0
- Data/Factual/Query/FacetsQuery.hs +49/−0
- Data/Factual/Query/GeocodeQuery.hs +20/−0
- Data/Factual/Query/GeopulseQuery.hs +26/−0
- Data/Factual/Query/ReadQuery.hs +52/−0
- Data/Factual/Query/ResolveQuery.hs +35/−0
- Data/Factual/Query/SchemaQuery.hs +19/−0
- Data/Factual/ReadQuery.hs +0/−153
- Data/Factual/ResolveQuery.hs +0/−35
- Data/Factual/SchemaQuery.hs +0/−17
- Data/Factual/Shared/Filter.hs +60/−0
- Data/Factual/Shared/Geo.hs +45/−0
- Data/Factual/Shared/Search.hs +26/−0
- Data/Factual/Shared/Table.hs +21/−0
- Data/Factual/Table.hs +0/−16
- Data/Factual/Utils.hs +16/−0
- Data/Factual/Write.hs +12/−0
- Data/Factual/Write/Flag.hs +64/−0
- Data/Factual/Write/Submit.hs +44/−0
- Network/Factual/API.hs +51/−15
- README.md +8/−8
- examples/CrosswalkExample.hs +2/−4
- examples/FacetsExample.hs +25/−0
- examples/GeocodeExample.hs +18/−0
- examples/GeopulseExample.hs +19/−0
- examples/RawReadExample.hs +16/−0
- examples/ReadExample.hs +2/−4
- examples/ResolveExample.hs +2/−4
- examples/SchemaExample.hs +2/−4
- factual-api.cabal +19/−12
- test/Tests.hs +169/−60
− Data/Factual/Credentials.hs
@@ -1,18 +0,0 @@--- | This module contains a simple definition of the credentials type which is--- used to perform submit queries to the Factual API.--module Data.Factual.Credentials- (- -- * Credentials type- Credentials(..)- , Key- , Secret- ) where---- | The Key is the oauth key as a String.-type Key = String--- | The Secret is the oauth secret as a String.-type Secret = String---- | A Credentials value is used to generate a Token.-data Credentials = Credentials Key Secret
− Data/Factual/CrosswalkQuery.hs
@@ -1,52 +0,0 @@--- | This module exports the type used to create crosswalk queries.-module Data.Factual.CrosswalkQuery- (- -- * CrosswalkQuery type- CrosswalkQuery(..)- ) where--import Data.Factual.Query-import Data.Factual.Utils---- | A crosswalk query can be formed by specifying a factual id and--- (optionally) a list of namespaces to only include when attempting to find--- the ids for various namespaces, or by specifying a namespace and namespace--- id in order to find the factual id. An optional limit can be set as well.-data CrosswalkQuery = CrosswalkQuery { factualId :: Maybe String- , limit :: Maybe Int- , namespace :: Maybe String- , namespaceId :: Maybe String- , only :: [String]- } deriving (Eq, Show)---- A crosswalk query is a member of the Query typeclass so it can generate a--- path to be queried.-instance Query CrosswalkQuery where- toPath query = "/places/crosswalk?"- ++ joinAndFilter [ factualIdString $ factualId query- , limitString $ limit query- , namespaceString $ namespace query- , namespaceIdString $ namespaceId query- , onlyString $ only query ]---- The following helper functions are used to generate the separate parts of--- the query path.-factualIdString :: Maybe String -> String-factualIdString (Just id) = "factual_id=" ++ id-factualIdString Nothing = ""--limitString :: Maybe Int -> String-limitString (Just lim) = "limit=" ++ (show lim)-limitString Nothing = ""--namespaceString :: Maybe String -> String-namespaceString (Just namespace) = "namespace=" ++ namespace-namespaceString Nothing = ""--namespaceIdString :: Maybe String -> String-namespaceIdString (Just id) = "namespace_id=" ++ id-namespaceIdString Nothing = ""--onlyString :: [String] -> String-onlyString [] = ""-onlyString strs = "only=" ++ (join "," strs)
+ Data/Factual/Query/CrosswalkQuery.hs view
@@ -0,0 +1,48 @@+-- | This module exports the type used to create crosswalk queries.+module Data.Factual.Query.CrosswalkQuery+ (+ -- * CrosswalkQuery type+ CrosswalkQuery(..)+ ) where++import Data.Factual.Query+import Data.Factual.Utils++-- | A crosswalk query can be formed by specifying a factual id and+-- (optionally) a list of namespaces to only include when attempting to find+-- the ids for various namespaces, or by specifying a namespace and namespace+-- id in order to find the factual id. An optional limit can be set as well.+data CrosswalkQuery = CrosswalkQuery { factualId :: Maybe String+ , limit :: Maybe Int+ , namespace :: Maybe String+ , namespaceId :: Maybe String+ , only :: [String]+ } deriving (Eq, Show)++-- A crosswalk query is a member of the Query typeclass so it can generate a+-- path to be queried.+instance Query CrosswalkQuery where+ toPath query = "/places/crosswalk?"+ ++ joinAndFilter [ factualIdString $ factualId query+ , limitString $ limit query+ , namespaceString $ namespace query+ , namespaceIdString $ namespaceId query+ , onlyString $ only query ]++-- The following helper functions are used to generate the separate parts of+-- the query path.+factualIdString :: Maybe String -> String+factualIdString (Just id) = "factual_id=" ++ id+factualIdString Nothing = ""++namespaceString :: Maybe String -> String+namespaceString (Just namespace) = "namespace=" ++ namespace+namespaceString Nothing = ""++namespaceIdString :: Maybe String -> String+namespaceIdString (Just id) = "namespace_id=" ++ id+namespaceIdString Nothing = ""++onlyString :: [String] -> String+onlyString [] = ""+onlyString strs = "only=" ++ (join "," strs)
+ Data/Factual/Query/FacetsQuery.hs view
@@ -0,0 +1,49 @@+-- | This module exports the types used to create facets queries.+module Data.Factual.Query.FacetsQuery+ (+ -- * FacetsQuery type+ FacetsQuery(..)+ -- * Required modules+ , module Data.Factual.Shared.Table+ , module Data.Factual.Shared.Search+ , module Data.Factual.Shared.Filter+ , module Data.Factual.Shared.Geo+ ) where++import Data.Factual.Query+import Data.Factual.Shared.Table+import Data.Factual.Shared.Search+import Data.Factual.Shared.Filter+import Data.Factual.Shared.Geo+import Data.Factual.Utils++-- | The FacetsQuery type is used to construct facets queries. A table and search+-- should be specified, but the rest of the query options are essentially+-- optional.+data FacetsQuery = FacetsQuery { table :: Table+ , search :: Search+ , select :: [String]+ , filters :: [Filter]+ , geo :: Maybe Geo+ , limit :: Maybe Int+ , minCount :: Maybe Int+ , includeCount :: Bool+ } deriving (Eq, Show)++-- The FacetsQuery type is a member of the Query typeclass so it can be used to+-- make a request.+instance Query FacetsQuery where+ toPath query = (show $ table query)+ ++ "facets?"+ ++ joinAndFilter [ searchString $ search query+ , selectString $ select query+ , filtersString $ filters query+ , geoString $ geo query+ , limitString $ limit query+ , minCountString $ minCount query+ , includeCountString $ includeCount query ]++-- Helper functions+minCountString :: Maybe Int -> String+minCountString (Just x) = "min_count=" ++ show x+minCountString Nothing = ""
+ Data/Factual/Query/GeocodeQuery.hs view
@@ -0,0 +1,20 @@+-- | This module exports the type used to create geopulse queries.+module Data.Factual.Query.GeocodeQuery+ (+ -- * ResolveQuery type+ GeocodeQuery(..)+ -- * Required modules+ , module Data.Factual.Shared.Geo+ ) where++import Data.Factual.Query+import Data.Factual.Shared.Geo++-- | The GeocodeQuery type is used to construct geocode queries. A geo point+-- is required.+data GeocodeQuery = GeocodeQuery Geo deriving (Eq, Show)++-- The GeocodeQuery type is a member of the Query typeclass so it can be used+-- to make a request.+instance Query GeocodeQuery where+ toPath (GeocodeQuery geo) = "/places/geocode?" ++ (geoString $ Just geo)
+ Data/Factual/Query/GeopulseQuery.hs view
@@ -0,0 +1,26 @@+-- | This module exports the type used to create geopulse queries.+module Data.Factual.Query.GeopulseQuery+ (+ -- * ResolveQuery type+ GeopulseQuery(..)+ -- * Required modules+ , module Data.Factual.Shared.Geo+ ) where++import Data.Factual.Query+import Data.Factual.Utils+import Data.Factual.Shared.Geo++-- | The GeopulseQuery type is used to construct geopulse queries. A geo point+-- is required but select values are optional (just use an empty list to+-- denote selecting all pulses).+data GeopulseQuery = GeopulseQuery { geo :: Geo+ , select :: [String]+ } deriving (Eq, Show)++-- The GeopulseQuery type is a member of the Query typeclass so it can be used+-- to make a request.+instance Query GeopulseQuery where+ toPath query = "/places/geopulse?"+ ++ joinAndFilter [ geoString $ Just $ geo query+ , selectString $ select query ]
+ Data/Factual/Query/ReadQuery.hs view
@@ -0,0 +1,52 @@+-- | This module exports the types used to create read queries.+module Data.Factual.Query.ReadQuery+ (+ -- * ReadQuery type+ ReadQuery(..)+ -- * Required modules+ , module Data.Factual.Shared.Table+ , module Data.Factual.Shared.Search+ , module Data.Factual.Shared.Filter+ , module Data.Factual.Shared.Geo+ ) where++import Data.Factual.Query+import Data.Factual.Shared.Table+import Data.Factual.Shared.Search+import Data.Factual.Shared.Filter+import Data.Factual.Shared.Geo+import Data.Factual.Utils++-- | The ReadQuery type is used to construct read queries. A table should be+-- specified, but the rest of the query options are essentially optional+-- (you opt out using Nothing or an empty List for the value). The select is+-- a list of field names to include in the results. The limit and offset are+-- used to request a specific range of rows and includeCount will include the+-- count of returned rows if it is set to True.+data ReadQuery = ReadQuery { table :: Table+ , search :: Search+ , select :: [String]+ , limit :: Maybe Int+ , offset :: Maybe Int+ , filters :: [Filter]+ , geo :: Maybe Geo+ , includeCount :: Bool+ } deriving (Eq, Show)++-- The ReadQuery type is a member of the Query typeclass so it can be used to+-- make a request.+instance Query ReadQuery where+ toPath query = (show $ table query)+ ++ "read?"+ ++ joinAndFilter [ searchString $ search query+ , selectString $ select query+ , limitString $ limit query+ , offsetString $ offset query+ , filtersString $ filters query+ , geoString $ geo query+ , includeCountString $ includeCount query ]++-- The following helper functions are used in generating query Strings.+offsetString :: Maybe Int -> String+offsetString (Just x) = "offset=" ++ show x+offsetString Nothing = ""
+ Data/Factual/Query/ResolveQuery.hs view
@@ -0,0 +1,35 @@+-- | This module exports the type used to create resolve queries.+module Data.Factual.Query.ResolveQuery+ (+ -- * ResolveQuery type+ ResolveQuery(..)+ -- * ResolveValue type+ , ResolveValue(..)+ ) where++import Data.Factual.Query+import Data.Factual.Utils++-- | A resolve value can either be a String or a Number (Double). The first+-- argument is the name of the field and the second argument is the input+-- value.+data ResolveValue = ResolveStr String String+ | ResolveNum String Double+ deriving Eq++-- ResolveValue is a member of the Show typeclass to help generate query Strings.+instance Show ResolveValue where+ show (ResolveStr name str) = (show name) ++ ":" ++ (show str)+ show (ResolveNum name num) = (show name) ++ ":" ++ (show num)++-- | A resolve query is formed as an array of resolve values. These values will+-- be compared with Factual records to return a cleaner, more canonical row+-- of data.+data ResolveQuery = ResolveQuery [ResolveValue] deriving Eq++-- ResolveQuery is a member of the Query typeclass so that it can be used to+-- make requests.+instance Query ResolveQuery where+ toPath (ResolveQuery values) = "/places/resolve?values={"+ ++ (join "," $ map show values)+ ++ "}"
+ Data/Factual/Query/SchemaQuery.hs view
@@ -0,0 +1,19 @@+-- | This module exports the type used to create schema queries.+module Data.Factual.Query.SchemaQuery+ (+ -- * SchemaQuery type+ SchemaQuery(..)+ -- * Required modules+ , module Data.Factual.Shared.Table+ ) where++import Data.Factual.Query+import Data.Factual.Shared.Table++-- | A schema query is formed by simply supplying a Table to the value+-- constructor.+data SchemaQuery = SchemaQuery Table deriving (Eq, Show)++-- SchemaQuery is a member of Query typeclass so that it can generate a response.+instance Query SchemaQuery where+ toPath (SchemaQuery table) = (show $ table) ++ "schema"
− Data/Factual/ReadQuery.hs
@@ -1,153 +0,0 @@--- | This module exports the types used to create read queries.-module Data.Factual.ReadQuery- (- -- * ReadQuery type- ReadQuery(..)- -- * Search type- , Search(..)- -- * Filter type- , Field- , Filter(..)- -- * Geo type- , Lat- , Long- , Radius- , Geo(..)- ) where--import Data.Factual.Query-import Data.Factual.Table-import Data.Factual.Utils---- | A Field is a String representation of the field name.-type Field = String--- | A Lat is the latitude represented as a Double.-type Lat = Double--- | A Long is the longitude represented as a Double.-type Long = Double--- | A Radius is the radius of the circle as a Double in meters.-type Radius = Double---- | This type is used to construct an ANDed or ORed search in a read query.-data Search = AndSearch [String] | OrSearch [String] deriving Eq---- When a Search is shown it displays the string representation that will go--- into the query string.-instance Show Search where- show (AndSearch terms) = join " " terms- show (OrSearch terms) = join "," terms---- | The Filter type is used to represent various filters in a read query.-data Filter = EqualNum Field Double -- ^ A numeric field has to match a number exactly.- | EqualStr Field String -- ^ A string field has to match a string exactly.- | NotEqualNum Field Double -- ^ A numeric field must equal a specific number.- | NotEqualStr Field String -- ^ A string field must equal a specific string.- | InNumList Field [Double] -- ^ A numeric field must be equal to any of the numbers in a list.- | InStrList Field [String] -- ^ A string field must be equal to any of the strings in a list.- | NotInNumList Field [Double] -- ^ A numeric field must not be equal to any of the numbers in a list.- | NotInStrList Field [String] -- ^ A string field must not be equal to any of the strings in a list.- | BeginsWith Field String -- ^ A string field must begin with a specific string.- | NotBeginsWith Field String -- ^ A string field must not begin with a specific string.- | BeginsWithAny Field [String] -- ^ A string field must begin with any of the strings in a list.- | NotBeginsWithAny Field [String] -- ^ A string field must not begin with any of the strings in a list.- | IsBlank Field -- ^ A field must be blank.- | IsNotBlank Field -- ^ A field must not be blank.- | And [Filter] -- ^ Form an AND condition with the filters in the list.- | Or [Filter] -- ^ Form an OR condition with the filters in the list.- deriving Eq---- Filter is a member of Show to help generate query strings.-instance Show Filter where- show (EqualNum field num) = (show field) ++ ":" ++ (show num)- show (EqualStr field str) = (show field) ++ ":" ++ (show str)- show (NotEqualNum field num) = (show field) ++ ":{" ++ (show "$neq") ++ ":" ++ (show num) ++ "}"- show (NotEqualStr field str) = (show field) ++ ":{" ++ (show "$neq") ++ ":" ++ (show str) ++ "}"- show (InNumList field nums) = (show field) ++ ":{" ++ (show "$in") ++ ":[" ++ (join "," $ map show nums) ++ "]}"- show (InStrList field strs) = (show field) ++ ":{" ++ (show "$in") ++ ":[" ++ (join "," $ map show strs) ++ "]}"- show (NotInNumList field nums) = (show field) ++ ":{" ++ (show "$nin") ++ ":[" ++ (join "," $ map show nums) ++ "]}"- show (NotInStrList field strs) = (show field) ++ ":{" ++ (show "$nin") ++ ":[" ++ (join "," $ map show strs) ++ "]}"- show (BeginsWith field str) = (show field) ++ ":{" ++ (show "$bw") ++ ":" ++ (show str) ++ "}"- show (NotBeginsWith field str) = (show field) ++ ":{" ++ (show "$nbw") ++ ":" ++ (show str) ++ "}"- show (BeginsWithAny field strs) = (show field) ++ ":{" ++ (show "$bwin") ++ ":[" ++ (join "," $ map show strs) ++ "]}"- show (NotBeginsWithAny field strs) = (show field) ++ ":{" ++ (show "$nbwin") ++ ":[" ++ (join "," $ map show strs) ++ "]}"- show (IsBlank field) = (show field) ++ ":{\"$blank\":true}"- show (IsNotBlank field) = (show field) ++ ":{\"$blank\":false}"- show (And filters) = (show "$and") ++ ":[" ++ (join "," $ map showFilter filters) ++ "]"- show (Or filters) = (show "$or") ++ ":[" ++ (join "," $ map showFilter filters) ++ "]"---- | The Geo type is used to limit the search to specific geograph location.--- Currently, only circles are supported. Supply a latitude, longitude and--- radius in meters for the circle.-data Geo = Circle Lat Long Radius deriving Eq---- Geo is a member of Show to help generate query strings.-instance Show Geo where- show (Circle lat long radius) = "{\"$circle\":{\"$center\":[" - ++ (show lat) - ++ ", "- ++ (show long)- ++ "],\"$meters\":"- ++ (show radius)- ++ "}}"---- | The ReadQuery type is used to construct read queries. A table should be--- specified, but the rest of the query options are essentially optional--- (you opt out using Nothing or an empty List for the value). The select is--- a list of field names to include in the results. The limit and offset are--- used to request a specific range of rows and includeCount will include the--- count of returned rows if it is set to True.-data ReadQuery = ReadQuery { table :: Table- , search :: Search- , select :: [String]- , limit :: Maybe Int- , offset :: Maybe Int- , filters :: [Filter]- , geo :: Maybe Geo- , includeCount :: Bool- } deriving (Eq, Show)---- The ReadQuery type is a member of the Query typeclass so it can be used to--- make a request.-instance Query ReadQuery where- toPath query = (show $ table query)- ++ "read?"- ++ joinAndFilter [ searchString $ search query- , selectString $ select query- , limitString $ limit query- , offsetString $ offset query- , filtersString $ filters query- , geoString $ geo query- , includeCountString $ includeCount query ]---- The following helper functions are used in generating query Strings.-showFilter :: Filter -> String-showFilter filter = "{" ++ (show filter) ++ "}"--searchString :: Search -> String-searchString (AndSearch []) = ""-searchString (OrSearch []) = ""-searchString search = "q=" ++ show search--selectString :: [String] -> String-selectString [] = ""-selectString selects = "select=" ++ (join "," selects)--limitString :: Maybe Int -> String-limitString (Just x) = "limit=" ++ show x-limitString Nothing = ""--offsetString :: Maybe Int -> String-offsetString (Just x) = "offset=" ++ show x-offsetString Nothing = ""--filtersString :: [Filter] -> String-filtersString [] = ""-filtersString fs = "filters={" ++ (join "," $ map show fs) ++ "}"--geoString :: Maybe Geo -> String-geoString (Just c) = "geo=" ++ show c-geoString Nothing = ""--includeCountString :: Bool -> String-includeCountString True = "include_count=true"-includeCountString False = "include_count=false"
− Data/Factual/ResolveQuery.hs
@@ -1,35 +0,0 @@--- | This module exports the type used to create resolve queries.-module Data.Factual.ResolveQuery- (- -- * ResolveQuery type- ResolveQuery(..)- -- * ResolveValue type- , ResolveValue(..)- ) where--import Data.Factual.Query-import Data.Factual.Utils---- | A resolve value can either be a String or a Number (Double). The first--- argument is the name of the field and the second argument is the input--- value.-data ResolveValue = ResolveStr String String- | ResolveNum String Double- deriving Eq---- ResolveValue is a member of the Show typeclass to help generate query Strings.-instance Show ResolveValue where- show (ResolveStr name str) = (show name) ++ ":" ++ (show str)- show (ResolveNum name num) = (show name) ++ ":" ++ (show num)---- | A resolve query is formed as an array of resolve values. These values will--- be compared with Factual records to return a cleaner, more canonical row--- of data.-data ResolveQuery = ResolveQuery [ResolveValue] deriving Eq---- ResolveQuery is a member of the Query typeclass so that it can be used to--- make requests.-instance Query ResolveQuery where- toPath (ResolveQuery values) = "/places/resolve?values={"- ++ (join "," $ map show values)- ++ "}"
− Data/Factual/SchemaQuery.hs
@@ -1,17 +0,0 @@--- | This module exports the type used to create schema queries.-module Data.Factual.SchemaQuery- (- -- * SchemaQuery type- SchemaQuery(..)- ) where--import Data.Factual.Query-import Data.Factual.Table---- | A schema query is formed by simply supplying a Table to the value--- constructor.-data SchemaQuery = SchemaQuery Table deriving (Eq, Show)---- SchemaQuery is a member of Query typeclass so that it can generate a response.-instance Query SchemaQuery where- toPath (SchemaQuery table) = (show $ table) ++ "schema"
@@ -0,0 +1,60 @@+-- | This module exports the Filter type used to create read and facet queries.+module Data.Factual.Shared.Filter+ (+ -- * Filter type+ Field+ , Filter(..)+ -- * Helper functions+ , filtersString+ ) where++import Data.Factual.Utils++-- | A Field is a String representation of the field name.+type Field = String++-- | The Filter type is used to represent various filters in a read or facets query.+data Filter = EqualNum Field Double -- ^ A numeric field has to match a number exactly.+ | EqualStr Field String -- ^ A string field has to match a string exactly.+ | NotEqualNum Field Double -- ^ A numeric field must equal a specific number.+ | NotEqualStr Field String -- ^ A string field must equal a specific string.+ | InNumList Field [Double] -- ^ A numeric field must be equal to any of the numbers in a list.+ | InStrList Field [String] -- ^ A string field must be equal to any of the strings in a list.+ | NotInNumList Field [Double] -- ^ A numeric field must not be equal to any of the numbers in a list.+ | NotInStrList Field [String] -- ^ A string field must not be equal to any of the strings in a list.+ | BeginsWith Field String -- ^ A string field must begin with a specific string.+ | NotBeginsWith Field String -- ^ A string field must not begin with a specific string.+ | BeginsWithAny Field [String] -- ^ A string field must begin with any of the strings in a list.+ | NotBeginsWithAny Field [String] -- ^ A string field must not begin with any of the strings in a list.+ | IsBlank Field -- ^ A field must be blank.+ | IsNotBlank Field -- ^ A field must not be blank.+ | And [Filter] -- ^ Form an AND condition with the filters in the list.+ | Or [Filter] -- ^ Form an OR condition with the filters in the list.+ deriving Eq++-- Filter is a member of Show to help generate query strings.+instance Show Filter where+ show (EqualNum field num) = (show field) ++ ":" ++ (show num)+ show (EqualStr field str) = (show field) ++ ":" ++ (show str)+ show (NotEqualNum field num) = (show field) ++ ":{" ++ (show "$neq") ++ ":" ++ (show num) ++ "}"+ show (NotEqualStr field str) = (show field) ++ ":{" ++ (show "$neq") ++ ":" ++ (show str) ++ "}"+ show (InNumList field nums) = (show field) ++ ":{" ++ (show "$in") ++ ":[" ++ (join "," $ map show nums) ++ "]}"+ show (InStrList field strs) = (show field) ++ ":{" ++ (show "$in") ++ ":[" ++ (join "," $ map show strs) ++ "]}"+ show (NotInNumList field nums) = (show field) ++ ":{" ++ (show "$nin") ++ ":[" ++ (join "," $ map show nums) ++ "]}"+ show (NotInStrList field strs) = (show field) ++ ":{" ++ (show "$nin") ++ ":[" ++ (join "," $ map show strs) ++ "]}"+ show (BeginsWith field str) = (show field) ++ ":{" ++ (show "$bw") ++ ":" ++ (show str) ++ "}"+ show (NotBeginsWith field str) = (show field) ++ ":{" ++ (show "$nbw") ++ ":" ++ (show str) ++ "}"+ show (BeginsWithAny field strs) = (show field) ++ ":{" ++ (show "$bwin") ++ ":[" ++ (join "," $ map show strs) ++ "]}"+ show (NotBeginsWithAny field strs) = (show field) ++ ":{" ++ (show "$nbwin") ++ ":[" ++ (join "," $ map show strs) ++ "]}"+ show (IsBlank field) = (show field) ++ ":{\"$blank\":true}"+ show (IsNotBlank field) = (show field) ++ ":{\"$blank\":false}"+ show (And filters) = (show "$and") ++ ":[" ++ (join "," $ map showFilter filters) ++ "]"+ show (Or filters) = (show "$or") ++ ":[" ++ (join "," $ map showFilter filters) ++ "]"++-- The following helper functions are used in generating query Strings.+showFilter :: Filter -> String+showFilter filter = "{" ++ (show filter) ++ "}"++filtersString :: [Filter] -> String+filtersString [] = ""+filtersString fs = "filters={" ++ (join "," $ map show fs) ++ "}"
@@ -0,0 +1,45 @@+-- | This module exports the Geo type used to create read and facet queries.+module Data.Factual.Shared.Geo+ (+ -- * Geo type+ Lat+ , Long+ , Radius+ , Geo(..)+ -- * Helper functions+ , geoString+ ) where++-- | A Lat is the latitude represented as a Double.+type Lat = Double+-- | A Long is the longitude represented as a Double.+type Long = Double+-- | A Radius is the radius of the circle as a Double in meters.+type Radius = Double++-- | The Geo type is used to limit the search to specific geograph location.+-- Currently, only circles are supported. Supply a latitude, longitude and+-- radius in meters for the circle.+data Geo = Circle Lat Long Radius+ | Point Lat Long+ deriving Eq++-- Geo is a member of Show to help generate query strings.+instance Show Geo where+ show (Circle lat long radius) = "{\"$circle\":{\"$center\":[" + ++ (show lat) + ++ ", "+ ++ (show long)+ ++ "],\"$meters\":"+ ++ (show radius)+ ++ "}}"+ show (Point lat long) = "{\"$point\":["+ ++ (show lat) + ++ ","+ ++ (show long)+ ++ "]}"++-- Helper functions+geoString :: Maybe Geo -> String+geoString (Just c) = "geo=" ++ show c+geoString Nothing = ""
@@ -0,0 +1,26 @@+-- | This module exports the Search type used to create read and facet queries.+module Data.Factual.Shared.Search+ (+ -- * Search type+ Search(..)+ -- * Helper functions+ , searchString+ ) where++import Data.Factual.Utils++-- | This type is used to construct an ANDed or ORed search in a query.+data Search = AndSearch [String] | OrSearch [String] | NoSearch deriving Eq++-- When a Search is shown it displays the string representation that will go+-- into the query string.+instance Show Search where+ show (AndSearch terms) = join " " terms+ show (OrSearch terms) = join "," terms+ show NoSearch = ""++-- Helper functions+searchString :: Search -> String+searchString (AndSearch []) = ""+searchString (OrSearch []) = ""+searchString search = "q=" ++ show search
@@ -0,0 +1,21 @@+-- | This module exports the type used to represent a table for the read or+-- schema query types.+module Data.Factual.Shared.Table+ (+ -- * Table type+ Table(..)+ ) where++-- | Three Table values are currently available for the Factual API.+data Table = Places+ | USRestaurants+ | Global+ | Custom String+ deriving Eq++-- Table is a member of the Show typeclass to generate the beginning of the path.+instance Show Table where+ show Places = "/t/places/"+ show USRestaurants = "/t/restaurants-us/"+ show Global = "/t/global/"+ show (Custom name) = "/t/" ++ name ++ "/"
− Data/Factual/Table.hs
@@ -1,16 +0,0 @@--- | This module exports the type used to represent a table for the read or--- schema query types.-module Data.Factual.Table- (- -- * Table type- Table(..)- ) where---- | Three Table values are currently available for the Factual API.-data Table = Places | USRestaurants | Global deriving Eq---- Table is a member of the Show typeclass to generate the beginning of the path.-instance Show Table where- show Places = "/t/places/"- show USRestaurants = "/t/restaurants-us/"- show Global = "/t/global/"
Data/Factual/Utils.hs view
@@ -4,6 +4,9 @@ -- * Utility methods join , joinAndFilter+ , selectString+ , limitString+ , includeCountString ) where import Data.List (intersperse)@@ -18,3 +21,16 @@ -- Strings with an & character. The use case is forming query path Strings. joinAndFilter :: [String] -> String joinAndFilter strs = join "&" $ filter ("" /=) strs++-- The following helper functions are used in generating query Strings.+selectString :: [String] -> String+selectString [] = ""+selectString selects = "select=" ++ (join "," selects)++limitString :: Maybe Int -> String+limitString (Just x) = "limit=" ++ show x+limitString Nothing = ""++includeCountString :: Bool -> String+includeCountString True = "include_count=true"+includeCountString False = "include_count=false"
+ Data/Factual/Write.hs view
@@ -0,0 +1,12 @@+-- | This module exports the definition of the Write typeclass.+module Data.Factual.Write+ (+ -- * Query typeclass+ Write(..)) where++-- | A member of the Write typeclass must define a url method which returns+-- the write url as a string and a body method which returns of the body+-- of the write.+class Write w where+ path :: w -> String+ body :: w -> String
+ Data/Factual/Write/Flag.hs view
@@ -0,0 +1,64 @@+-- | This module exports the types used to create flag writes.+module Data.Factual.Write.Flag+ (+ -- * Flag type+ Flag(..)+ -- * Problem type+ , Problem(..)+ -- * Required modules+ , module Data.Factual.Shared.Table+ ) where++import Data.Factual.Write+import Data.Factual.Shared.Table+import Data.Maybe (fromJust)+import Data.Factual.Utils++-- | A Problem represents what is wrong with the row being flagged+data Problem = Duplicate+ | Nonexistent+ | Inaccurate+ | Inappropriate+ | Spam+ | Other+ deriving (Eq, Show)++-- | The Flag type represents a Write to be made to the API which flags a+-- row as having some kind of problem. The table and factualId identify the+-- problematic row, while the problem indicates the type of issue the row+-- has. The user is specified as a string. Other fields such as comment and+-- reference are optional. The debug flag is used to write in debug mode.+data Flag = Flag { table :: Table+ , factualId :: String+ , problem :: Problem+ , user :: String+ , comment :: Maybe String+ , debug :: Bool+ , reference :: Maybe String+ } deriving (Eq, Show)++-- The Flag type is a member of the Write typeclass so it can be sent as a post+-- request to the API.+instance Write Flag where+ path flag = (show $ table flag) ++ (factualId flag) ++ "/flag"+ body flag = "problem=" ++ (show $ problem flag) ++ "&" +++ "user=" ++ (user flag) ++ "&" +++ joinAndFilter [ commentString flag+ , debugString flag+ , referenceString flag ]++-- The following functions are helpers for the body function+commentString :: Flag -> String+commentString flag+ | comment flag == Nothing = ""+ | otherwise = "comment=" ++ (fromJust $ comment flag)++debugString :: Flag -> String+debugString flag+ | debug flag == True = "debug=true"+ | otherwise = "debug=false"++referenceString :: Flag -> String+referenceString flag+ | reference flag == Nothing = ""+ | otherwise = "reference=" ++ (fromJust $ reference flag)
+ Data/Factual/Write/Submit.hs view
@@ -0,0 +1,44 @@+-- | This module exports the types used to create contributions.+module Data.Factual.Write.Submit+ (+ -- * Submit type+ Submit(..)+ -- * Required modules+ , module Data.Factual.Shared.Table+ ) where++import Data.Factual.Write+import Data.Factual.Shared.Table+import Data.Maybe (fromJust)+import Data.Factual.Utils+import qualified Data.Map as M++-- | The Submit type represents a Write to the API which performs an upsert+-- (a row can be updated or a new row can be written). The table and user+-- must be specified, while the factual ID is optional (omitted for new+-- rows). Finally the values are specified in a String to String Map.+data Submit = Submit { table :: Table+ , user :: String+ , factualId :: Maybe String+ , values :: M.Map String String+ } deriving (Eq, Show)++-- The Submit type is a member of the Write typeclass so that it can be+-- sent as a post request to the API.+instance Write Submit where+ path submit = pathString submit+ body submit = "user=" ++ (user submit) ++ "&" +++ "values=" ++ valuesString (values submit)++-- The following functions are helpers for the Write typeclass functions.+pathString :: Submit -> String+pathString submit+ | factualId submit == Nothing = (show $ table submit) ++ "submit"+ | otherwise = (show $ table submit) +++ (fromJust $ factualId submit) +++ "/submit"++valuesString :: M.Map String String -> String+valuesString values = "{" ++ join "," (map valueString $ M.keys values) ++ "}"+ where valueString key = "\"" ++ key ++ "\":\"" +++ (fromJust $ M.lookup key values) ++ "\""
Network/Factual/API.hs view
@@ -2,42 +2,75 @@ -- the OAuth authentication process. module Network.Factual.API (- -- * API functions- makeRequest- , generateToken+ -- * Authentication+ generateToken+ -- * Read functions+ , makeRequest+ , makeRawRequest+ -- * Write functions+ , sendWrite -- * The hoauth Token type , Token(..) ) where import Data.Maybe (fromJust) import Network.OAuth.Consumer-import Network.OAuth.Http.Request (Request(..), parseURL, fromList)+import Network.OAuth.Http.Request (Request(..), Method(..), parseURL, fromList) import Network.OAuth.Http.Response (Response(..)) import Network.OAuth.Http.CurlHttpClient (CurlClient(..)) import Data.Aeson (Value, decode) import Data.Factual.Query-import Data.Factual.Credentials+import Data.Factual.Write+import qualified Data.ByteString.Lazy.Char8 as B import qualified Data.Factual.Response as F +-- | The Key is the oauth key as a String.+type Key = String+-- | The Secret is the oauth secret as a String.+type Secret = String++-- | This function takes a set of credentials and returns an OAuth token that+-- can be used to make requests.+generateToken :: Key -> Secret -> Token+generateToken key secret = fromApplication $ Application key secret OOB+ -- | This function takes an OAuth token and a query (which is member of the--- Query typeclass and returns an IO action which will fetch a response from+-- Query typeclass) and returns an IO action which will fetch a response from -- the Factual API. makeRequest :: (Query query) => Token -> query -> IO F.Response-makeRequest token query = do- let fullpath = "http://api.v3.factual.com" ++ toPath query+makeRequest token query = makeRawRequest token (toPath query)++-- | This function can be used to make raw read requests for any path. You pass+-- in your Token and the path of your request (e.g. \"\/t\/places?q=starbucks\")+makeRawRequest :: Token -> String -> IO F.Response+makeRawRequest token queryString = do+ let fullpath = basePath ++ queryString let request = generateRequest fullpath- response <- runOAuthM token $ setupOAuth request- return $ F.fromValue $ extractJSON response+ makeRequest' token request --- | This function takes a set of credentials and returns an OAuth token that--- can be used to make requests.-generateToken :: Credentials -> Token-generateToken (Credentials key secret) = fromApplication $ Application key secret OOB+-- | This function takes an OAuth token and a Write and retunrs and IO action+-- which sends the Write to API and returns a Response.+sendWrite :: (Write write) => Token -> write -> IO F.Response+sendWrite token write = do+ let fullpath = basePath ++ path write+ let request = generatePostRequest fullpath (body write)+ makeRequest' token request -- The following helper functions aid the exported API functions+makeRequest' :: Token -> Request -> IO F.Response+makeRequest' token request = do+ response <- runOAuthM token $ setupOAuth request+ return $ F.fromValue $ extractJSON response+ generateRequest :: String -> Request generateRequest url = (fromJust $ parseURL url) { reqHeaders = (fromList headersList) } +generatePostRequest :: String -> String -> Request+generatePostRequest url body = baseRequest { reqHeaders = (fromList headersList)+ , method = POST+ , reqPayload = B.pack body }+ where baseRequest = (fromJust $ parseURL url)+ setupOAuth :: Request -> OAuthMonadT IO Response setupOAuth request = do oauthRequest <- signRq2 HMACSHA1 Nothing request@@ -47,4 +80,7 @@ extractJSON = fromJust . decode . rspPayload headersList :: [(String, String)]-headersList = [("X-Factual-Lib", "factual-haskell-driver-0.1.2")]+headersList = [("X-Factual-Lib", "factual-haskell-driver-0.2.0")]++basePath :: String+basePath = "http://api.v3.factual.com"
README.md view
@@ -31,15 +31,15 @@ $ ghci test/Tests.hs -To run the response tests you'll need an API key and secret, but you-can always run the query tests:+To run the integration tests you'll need an API key and secret, but you+can always run the unit tests: - *Main> runQueryTests- Cases: 33 Tried: 33 Errors: 0 Failures: 0- Counts {cases = 33, tried = 33, errors = 0, failures = 0}- *Main> runResponseTests "mykey" "mysecret"- Cases: 4 Tried: 4 Errors: 0 Failures: 0- Counts {cases = 4, tried = 4, errors = 0, failures = 0}+ *Main> runUnitTests+ Cases: 41 Tried: 41 Errors: 0 Failures: 0+ Counts {cases = 41, tried = 41, errors = 0, failures = 0}+ *Main> runIntegrationTests "mykey" "mysecret"+ Cases: 8 Tried: 8 Errors: 0 Failures: 0+ Counts {cases = 8, tried = 8, errors = 0, failures = 0} # Documentation
examples/CrosswalkExample.hs view
@@ -2,9 +2,7 @@ import System (getArgs) import Network.Factual.API-import Data.Factual.Credentials-import Data.Factual.Table-import Data.Factual.CrosswalkQuery+import Data.Factual.Query.CrosswalkQuery import Data.Factual.Response main :: IO()@@ -12,7 +10,7 @@ args <- getArgs let oauthKey = head args let oauthSecret = last args- let token = generateToken (Credentials oauthKey oauthSecret)+ let token = generateToken oauthKey oauthSecret let query = CrosswalkQuery { factualId = Just "97598010-433f-4946-8fd5-4a6dd1639d77" , limit = Nothing , namespace = Nothing
+ examples/FacetsExample.hs view
@@ -0,0 +1,25 @@+module Main where++import System (getArgs)+import Network.Factual.API+import Data.Factual.Query.FacetsQuery+import Data.Factual.Response++main :: IO()+main = do+ args <- getArgs+ let oauthKey = head args+ let oauthSecret = last args+ let token = generateToken oauthKey oauthSecret+ let query = FacetsQuery { table = Places+ , search = AndSearch ["Starbucks"]+ , select = ["country"]+ , filters = []+ , geo = Nothing+ , limit = Nothing+ , minCount = Nothing+ , includeCount = False }+ result <- makeRequest token query+ putStrLn $ "Status: " ++ status result+ putStrLn $ "Version: " ++ show (version result)+ putStrLn $ "Data: " ++ show (response result)
+ examples/GeocodeExample.hs view
@@ -0,0 +1,18 @@+module Main where++import System (getArgs)+import Network.Factual.API+import Data.Factual.Query.GeocodeQuery+import Data.Factual.Response++main :: IO()+main = do+ args <- getArgs+ let oauthKey = head args+ let oauthSecret = last args+ let token = generateToken oauthKey oauthSecret+ let query = GeocodeQuery $ Point 34.06021 (-118.41828)+ result <- makeRequest token query+ putStrLn $ "Status: " ++ status result+ putStrLn $ "Version: " ++ show (version result)+ putStrLn $ "Data: " ++ show (response result)
+ examples/GeopulseExample.hs view
@@ -0,0 +1,19 @@+module Main where++import System (getArgs)+import Network.Factual.API+import Data.Factual.Query.GeopulseQuery+import Data.Factual.Response++main :: IO()+main = do+ args <- getArgs+ let oauthKey = head args+ let oauthSecret = last args+ let token = generateToken oauthKey oauthSecret+ let query = GeopulseQuery { geo = Point 34.06021 (-118.41828)+ , select = ["commercial_density"] }+ result <- makeRequest token query+ putStrLn $ "Status: " ++ status result+ putStrLn $ "Version: " ++ show (version result)+ putStrLn $ "Data: " ++ show (response result)
+ examples/RawReadExample.hs view
@@ -0,0 +1,16 @@+module Main where++import System (getArgs)+import Network.Factual.API+import Data.Factual.Response++main :: IO()+main = do+ args <- getArgs+ let oauthKey = head args+ let oauthSecret = last args+ let token = generateToken oauthKey oauthSecret+ result <- makeRawRequest token "/t/places?q=starbucks"+ putStrLn $ "Status: " ++ status result+ putStrLn $ "Version: " ++ show (version result)+ putStrLn $ "Data: " ++ show (response result)
examples/ReadExample.hs view
@@ -2,9 +2,7 @@ import System (getArgs) import Network.Factual.API-import Data.Factual.Credentials-import Data.Factual.Table-import Data.Factual.ReadQuery+import Data.Factual.Query.ReadQuery import Data.Factual.Response main :: IO()@@ -12,7 +10,7 @@ args <- getArgs let oauthKey = head args let oauthSecret = last args- let token = generateToken (Credentials oauthKey oauthSecret)+ let token = generateToken oauthKey oauthSecret let query = ReadQuery { table = Places , search = AndSearch [] , select = ["name"]
examples/ResolveExample.hs view
@@ -2,9 +2,7 @@ import System (getArgs) import Network.Factual.API-import Data.Factual.Credentials-import Data.Factual.Table-import Data.Factual.ResolveQuery+import Data.Factual.Query.ResolveQuery import Data.Factual.Response main :: IO()@@ -12,7 +10,7 @@ args <- getArgs let oauthKey = head args let oauthSecret = last args- let token = generateToken (Credentials oauthKey oauthSecret)+ let token = generateToken oauthKey oauthSecret let query = ResolveQuery [ ResolveStr "name" "McDonalds" , ResolveStr "address" "10451 Santa Monica Blvd" ] result <- makeRequest token query
examples/SchemaExample.hs view
@@ -2,9 +2,7 @@ import System (getArgs) import Network.Factual.API-import Data.Factual.Credentials-import Data.Factual.Table-import Data.Factual.SchemaQuery+import Data.Factual.Query.SchemaQuery import Data.Factual.Response main :: IO()@@ -12,7 +10,7 @@ args <- getArgs let oauthKey = head args let oauthSecret = last args- let token = generateToken (Credentials oauthKey oauthSecret)+ let token = generateToken oauthKey oauthSecret let query = SchemaQuery Places result <- makeRequest token query putStrLn $ "Status: " ++ status result
factual-api.cabal view
@@ -1,12 +1,12 @@ name: factual-api-version: 0.1.2+version: 0.2.0 license: BSD3 license-file: LICENSE.txt category: API, Web copyright: (c) 2011 Rudiger Lippert author: Rudiger Lippert <rudy@factual.com> maintainer: Rudiger Lippert <rudy@factual.com>-stability: experimental+stability: stable tested-with: GHC == 7.0.4 synopsis: A driver for the Factual API cabal-version: >= 1.8@@ -14,9 +14,8 @@ bug-reports: https://github.com/rudyl313/factual-haskell-driver/issues build-type: Simple description:- This is a driver for the Factual API. It provides a typesafe way to- generate queries, an easy way to setup OAuth authentication and a- simple way to send queries to the API.+ This is a driver for the Factual API. It provides a type-safe, easy way to+ generate queries, setup OAuth authentication and send queries to the API. extra-source-files: README.md@@ -27,14 +26,22 @@ library exposed-modules: Network.Factual.API- Data.Factual.Credentials- Data.Factual.CrosswalkQuery Data.Factual.Query- Data.Factual.ReadQuery- Data.Factual.ResolveQuery+ Data.Factual.Query.CrosswalkQuery+ Data.Factual.Query.FacetsQuery+ Data.Factual.Query.GeocodeQuery+ Data.Factual.Query.GeopulseQuery+ Data.Factual.Query.ReadQuery+ Data.Factual.Query.ResolveQuery+ Data.Factual.Query.SchemaQuery+ Data.Factual.Write+ Data.Factual.Write.Flag+ Data.Factual.Write.Submit Data.Factual.Response- Data.Factual.SchemaQuery- Data.Factual.Table+ Data.Factual.Shared.Filter+ Data.Factual.Shared.Geo+ Data.Factual.Shared.Search+ Data.Factual.Shared.Table other-modules: Data.Factual.Utils@@ -43,7 +50,7 @@ aeson >= 0.6.0.0, attoparsec >= 0.10.1.0, base == 4.*,- hoauth >= 0.3.3,+ hoauth >= 0.3.0, text >= 0.11.1.5, unordered-containers >= 0.1.4.6, vector >= 0.9.1
test/Tests.hs view
@@ -1,57 +1,74 @@ import Test.HUnit import Network.Factual.API import Data.Factual.Query-import Data.Factual.Table-import Data.Factual.ReadQuery-import Data.Factual.SchemaQuery-import Data.Factual.ResolveQuery+import Data.Factual.Query.ReadQuery+import Data.Factual.Query.SchemaQuery+import Data.Factual.Query.ResolveQuery+import Data.Factual.Query.GeocodeQuery import Data.Factual.Response-import Data.Factual.Credentials-import qualified Data.Factual.CrosswalkQuery as C+import qualified Data.Map as M+import qualified Data.Factual.Write as W+import qualified Data.Factual.Query.CrosswalkQuery as C+import qualified Data.Factual.Query.FacetsQuery as F+import qualified Data.Factual.Query.GeopulseQuery as G+import qualified Data.Factual.Write.Submit as S+import qualified Data.Factual.Write.Flag as L -runQueryTests = runTestTT queryTests+runUnitTests = runTestTT unitTests -runResponseTests key secret = runTestTT $ responseTests (Credentials key secret)+runIntegrationTests key secret = runTestTT $ integrationTests key secret -queryTests = TestList [ TestLabel "Place table test" placeTablePathTest- , TestLabel "Restaurants table test" restaurantsTablePathTest- , TestLabel "Global table test" globalTablePathTest- , TestLabel "And search test" andSearchPathTest- , TestLabel "Or search test" orSearchPathTest- , TestLabel "Select test" selectPathTest- , TestLabel "Limit test" limitPathTest- , TestLabel "Offset test" offsetPathTest- , TestLabel "Equal number filter test" equalNumFilterTest- , TestLabel "Equal string filter test" equalStrFilterTest- , TestLabel "Not equal number filter test" notEqualNumFilterTest- , TestLabel "Not equal string filter test" notEqualStrFilterTest- , TestLabel "In number list filter test" inNumListFilterTest- , TestLabel "In string list filter test" inStrListFilterTest- , TestLabel "Not in number list filter test" notInNumListFilterTest- , TestLabel "Not in string list filter test" notInStrListFilterTest- , TestLabel "Begins with filter test" beginsWithFilterTest- , TestLabel "Not begins with filter test" notBeginsWithFilterTest- , TestLabel "Begins with any filter test" beginsWithAnyFilterTest- , TestLabel "Not begins with any filter test" notBeginsWithAnyFilterTest- , TestLabel "Is blank filter test" isBlankFilterTest- , TestLabel "Is not blank filter test" isNotBlankFilterTest- , TestLabel "And filter test" andFilterTest- , TestLabel "Or filter test" andFilterTest- , TestLabel "Geo test" geoTest- , TestLabel "Include count test" includeCountTest- , TestLabel "Schema query test" schemaQueryTest- , TestLabel "Resolve query test" resolveQueryTest- , TestLabel "Factual ID test" factualIdTest- , TestLabel "Crosswalk limit test" limitCWPathTest- , TestLabel "Namespace test" namespaceTest- , TestLabel "Namespace ID test" namespaceIdTest- , TestLabel "Only test" onlyTest ]+unitTests = TestList [ TestLabel "Place table test" placeTablePathTest+ , TestLabel "Restaurants table test" restaurantsTablePathTest+ , TestLabel "Global table test" globalTablePathTest+ , TestLabel "Custom table test" customTablePathTest+ , TestLabel "Geopulse test" geopulsePathTest+ , TestLabel "Geocode test" geocodePathTest+ , TestLabel "And search test" andSearchPathTest+ , TestLabel "Or search test" orSearchPathTest+ , TestLabel "Select test" selectPathTest+ , TestLabel "Limit test" limitPathTest+ , TestLabel "Offset test" offsetPathTest+ , TestLabel "Equal number filter test" equalNumFilterTest+ , TestLabel "Equal string filter test" equalStrFilterTest+ , TestLabel "Not equal number filter test" notEqualNumFilterTest+ , TestLabel "Not equal string filter test" notEqualStrFilterTest+ , TestLabel "In number list filter test" inNumListFilterTest+ , TestLabel "In string list filter test" inStrListFilterTest+ , TestLabel "Not in number list filter test" notInNumListFilterTest+ , TestLabel "Not in string list filter test" notInStrListFilterTest+ , TestLabel "Begins with filter test" beginsWithFilterTest+ , TestLabel "Not begins with filter test" notBeginsWithFilterTest+ , TestLabel "Begins with any filter test" beginsWithAnyFilterTest+ , TestLabel "Not begins with any filter test" notBeginsWithAnyFilterTest+ , TestLabel "Is blank filter test" isBlankFilterTest+ , TestLabel "Is not blank filter test" isNotBlankFilterTest+ , TestLabel "And filter test" andFilterTest+ , TestLabel "Or filter test" andFilterTest+ , TestLabel "Geo test" geoTest+ , TestLabel "Include count test" includeCountTest+ , TestLabel "Schema query test" schemaQueryTest+ , TestLabel "Resolve query test" resolveQueryTest+ , TestLabel "Factual ID test" factualIdTest+ , TestLabel "Crosswalk limit test" limitCWPathTest+ , TestLabel "Namespace test" namespaceTest+ , TestLabel "Namespace ID test" namespaceIdTest+ , TestLabel "Only test" onlyTest+ , TestLabel "Facets test" facetsTest+ , TestLabel "Submit path test" submitPathTest+ , TestLabel "Submit body test" submitBodyTest+ , TestLabel "Flag path test" flagPathTest+ , TestLabel "Flag body test" flagBodyTest ] -responseTests creds = TestList [ TestLabel "Read test" (readResponseTest token)- , TestLabel "Schema test" (schemaResponseTest token)- , TestLabel "Resolve test" (resolveResponseTest token)- , TestLabel "Crosswalk test" (crosswalkResponseTest token) ]- where token = generateToken creds+integrationTests key secret = TestList [ TestLabel "Read test" (readIntegrationTest token)+ , TestLabel "Schema test" (schemaIntegrationTest token)+ , TestLabel "Resolve test" (resolveIntegrationTest token)+ , TestLabel "Crosswalk test" (crosswalkIntegrationTest token)+ , TestLabel "Raw read test" (rawIntegrationTest token)+ , TestLabel "Facets test" (facetsIntegrationTest token)+ , TestLabel "Geopulse test" (geopulseIntegrationTest token)+ , TestLabel "Geocode test" (geocodeIntegrationTest token) ]+ where token = generateToken key secret placeTablePathTest = TestCase (do let expected = "/t/places/read?include_count=false"@@ -68,6 +85,21 @@ let path = toPath $ blankReadQuery { table = Global } assertEqual "Correct path for global table" expected path) +customTablePathTest = TestCase (do+ let expected = "/t/foo/read?include_count=false"+ let path = toPath $ blankReadQuery { table = Custom "foo" }+ assertEqual "Correct path for custom table" expected path)++geopulsePathTest = TestCase (do+ let expected = "/places/geopulse?geo={\"$point\":[34.06021,-118.41828]}&select=commercial_density"+ let path = toPath $ G.GeopulseQuery { G.geo = Point 34.06021 (-118.41828) , G.select = ["commercial_density"] }+ assertEqual "Correct path for geopulse" expected path)++geocodePathTest = TestCase (do+ let expected = "/places/geocode?geo={\"$point\":[34.06021,-118.41828]}"+ let path = toPath $ GeocodeQuery $ Point 34.06021 (-118.41828)+ assertEqual "Correct path for geocode" expected path)+ andSearchPathTest = TestCase (do let expected = "/t/places/read?q=foo bar&include_count=false" let path = toPath $ blankReadQuery { search = AndSearch ["foo", "bar"] }@@ -218,8 +250,42 @@ let path = toPath $ blankCrosswalkQuery { C.only = ["yelp", "loopd"] } assertEqual "Correct path for a only" expected path) -readResponseTest :: Token -> Test-readResponseTest token = TestCase (do+facetsTest = TestCase (do+ let expected = "/t/places/facets?q=starbucks&select=locality,region&"+ ++ "filters={\"country\":\"US\"}&limit=10&min_count=2&"+ ++ "include_count=false"+ let path = toPath $ F.FacetsQuery { F.table = Places+ , F.search = AndSearch ["starbucks"]+ , F.select = ["locality", "region"]+ , F.filters = [EqualStr "country" "US"]+ , F.geo = Nothing+ , F.limit = Just 10+ , F.minCount = Just 2+ , F.includeCount = False }+ assertEqual "Correct path for a facets query" expected path)++submitPathTest = TestCase (do+ let expected = "/t/places/foobar/submit"+ let path = W.path submitWrite+ assertEqual "Correct path for submit" expected path)++submitBodyTest = TestCase (do+ let expected = "user=user123&values={\"key\":\"val\"}"+ let body = W.body submitWrite+ assertEqual "Correct body for submit" expected body)++flagPathTest = TestCase (do+ let expected = "/t/places/foobar/flag"+ let path = W.path flagWrite+ assertEqual "Correct path for flag" expected path)++flagBodyTest = TestCase (do+ let expected = "problem=Duplicate&user=user123&comment=There was a problem&debug=false"+ let body = W.body flagWrite+ assertEqual "Correct body for flag" expected body)++readIntegrationTest :: Token -> Test+readIntegrationTest token = TestCase (do let query = ReadQuery { table = Places , search = AndSearch ["McDonalds", "Burger King"] , select = ["name"]@@ -231,28 +297,56 @@ result <- makeRequest token query assertEqual "Valid read query" "ok" (status result)) -schemaResponseTest :: Token -> Test-schemaResponseTest token = TestCase (do+schemaIntegrationTest :: Token -> Test+schemaIntegrationTest token = TestCase (do let query = SchemaQuery Places result <- makeRequest token query- assertEqual "Valid read query" "ok" (status result))+ assertEqual "Valid schema query" "ok" (status result)) -resolveResponseTest :: Token -> Test-resolveResponseTest token = TestCase (do+resolveIntegrationTest :: Token -> Test+resolveIntegrationTest token = TestCase (do let query = ResolveQuery [ResolveStr "name" "McDonalds"] result <- makeRequest token query- assertEqual "Valid read query" "ok" (status result))+ assertEqual "Valid resolve query" "ok" (status result)) -crosswalkResponseTest :: Token -> Test-crosswalkResponseTest token = TestCase (do+crosswalkIntegrationTest :: Token -> Test+crosswalkIntegrationTest token = TestCase (do let query = C.CrosswalkQuery { C.factualId = Just "97598010-433f-4946-8fd5-4a6dd1639d77"- , C.limit = Nothing- , C.namespace = Nothing- , C.namespaceId = Nothing- , C.only = ["loopt"] }+ , C.limit = Nothing+ , C.namespace = Nothing+ , C.namespaceId = Nothing+ , C.only = ["loopt"] } result <- makeRequest token query+ assertEqual "Valid crosswalk query" "ok" (status result))++rawIntegrationTest :: Token -> Test+rawIntegrationTest token = TestCase (do+ result <- makeRawRequest token "/t/places?q=starbucks" assertEqual "Valid read query" "ok" (status result)) +facetsIntegrationTest token = TestCase (do+ let query = F.FacetsQuery { F.table = Places+ , F.search = AndSearch ["Starbucks"]+ , F.select = ["country"]+ , F.filters = []+ , F.geo = Nothing+ , F.limit = Just 100+ , F.minCount = Just 1+ , F.includeCount = False }+ result <- makeRequest token query+ assertEqual "Valid facets query" "ok" (status result))++geopulseIntegrationTest token = TestCase (do+ let query = G.GeopulseQuery { G.geo = Point 34.06021 (-118.41828)+ , G.select = [] }+ result <- makeRequest token query+ assertEqual "Valid geopulse query" "ok" (status result))++geocodeIntegrationTest token = TestCase (do+ let query = GeocodeQuery $ Point 34.06021 (-118.41828)+ result <- makeRequest token query+ assertEqual "Valid geopulse query" "ok" (status result))+ blankReadQuery :: ReadQuery blankReadQuery = ReadQuery { table = Places , search = AndSearch []@@ -269,3 +363,18 @@ , C.namespace = Nothing , C.namespaceId = Nothing , C.only = [] }++submitWrite :: S.Submit+submitWrite = S.Submit { S.table = Places+ , S.user = "user123"+ , S.factualId = Just "foobar"+ , S.values = M.fromList [("key", "val")] }++flagWrite :: L.Flag+flagWrite = L.Flag { L.table = Places+ , L.factualId = "foobar"+ , L.problem = L.Duplicate+ , L.user = "user123"+ , L.comment = Just "There was a problem"+ , L.debug = False+ , L.reference = Nothing }