factual-api 0.2.0 → 0.3.0
raw patch · 23 files changed
+215/−86 lines, 23 filesdep +HTTPdep +bytestringdep +containers
Dependencies added: HTTP, bytestring, containers
Files
- Data/Factual/Query/CrosswalkQuery.hs +5/−4
- Data/Factual/Query/FacetsQuery.hs +2/−1
- Data/Factual/Query/ReadQuery.hs +2/−1
- Data/Factual/Query/ResolveQuery.hs +3/−3
- Data/Factual/Response.hs +22/−6
- Data/Factual/Shared/Filter.hs +2/−1
- Data/Factual/Shared/Geo.hs +3/−1
- Data/Factual/Shared/Search.hs +2/−1
- Data/Factual/Shared/Table.hs +14/−5
- Data/Factual/Utils.hs +3/−2
- Network/Factual/API.hs +35/−9
- README.md +16/−13
- examples/CrosswalkExample.hs +1/−1
- examples/FacetsExample.hs +1/−1
- examples/GeocodeExample.hs +1/−1
- examples/GeopulseExample.hs +1/−1
- examples/MultiExample.hs +42/−0
- examples/RawReadExample.hs +1/−1
- examples/ReadExample.hs +1/−1
- examples/ResolveExample.hs +1/−1
- examples/SchemaExample.hs +1/−1
- factual-api.cabal +5/−2
- test/Tests.hs +51/−29
Data/Factual/Query/CrosswalkQuery.hs view
@@ -7,6 +7,7 @@ import Data.Factual.Query import Data.Factual.Utils+import Network.HTTP.Base (urlEncode) -- | A crosswalk query can be formed by specifying a factual id and -- (optionally) a list of namespaces to only include when attempting to find@@ -32,17 +33,17 @@ -- 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 (Just id) = "factual_id=" ++ urlEncode id factualIdString Nothing = "" namespaceString :: Maybe String -> String-namespaceString (Just namespace) = "namespace=" ++ namespace+namespaceString (Just namespace) = "namespace=" ++ urlEncode namespace namespaceString Nothing = "" namespaceIdString :: Maybe String -> String-namespaceIdString (Just id) = "namespace_id=" ++ id+namespaceIdString (Just id) = "namespace_id=" ++ urlEncode id namespaceIdString Nothing = "" onlyString :: [String] -> String onlyString [] = ""-onlyString strs = "only=" ++ (join "," strs)+onlyString strs = "only=" ++ urlEncode (join "," strs)
Data/Factual/Query/FacetsQuery.hs view
@@ -16,6 +16,7 @@ import Data.Factual.Shared.Filter import Data.Factual.Shared.Geo import Data.Factual.Utils+import Network.HTTP.Base (urlEncode) -- | 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@@ -45,5 +46,5 @@ -- Helper functions minCountString :: Maybe Int -> String-minCountString (Just x) = "min_count=" ++ show x+minCountString (Just x) = "min_count=" ++ (urlEncode $ show x) minCountString Nothing = ""
Data/Factual/Query/ReadQuery.hs view
@@ -16,6 +16,7 @@ import Data.Factual.Shared.Filter import Data.Factual.Shared.Geo import Data.Factual.Utils+import Network.HTTP.Base (urlEncode) -- | The ReadQuery type is used to construct read queries. A table should be -- specified, but the rest of the query options are essentially optional@@ -48,5 +49,5 @@ -- The following helper functions are used in generating query Strings. offsetString :: Maybe Int -> String-offsetString (Just x) = "offset=" ++ show x+offsetString (Just x) = "offset=" ++ (urlEncode $ show x) offsetString Nothing = ""
Data/Factual/Query/ResolveQuery.hs view
@@ -9,6 +9,7 @@ import Data.Factual.Query import Data.Factual.Utils+import Network.HTTP.Base (urlEncode) -- | 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@@ -30,6 +31,5 @@ -- 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)- ++ "}"+ toPath (ResolveQuery values) = "/places/resolve?values="+ ++ urlEncode ("{" ++ (join "," $ map show values) ++ "}")
Data/Factual/Response.hs view
@@ -26,17 +26,20 @@ -- | A response object has a status (that will be ok if the query was successful -- and error if the query failed), a version (which should always be 3.0) and -- the actual response data which is an Aeson value.-data Response = Response { status :: String- , version :: Double- , response :: Value+data Response = Response { status :: String+ , version :: Double+ , response :: Value+ , errorMessage :: Maybe String+ , errorType :: Maybe String } deriving (Eq, Show) -- | This function is used by the API module to turn the Aeson value returned by -- the API into a Response value. fromValue :: Value -> Response-fromValue value = Response { status = lookupString "status" value- , version = lookupNumber "version" value- , response = lookupValue "response" value }+fromValue value+ | respStatus == "error" = formErrorResponse value+ | otherwise = formValidResponse value+ where respStatus = lookupString "status" value -- | This function can be used to convert an Aeson Array value into a vanilla -- list.@@ -65,6 +68,19 @@ lookupValueSafe key (Object x) = L.lookup (T.pack key) x -- The following helper functions aid the lookup functions.+formErrorResponse :: Value -> Response+formErrorResponse value = Response { status = "error"+ , version = lookupNumber "version" value+ , response = Null+ , errorMessage = Just $ lookupString "message" value+ , errorType = Just $ lookupString "error_type" value }++formValidResponse :: Value -> Response+formValidResponse value = Response { status = "ok"+ , version = lookupNumber "version" value+ , response = lookupValue "response" value+ , errorMessage = Nothing+ , errorType = Nothing } extractString :: Value -> String extractString (String x) = T.unpack x
@@ -9,6 +9,7 @@ ) where import Data.Factual.Utils+import Network.HTTP.Base (urlEncode) -- | A Field is a String representation of the field name. type Field = String@@ -57,4 +58,4 @@ filtersString :: [Filter] -> String filtersString [] = ""-filtersString fs = "filters={" ++ (join "," $ map show fs) ++ "}"+filtersString fs = "filters=" ++ urlEncode ("{" ++ (join "," $ map show fs) ++ "}")
@@ -10,6 +10,8 @@ , geoString ) where +import Network.HTTP.Base (urlEncode)+ -- | A Lat is the latitude represented as a Double. type Lat = Double -- | A Long is the longitude represented as a Double.@@ -41,5 +43,5 @@ -- Helper functions geoString :: Maybe Geo -> String-geoString (Just c) = "geo=" ++ show c+geoString (Just g) = "geo=" ++ (urlEncode $ show g) geoString Nothing = ""
@@ -8,6 +8,7 @@ ) where import Data.Factual.Utils+import Network.HTTP.Base (urlEncode) -- | This type is used to construct an ANDed or ORed search in a query. data Search = AndSearch [String] | OrSearch [String] | NoSearch deriving Eq@@ -23,4 +24,4 @@ searchString :: Search -> String searchString (AndSearch []) = "" searchString (OrSearch []) = ""-searchString search = "q=" ++ show search+searchString search = "q=" ++ (urlEncode $ show search)
@@ -6,16 +6,25 @@ Table(..) ) where --- | Three Table values are currently available for the Factual API.+-- | This type defines the available tables. Use the Custom table option for+-- tables that are not listed you. data Table = Places | USRestaurants | Global+ | HealthCareProviders+ | WorldGeographies+ | ProductsCPG+ | ProductsCrosswalk | 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 ++ "/"+ show Places = "/t/places/"+ show USRestaurants = "/t/restaurants-us/"+ show Global = "/t/global/"+ show HealthCareProviders = "/t/health-care-providers-us"+ show WorldGeographies = "/t/world-geographies"+ show ProductsCPG = "/t/products-cpg"+ show ProductsCrosswalk = "/t/products-crosswalk"+ show (Custom name) = "/t/" ++ name ++ "/"
Data/Factual/Utils.hs view
@@ -10,6 +10,7 @@ ) where import Data.List (intersperse)+import Network.HTTP.Base (urlEncode) -- | The join function joins a list of lists into a list using a separator list. -- The most common use case is for joining Strings with a common separator@@ -25,10 +26,10 @@ -- The following helper functions are used in generating query Strings. selectString :: [String] -> String selectString [] = ""-selectString selects = "select=" ++ (join "," selects)+selectString selects = "select=" ++ (urlEncode $ (join "," selects)) limitString :: Maybe Int -> String-limitString (Just x) = "limit=" ++ show x+limitString (Just x) = "limit=" ++ (urlEncode $ show x) limitString Nothing = "" includeCountString :: Bool -> String
Network/Factual/API.hs view
@@ -7,13 +7,16 @@ -- * Read functions , makeRequest , makeRawRequest+ , makeMultiRequest -- * Write functions , sendWrite -- * The hoauth Token type , Token(..) ) where +import Network.HTTP.Base (urlEncode) import Data.Maybe (fromJust)+import Data.List (intersperse) import Network.OAuth.Consumer import Network.OAuth.Http.Request (Request(..), Method(..), parseURL, fromList) import Network.OAuth.Http.Response (Response(..))@@ -21,6 +24,8 @@ import Data.Aeson (Value, decode) import Data.Factual.Query import Data.Factual.Write+import Data.Factual.Utils+import qualified Data.Map as M import qualified Data.ByteString.Lazy.Char8 as B import qualified Data.Factual.Response as F @@ -44,24 +49,45 @@ -- 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- makeRequest' token request+ response <- makeRawRequest' token queryString+ return $ F.fromValue $ extractJSON response +-- | This function can be used to make multi queries. You pass in a Map of Strings+-- to queries and a single query is made to the API. The result is a Map of the+-- same keys to regular response values.+makeMultiRequest :: (Query query) => Token -> M.Map String query -> IO (M.Map String F.Response)+makeMultiRequest token mult = do+ let queryString = multiQueryString $ M.toList mult+ response <- makeRawRequest' token queryString+ return $ formMultiResponse response $ M.keys mult+ -- | 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 :: (Write write) => Token -> write -> IO 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+makeRawRequest' :: Token -> String -> IO Response+makeRawRequest' token queryString = do+ let fullpath = basePath ++ queryString+ let request = generateRequest fullpath+ makeRequest' token request +makeRequest' :: Token -> Request -> IO Response+makeRequest' token request = runOAuthM token $ setupOAuth request++multiQueryString :: (Query query) => [(String, query)] -> String+multiQueryString ps = "/multi?queries=" ++ (urlEncode $ "{" ++ (join "," $ map queryPair ps) ++ "}")+ where queryPair (n,q) = "\"" ++ n ++ "\":\"" ++ toPath q ++ "\""++formMultiResponse :: Response -> [String] -> M.Map String F.Response+formMultiResponse res ks = M.fromList $ map formPair ks+ where formPair k = (k, F.fromValue $ F.lookupValue k json)+ json = extractJSON res+ generateRequest :: String -> Request generateRequest url = (fromJust $ parseURL url) { reqHeaders = (fromList headersList) } @@ -80,7 +106,7 @@ extractJSON = fromJust . decode . rspPayload headersList :: [(String, String)]-headersList = [("X-Factual-Lib", "factual-haskell-driver-0.2.0")]+headersList = [("X-Factual-Lib", "factual-haskell-driver-0.3.0")] basePath :: String basePath = "http://api.v3.factual.com"
README.md view
@@ -6,25 +6,21 @@ ## Prerequisites -This driver was developed on [ghc 7.0.4](http://www.haskell.org/ghc/)-and [The Haskell Platform 2011.4.0.0](http://hackage.haskell.org/platform/).+This driver targets [ghc 7.4.1](http://www.haskell.org/ghc/)+and [The Haskell Platform 2012.2.0.0](http://hackage.haskell.org/platform/). Please follow the installation instructions for your specific platform to install ghc and The Haskell Platform. +## Installation from cabal++ $ cabal install factual-api+ ## Installation from git $ git clone git@github.com:rudyl313/factual-haskell-driver.git $ cabal install hoauth $ cabal install aeson -Note you may require libcurl to install hoauth. On lucid you can run:-- $ sudo apt-get install libcurl4-openssl-dev--## Installation from cabal-- $ cabal install factual-api- # Tests Load the tests file into ghci to run the tests:@@ -38,13 +34,13 @@ 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}+ Cases: 10 Tried: 10 Errors: 0 Failures: 0+ Counts {cases = 10, tried = 10, errors = 0, failures = 0} # Documentation You can read library documentation by opening docs/index.html in-your browser.+your browser or by visiting the [Hackage page](http://hackage.haskell.org/package/factual-api). # Examples @@ -57,3 +53,10 @@ In this example replace mykey with your key and mysecret with your secret. Note that compiling the source code generates .o and .hi files in the source directories.++# Writes++Although there is code in the current version to support API writes,+these features have not yet been implemented in the Factual API. Please+refrain from attempting to use these features until they have been+officially announced.
examples/CrosswalkExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.CrosswalkQuery import Data.Factual.Response
examples/FacetsExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.FacetsQuery import Data.Factual.Response
examples/GeocodeExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.GeocodeQuery import Data.Factual.Response
examples/GeopulseExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.GeopulseQuery import Data.Factual.Response
+ examples/MultiExample.hs view
@@ -0,0 +1,42 @@+module Main where++import System.Environment (getArgs)+import Network.Factual.API+import Data.Factual.Query.ReadQuery+import Data.Factual.Response+import qualified Data.Map as M++main :: IO()+main = do+ args <- getArgs+ let oauthKey = head args+ let oauthSecret = last args+ let token = generateToken oauthKey oauthSecret+ let query1 = ReadQuery { table = Places+ , search = AndSearch []+ , select = ["name"]+ , limit = Just 50+ , offset = Nothing+ , includeCount = True+ , geo = Just (Circle 34.06021 (-118.41828) 5000.0)+ , filters = [EqualStr "name" "Stand"] }+ let query2 = ReadQuery { table = Places+ , search = AndSearch []+ , select = ["name"]+ , limit = Just 50+ , offset = Nothing+ , includeCount = True+ , geo = Just (Circle 34.06021 (-118.41828) 5000.0)+ , filters = [EqualStr "name" "Xerox"] }+ let multiQuery = M.fromList [("query1", query1), ("query2", query2)]+ multiResult <- makeMultiRequest token multiQuery+ let result1 = multiResult M.! "query1"+ let result2 = multiResult M.! "query2"+ putStrLn "query1:"+ putStrLn $ "Status: " ++ status result1+ putStrLn $ "Version: " ++ show (version result1)+ putStrLn $ "Data: " ++ show (response result1)+ putStrLn "query2:"+ putStrLn $ "Status: " ++ status result2+ putStrLn $ "Version: " ++ show (version result2)+ putStrLn $ "Data: " ++ show (response result2)
examples/RawReadExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Response
examples/ReadExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.ReadQuery import Data.Factual.Response
examples/ResolveExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.ResolveQuery import Data.Factual.Response
examples/SchemaExample.hs view
@@ -1,6 +1,6 @@ module Main where -import System (getArgs)+import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Query.SchemaQuery import Data.Factual.Response
factual-api.cabal view
@@ -1,5 +1,5 @@ name: factual-api-version: 0.2.0+version: 0.3.0 license: BSD3 license-file: LICENSE.txt category: API, Web@@ -7,7 +7,7 @@ author: Rudiger Lippert <rudy@factual.com> maintainer: Rudiger Lippert <rudy@factual.com> stability: stable-tested-with: GHC == 7.0.4+tested-with: GHC == 7.4.1 synopsis: A driver for the Factual API cabal-version: >= 1.8 homepage: https://github.com/rudyl313/factual-haskell-driver@@ -50,7 +50,10 @@ aeson >= 0.6.0.0, attoparsec >= 0.10.1.0, base == 4.*,+ bytestring,+ containers, hoauth >= 0.3.0,+ HTTP, text >= 0.11.1.5, unordered-containers >= 0.1.4.6, vector >= 0.9.1
test/Tests.hs view
@@ -44,7 +44,7 @@ , TestLabel "Is blank filter test" isBlankFilterTest , TestLabel "Is not blank filter test" isNotBlankFilterTest , TestLabel "And filter test" andFilterTest- , TestLabel "Or filter test" andFilterTest+ , TestLabel "Or filter test" orFilterTest , TestLabel "Geo test" geoTest , TestLabel "Include count test" includeCountTest , TestLabel "Schema query test" schemaQueryTest@@ -67,7 +67,9 @@ , TestLabel "Raw read test" (rawIntegrationTest token) , TestLabel "Facets test" (facetsIntegrationTest token) , TestLabel "Geopulse test" (geopulseIntegrationTest token)- , TestLabel "Geocode test" (geocodeIntegrationTest token) ]+ , TestLabel "Geocode test" (geocodeIntegrationTest token)+ , TestLabel "Multi test" (multiIntegrationTest token)+ , TestLabel "Error test" (errorIntegrationTest token) ] where token = generateToken key secret placeTablePathTest = TestCase (do@@ -91,27 +93,27 @@ 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 expected = "/places/geopulse?geo=%7B%22%24point%22%3A%5B34.06021%2C-118.41828%5D%7D&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 expected = "/places/geocode?geo=%7B%22%24point%22%3A%5B34.06021%2C-118.41828%5D%7D" 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 expected = "/t/places/read?q=foo%20bar&include_count=false" let path = toPath $ blankReadQuery { search = AndSearch ["foo", "bar"] } assertEqual "Correct path for ANDed search" expected path) orSearchPathTest = TestCase (do- let expected = "/t/places/read?q=foo,bar&include_count=false"+ let expected = "/t/places/read?q=foo%2Cbar&include_count=false" let path = toPath $ blankReadQuery { search = OrSearch ["foo", "bar"] } assertEqual "Correct path for ANDed search" expected path) selectPathTest = TestCase (do- let expected = "/t/places/read?select=foo,bar&include_count=false"+ let expected = "/t/places/read?select=foo%2Cbar&include_count=false" let path = toPath $ blankReadQuery { select = ["foo", "bar"] } assertEqual "Correct path for select terms" expected path) @@ -126,87 +128,87 @@ assertEqual "Correct path for offset" expected path) equalNumFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":123.4}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A123.4%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [EqualNum "field" 123.4] } assertEqual "Correct path for equal number filter" expected path) equalStrFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":\"value\"}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%22value%22%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [EqualStr "field" "value"] } assertEqual "Correct path for equal string filter" expected path) notEqualNumFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$neq\":123.4}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24neq%22%3A123.4%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [NotEqualNum "field" 123.4] } assertEqual "Correct path for not equal number filter" expected path) notEqualStrFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$neq\":\"value\"}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24neq%22%3A%22value%22%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [NotEqualStr "field" "value"] } assertEqual "Correct path for not equal string filter" expected path) inNumListFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$in\":[123.4,5432.1]}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24in%22%3A%5B123.4%2C5432.1%5D%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [InNumList "field" [123.4, 5432.1]] } assertEqual "Correct path for in number list filter" expected path) inStrListFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$in\":[\"value\",\"other\"]}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24in%22%3A%5B%22value%22%2C%22other%22%5D%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [InStrList "field" ["value","other"]] } assertEqual "Correct path for in string list filter" expected path) notInNumListFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$nin\":[123.4,5432.1]}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24nin%22%3A%5B123.4%2C5432.1%5D%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [NotInNumList "field" [123.4, 5432.1]] } assertEqual "Correct path for not in number list filter" expected path) notInStrListFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$nin\":[\"value\",\"other\"]}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24nin%22%3A%5B%22value%22%2C%22other%22%5D%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [NotInStrList "field" ["value","other"]] } assertEqual "Correct path for not in string list filter" expected path) beginsWithFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$bw\":\"val\"}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24bw%22%3A%22val%22%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [BeginsWith "field" "val"] } assertEqual "Correct path for begins with filter" expected path) notBeginsWithFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$nbw\":\"val\"}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24nbw%22%3A%22val%22%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [NotBeginsWith "field" "val"] } assertEqual "Correct path for not begins with filter" expected path) beginsWithAnyFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$bwin\":[\"val\",\"ot\"]}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24bwin%22%3A%5B%22val%22%2C%22ot%22%5D%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [BeginsWithAny "field" ["val","ot"]] } assertEqual "Correct path for begins with any filter" expected path) notBeginsWithAnyFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$nbwin\":[\"val\",\"ot\"]}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24nbwin%22%3A%5B%22val%22%2C%22ot%22%5D%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [NotBeginsWithAny "field" ["val","ot"]] } assertEqual "Correct path for not begins with any filter" expected path) isBlankFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$blank\":true}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24blank%22%3Atrue%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [IsBlank "field"] } assertEqual "Correct path for is blank filter" expected path) isNotBlankFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"field\":{\"$blank\":false}}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22field%22%3A%7B%22%24blank%22%3Afalse%7D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [IsNotBlank "field"] } assertEqual "Correct path for is not blank filter" expected path) andFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"$and\":[{\"field1\":{\"$blank\":true}},{\"field2\":{\"$blank\":false}}]}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22%24and%22%3A%5B%7B%22field1%22%3A%7B%22%24blank%22%3Atrue%7D%7D%2C%7B%22field2%22%3A%7B%22%24blank%22%3Afalse%7D%7D%5D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [And [IsBlank "field1", IsNotBlank "field2"]] } assertEqual "Correct path for and filter" expected path) orFilterTest = TestCase (do- let expected = "/t/places/read?filters={\"$or\":[{\"field1\":{\"$blank\":true}},{\"field2\":{\"$blank\":false}}]}&include_count=false"+ let expected = "/t/places/read?filters=%7B%22%24or%22%3A%5B%7B%22field1%22%3A%7B%22%24blank%22%3Atrue%7D%7D%2C%7B%22field2%22%3A%7B%22%24blank%22%3Afalse%7D%7D%5D%7D&include_count=false" let path = toPath $ blankReadQuery { filters = [Or [IsBlank "field1", IsNotBlank "field2"]] } assertEqual "Correct path for or filter" expected path) geoTest = TestCase (do- let expected = "/t/places/read?geo={\"$circle\":{\"$center\":[300.1, 200.3],\"$meters\":100.5}}&include_count=false"+ let expected = "/t/places/read?geo=%7B%22%24circle%22%3A%7B%22%24center%22%3A%5B300.1%2C%20200.3%5D%2C%22%24meters%22%3A100.5%7D%7D&include_count=false" let path = toPath $ blankReadQuery { geo = Just (Circle 300.1 200.3 100.5) } assertEqual "Correct path for geo" expected path) @@ -221,7 +223,7 @@ assertEqual "Correct path for a schema query" expected path) resolveQueryTest = TestCase (do- let expected = "/places/resolve?values={\"field1\":\"value1\",\"field2\":32.1}"+ let expected = "/places/resolve?values=%7B%22field1%22%3A%22value1%22%2C%22field2%22%3A32.1%7D" let path = toPath $ ResolveQuery [ResolveStr "field1" "value1", ResolveNum "field2" 32.1] assertEqual "Correct path for a resolve query" expected path) @@ -246,14 +248,12 @@ assertEqual "Correct path for a namespace id" expected path) onlyTest = TestCase (do- let expected = "/places/crosswalk?only=yelp,loopd"+ let expected = "/places/crosswalk?only=yelp%2Cloopd" let path = toPath $ blankCrosswalkQuery { C.only = ["yelp", "loopd"] } assertEqual "Correct path for a only" expected path) 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 expected = "/t/places/facets?q=starbucks&select=locality%2Cregion&filters=%7B%22country%22%3A%22US%22%7D&limit=10&min_count=2&include_count=false" let path = toPath $ F.FacetsQuery { F.table = Places , F.search = AndSearch ["starbucks"] , F.select = ["locality", "region"]@@ -346,6 +346,28 @@ let query = GeocodeQuery $ Point 34.06021 (-118.41828) result <- makeRequest token query assertEqual "Valid geopulse query" "ok" (status result))+++multiIntegrationTest :: Token -> Test+multiIntegrationTest token = TestCase (do+ let query1 = ReadQuery { table = Places+ , search = AndSearch ["McDonalds", "Burger King"]+ , select = ["name"]+ , limit = Just 50+ , offset = Just 10+ , includeCount = True+ , geo = Just (Circle 34.06021 (-118.41828) 5000.0)+ , filters = [EqualStr "name" "Stand"] }+ let query2 = query1 { filters = [EqualStr "name" "Xerox"] }+ results <- makeMultiRequest token $ M.fromList [("query1", query1), ("query2", query2)]+ let result1 = results M.! "query1"+ let result2 = results M.! "query2"+ assertEqual "Valid multi query" ["ok","ok"] [status result1, status result2])++errorIntegrationTest :: Token -> Test+errorIntegrationTest token = TestCase (do+ result <- makeRawRequest token "/t/foobarbaz"+ assertEqual "Invalud read query" "error" (status result)) blankReadQuery :: ReadQuery blankReadQuery = ReadQuery { table = Places