packages feed

factual-api 0.4.0 → 0.5.0

raw patch · 32 files changed

+665/−330 lines, 32 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Factual.Query: toPath :: Query q => q -> String
- Data.Factual.Shared.Filter: filtersString :: [Filter] -> String
- Data.Factual.Shared.Geo: geoString :: Maybe Geo -> String
- Data.Factual.Shared.Search: searchString :: Search -> String
- Network.Factual.API: makeMultiRequest :: Query query => Token -> Map String query -> IO (Map String Response)
- Network.Factual.API: makeRawRequest :: Token -> String -> IO Response
- Network.Factual.API: makeRequest :: Query query => Token -> query -> IO Response
- Network.Factual.API: sendWrite :: Write write => Token -> write -> IO Response
+ Data.Factual.Query: params :: Query q => q -> Map String String
+ Data.Factual.Query: path :: Query q => q -> String
+ Data.Factual.Query.DiffsQuery: DiffsQuery :: Table -> Timestamp -> Timestamp -> DiffsQuery
+ Data.Factual.Query.DiffsQuery: data DiffsQuery
+ Data.Factual.Query.DiffsQuery: end :: DiffsQuery -> Timestamp
+ Data.Factual.Query.DiffsQuery: instance Eq DiffsQuery
+ Data.Factual.Query.DiffsQuery: instance Query DiffsQuery
+ Data.Factual.Query.DiffsQuery: instance Show DiffsQuery
+ Data.Factual.Query.DiffsQuery: start :: DiffsQuery -> Timestamp
+ Data.Factual.Query.DiffsQuery: table :: DiffsQuery -> Table
+ Data.Factual.Query.MatchQuery: MatchNum :: String -> Double -> MatchValue
+ Data.Factual.Query.MatchQuery: MatchQuery :: [MatchValue] -> MatchQuery
+ Data.Factual.Query.MatchQuery: MatchStr :: String -> String -> MatchValue
+ Data.Factual.Query.MatchQuery: data MatchQuery
+ Data.Factual.Query.MatchQuery: data MatchValue
+ Data.Factual.Query.MatchQuery: instance Eq MatchQuery
+ Data.Factual.Query.MatchQuery: instance Eq MatchValue
+ Data.Factual.Query.MatchQuery: instance Query MatchQuery
+ Data.Factual.Query.MatchQuery: instance Show MatchValue
+ Data.Factual.Query.ReadQuery: sort :: ReadQuery -> [SortOrder]
+ Data.Factual.Query.ResolveQuery: debug :: ResolveQuery -> Bool
+ Data.Factual.Query.ResolveQuery: values :: ResolveQuery -> [ResolveValue]
+ Data.Factual.Shared.Filter: GreaterThan :: Field -> Double -> Filter
+ Data.Factual.Shared.Filter: GreaterThanOrEqualTo :: Field -> Double -> Filter
+ Data.Factual.Shared.Filter: LessThan :: Field -> Double -> Filter
+ Data.Factual.Shared.Filter: LessThanOrEqualTo :: Field -> Double -> Filter
+ Data.Factual.Shared.Filter: SearchFilter :: Field -> String -> Filter
+ Data.Factual.Shared.Filter: filtersPair :: [Filter] -> (String, String)
+ Data.Factual.Shared.Geo: geoPair :: Maybe Geo -> (String, String)
+ Data.Factual.Shared.Search: searchPair :: Search -> (String, String)
+ Data.Factual.Shared.SortOrder: Asc :: String -> SortOrder
+ Data.Factual.Shared.SortOrder: Desc :: String -> SortOrder
+ Data.Factual.Shared.SortOrder: data SortOrder
+ Data.Factual.Shared.SortOrder: instance Eq SortOrder
+ Data.Factual.Shared.SortOrder: instance Show SortOrder
+ Data.Factual.Shared.SortOrder: sortPair :: [SortOrder] -> (String, String)
+ Data.Factual.Write: params :: Write w => w -> Map String String
+ Network.Factual.API: executeMultiQuery :: Query query => Token -> Map String query -> IO (Map String Response)
+ Network.Factual.API: executeQuery :: Query query => Token -> query -> IO Response
+ Network.Factual.API: executeWrite :: Write write => Token -> write -> IO Response
+ Network.Factual.API: get :: Token -> Path -> Params -> IO Response
+ Network.Factual.API: post :: Token -> Path -> Params -> Body -> IO Response
- Data.Factual.Query.ReadQuery: ReadQuery :: Table -> Search -> [String] -> Maybe Int -> Maybe Int -> [Filter] -> Maybe Geo -> Bool -> ReadQuery
+ Data.Factual.Query.ReadQuery: ReadQuery :: Table -> Search -> [String] -> Maybe Int -> Maybe Int -> [Filter] -> Maybe Geo -> [SortOrder] -> Bool -> ReadQuery
- Data.Factual.Query.ResolveQuery: ResolveQuery :: [ResolveValue] -> ResolveQuery
+ Data.Factual.Query.ResolveQuery: ResolveQuery :: [ResolveValue] -> Bool -> ResolveQuery
- Data.Factual.Write: body :: Write w => w -> String
+ Data.Factual.Write: body :: Write w => w -> Map String String

Files

Data/Factual/Query.hs view
@@ -4,7 +4,11 @@   -- * Query typeclass   Query(..)) where --- | A member of the Query typeclass must define a toPath method which converts---   the Query into a path String.+import qualified Data.Map as M++-- | A member of the Query typeclass must define a path function which outputs+--   the Query endpoint path, and a params function that outputs a Map of query+--   params keys and values. class Query q where-  toPath :: q -> String+  path   :: q -> String+  params :: q -> M.Map String String
+ Data/Factual/Query/DiffsQuery.hs view
@@ -0,0 +1,29 @@+-- | This module exports the types used to create diffs queries.+module Data.Factual.Query.DiffsQuery+ (+   -- * DiffsQuery type+   DiffsQuery(..)+   -- * Required modules+ , module Data.Factual.Shared.Table+ ) where++import Data.Factual.Query+import Data.Factual.Shared.Table+import qualified Data.Map as M++-- | The timestamps are unix epoch integers+type Timestamp = Integer++-- | The DiffsQuery type is used to construct diffs queries. A table, start+--   timestamp and end timestamp should be specified.+data DiffsQuery = DiffsQuery { table :: Table+                             , start :: Timestamp+                             , end   :: Timestamp+                             } deriving (Eq, Show)++-- The DiffsQuery type is a member of the Query typeclass so it can be used to+-- make a request.+instance Query DiffsQuery where+  path   query = (show $ table query) ++ "/diffs"+  params query = M.fromList [ ("start", show $ start query)+                            , ("end", show $ end query) ]
Data/Factual/Query/FacetsQuery.hs view
@@ -16,7 +16,7 @@ import Data.Factual.Shared.Filter import Data.Factual.Shared.Geo import Data.Factual.Utils-import Network.HTTP.Base (urlEncode)+import qualified Data.Map as M  -- | 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@@ -34,17 +34,16 @@ -- 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 ]+  path query   = (show $ table query) ++ "/facets"+  params query = M.fromList [ searchPair $ search query+                            , selectPair $ select query+                            , filtersPair $ filters query+                            , geoPair $ geo query+                            , limitPair $ limit query+                            , minCountPair $ minCount query+                            , includeCountPair $ includeCount query ]  -- Helper functions-minCountString :: Maybe Int -> String-minCountString (Just x) = "min_count=" ++ (urlEncode $ show x)-minCountString Nothing  = ""+minCountPair :: Maybe Int -> (String, String)+minCountPair (Just x) = ("min_count", show x)+minCountPair Nothing  = ("min_count", "")
Data/Factual/Query/GeocodeQuery.hs view
@@ -9,6 +9,7 @@  import Data.Factual.Query import Data.Factual.Shared.Geo+import qualified Data.Map as M  -- | The GeocodeQuery type is used to construct geocode queries. A geo point --   is required.@@ -17,4 +18,5 @@ -- 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)+  path   _                  = "/places/geocode"+  params (GeocodeQuery geo) = M.fromList [(geoPair $ Just geo)]
Data/Factual/Query/GeopulseQuery.hs view
@@ -10,6 +10,7 @@ import Data.Factual.Query import Data.Factual.Utils import Data.Factual.Shared.Geo+import qualified Data.Map as M  -- | 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@@ -21,6 +22,6 @@ -- 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 ]+  path   _     = "/places/geopulse"+  params query = M.fromList [ geoPair $ Just $ geo query+                            , selectPair $ select query ]
+ Data/Factual/Query/MatchQuery.hs view
@@ -0,0 +1,36 @@+-- | This module exports the type used to create match queries.+module Data.Factual.Query.MatchQuery+  (+    -- * MatchQuery type+    MatchQuery(..)+    -- * MatchValue type+  , MatchValue(..)+  ) where++import Data.Factual.Query+import Data.Factual.Utils+import qualified Data.Map as M++-- | A match 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 MatchValue = MatchStr String String+                | MatchNum String Double+                deriving Eq++-- MatchValue is a member of the Show typeclass to help generate query Strings.+instance Show MatchValue where+  show (MatchStr name str) = (show name) ++ ":" ++ (show str)+  show (MatchNum name num) = (show name) ++ ":" ++ (show num)++-- | A match query is formed as an array of match values. These values will+--   be compared with Factual records to return a cleaner, more canonical row+--   of data.+data MatchQuery = MatchQuery [MatchValue] deriving Eq++-- MatchQuery is a member of the Query typeclass so that it can be used to+-- make requests.+instance Query MatchQuery where+  path   _                   = "/places/match"+  params (MatchQuery values) = M.fromList [("values", valuesString)]+    where valuesString = "{" ++ (join "," $ map show values) ++ "}"
Data/Factual/Query/ReadQuery.hs view
@@ -6,6 +6,7 @@     -- * Required modules   , module Data.Factual.Shared.Table   , module Data.Factual.Shared.Search+  , module Data.Factual.Shared.SortOrder   , module Data.Factual.Shared.Filter   , module Data.Factual.Shared.Geo   ) where@@ -13,10 +14,11 @@ import Data.Factual.Query import Data.Factual.Shared.Table import Data.Factual.Shared.Search+import Data.Factual.Shared.SortOrder import Data.Factual.Shared.Filter import Data.Factual.Shared.Geo import Data.Factual.Utils-import Network.HTTP.Base (urlEncode)+import qualified Data.Map as M  -- | The ReadQuery type is used to construct read queries. A table should be --   specified, but the rest of the query options are essentially optional@@ -31,23 +33,25 @@                            , offset       :: Maybe Int                            , filters      :: [Filter]                            , geo          :: Maybe Geo+                           , sort         :: [SortOrder]                            , 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)-               ++ "?"-               ++ joinAndFilter [ searchString $ search query-                                , selectString $ select query-                                , limitString $ limit query-                                , offsetString $ offset query-                                , filtersString $ filters query-                                , geoString $ geo query-                                , includeCountString $ includeCount query ]+  path query   = show $ table query+  params query = M.fromList [ searchPair       $ search query+                            , selectPair       $ select query+                            , limitPair        $ limit query+                            , offsetPair       $ offset query+                            , filtersPair      $ filters query+                            , geoPair          $ geo query+                            , sortPair         $ sort query+                            , includeCountPair $ includeCount query ]  -- The following helper functions are used in generating query Strings.-offsetString :: Maybe Int -> String-offsetString (Just x) = "offset=" ++ (urlEncode $ show x)-offsetString Nothing = ""+offsetPair :: Maybe Int -> (String, String)+offsetPair (Just x) = ("offset", show x)+offsetPair Nothing  = ("offset", "")
Data/Factual/Query/ResolveQuery.hs view
@@ -9,7 +9,7 @@  import Data.Factual.Query import Data.Factual.Utils-import Network.HTTP.Base (urlEncode)+import qualified Data.Map as M  -- | 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@@ -26,10 +26,14 @@ -- | 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+data ResolveQuery = ResolveQuery { values :: [ResolveValue]+                                 , debug  :: Bool+                                 } 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="-                               ++ urlEncode ("{" ++ (join "," $ map show values) ++ "}")+  path   _     = "/places/resolve"+  params query = M.fromList [("values", valuesString), ("debug", debugString)]+    where valuesString = "{" ++ (join "," $ map show $ values query) ++ "}"+          debugString  = if (debug query) then "true" else "false"
Data/Factual/Query/SchemaQuery.hs view
@@ -9,6 +9,7 @@  import Data.Factual.Query import Data.Factual.Shared.Table+import qualified Data.Map as M  -- | A schema query is formed by simply supplying a Table to the value --   constructor.@@ -16,4 +17,5 @@  -- SchemaQuery is a member of Query typeclass so that it can generate a response. instance Query SchemaQuery where-  toPath (SchemaQuery table) = (show $ table) ++ "/schema"+  path   (SchemaQuery table) = (show $ table) ++ "/schema"+  params _                   = M.empty
Data/Factual/Response.hs view
@@ -22,6 +22,7 @@ import qualified Data.HashMap.Lazy as L import qualified Data.Text as T import qualified Data.Vector as V+import Debug.Trace  -- | 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@@ -37,8 +38,8 @@ --   the API into a Response value. fromValue :: Value -> Response fromValue value-  | respStatus == "error" = formErrorResponse value-  | otherwise             = formValidResponse value+  | respStatus == "ok" = formValidResponse value+  | otherwise          = formErrorResponse value   where respStatus = lookupString "status" value  -- | This function can be used to convert an Aeson Array value into a vanilla@@ -69,7 +70,7 @@  -- The following helper functions aid the lookup functions. formErrorResponse :: Value -> Response-formErrorResponse value = Response { status = "error"+formErrorResponse value = Response { status = lookupString "status" value                                    , version = lookupNumber "version" value                                    , response = Null                                    , errorMessage = Just $ lookupString "message" value@@ -81,6 +82,7 @@                                    , response = lookupValue "response" value                                    , errorMessage = Nothing                                    , errorType = Nothing }+ extractString :: Value -> String extractString (String x) = T.unpack x 
Data/Factual/Shared/Filter.hs view
@@ -5,11 +5,10 @@     Field   , Filter(..)     -- * Helper functions-  , filtersString+  , filtersPair   ) where  import Data.Factual.Utils-import Network.HTTP.Base (urlEncode)  -- | A Field is a String representation of the field name. type Field = String@@ -29,6 +28,11 @@             | 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.+            | GreaterThan Field Double -- ^ A field must be greater than the given value.+            | GreaterThanOrEqualTo Field Double -- ^ A field must be greater than or equal to the given value.+            | LessThan Field Double -- ^ A field must be less than the given value.+            | LessThanOrEqualTo Field Double -- ^ A field must be less than or equal to the given value.+            | SearchFilter Field String -- ^ A field must match of full text search with the given string.             | 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@@ -49,13 +53,18 @@   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 (GreaterThan field num) = (show field) ++ ":{" ++ (show "$gt") ++ ":" ++ (show num) ++ "}"+  show (GreaterThanOrEqualTo field num) = (show field) ++ ":{" ++ (show "$gte") ++ ":" ++ (show num) ++ "}"+  show (LessThan field num) = (show field) ++ ":{" ++ (show "$lt") ++ ":" ++ (show num) ++ "}"+  show (LessThanOrEqualTo field num) = (show field) ++ ":{" ++ (show "$lte") ++ ":" ++ (show num) ++ "}"+  show (SearchFilter field str) = (show field) ++ ":{" ++ (show "$search") ++ ":" ++ (show str) ++ "}"   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.+-- The following helper functions are used in generating query params. showFilter :: Filter -> String showFilter filter = "{" ++ (show filter) ++ "}" -filtersString :: [Filter] -> String-filtersString [] = ""-filtersString fs = "filters=" ++ urlEncode ("{" ++ (join "," $ map show fs) ++ "}")+filtersPair :: [Filter] -> (String, String)+filtersPair [] = ("filters", "")+filtersPair fs = ("filters", "{" ++ (join "," $ map show fs) ++ "}")
Data/Factual/Shared/Geo.hs view
@@ -7,11 +7,9 @@   , Radius   , Geo(..)     -- * Helper functions-  , geoString+  , geoPair   ) 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.@@ -26,7 +24,7 @@          | Point Lat Long          deriving Eq --- Geo is a member of Show to help generate query strings.+-- Geo is a member of Show to help generate query params. instance Show Geo where   show (Circle lat long radius) = "{\"$circle\":{\"$center\":["                                  ++ (show lat) @@ -42,6 +40,6 @@                         ++ "]}"  -- Helper functions-geoString :: Maybe Geo -> String-geoString (Just g) = "geo=" ++ (urlEncode $ show g)-geoString Nothing = ""+geoPair :: Maybe Geo -> (String, String)+geoPair (Just g) = ("geo", show g)+geoPair Nothing  = ("geo", "")
Data/Factual/Shared/Search.hs view
@@ -4,24 +4,21 @@     -- * Search type     Search(..)     -- * Helper functions-  , searchString+  , searchPair   ) 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  -- When a Search is shown it displays the string representation that will go--- into the query string.+-- into the query params. 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=" ++ (urlEncode $ show search)+searchPair :: Search -> (String, String)+searchPair search = ("q", show search)
+ Data/Factual/Shared/SortOrder.hs view
@@ -0,0 +1,23 @@+-- | This module exports the SortOrder type used to create read and facet queries.+module Data.Factual.Shared.SortOrder+  (+    -- * SortOrder type+    SortOrder(..)+    -- * Helper functions+  , sortPair+  ) where++import Data.Factual.Utils++-- | The SortOrder type is used to represent sorting parameters+data SortOrder = Asc String | Desc String deriving Eq++-- SortOrder is a member of Show to help generate query strings.+instance Show SortOrder where+  show (Asc field)  = field ++ ":asc"+  show (Desc field) = field ++ ":desc"++-- The following helper function is used in generating query params.+sortPair :: [SortOrder] -> (String, String)+sortPair []    = ("sort", "")+sortPair sorts = ("sort", join "," $ map show sorts)
Data/Factual/Utils.hs view
@@ -3,14 +3,13 @@   (     -- * Utility methods     join-  , joinAndFilter-  , selectString-  , limitString-  , includeCountString+  , selectPair+  , limitPair+  , includeCountPair   ) where  import Data.List (intersperse)-import Network.HTTP.Base (urlEncode)+import qualified Data.Map as M  -- | 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@@ -18,20 +17,14 @@ join :: [a] -> [[a]] -> [a] join delim xs = concat (intersperse delim xs) --- | This function filters out empty Strings from a list before joining the---   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=" ++ (urlEncode $ (join "," selects))+-- The following helper functions are used in generating query params Maps.+selectPair :: [String] -> (String, String)+selectPair selects = ("select", join "," selects) -limitString :: Maybe Int -> String-limitString (Just x) = "limit=" ++ (urlEncode $ show x)-limitString Nothing = ""+limitPair :: Maybe Int -> (String, String)+limitPair (Just x) = ("limit", show x)+limitPair Nothing  = ("limit", "") -includeCountString :: Bool -> String-includeCountString True = "include_count=true"-includeCountString False = "include_count=false"+includeCountPair :: Bool -> (String, String)+includeCountPair True  = ("include_count", "true")+includeCountPair False = ("include_count", "false")
Data/Factual/Write.hs view
@@ -1,12 +1,16 @@ -- | This module exports the definition of the Write typeclass. module Data.Factual.Write   (-  -- * Query typeclass+  -- * Write 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.+import qualified Data.Map as M++-- | A member of the Write typeclass must define a path function which returns+--   the write path as a String, a params function that outputs any addition path+--   params as a Map, and a body function which returns Map of the data passed in+--   the body of the post request. class Write w where-  path :: w -> String-  body :: w -> String+  path   :: w -> String+  params :: w -> M.Map String String+  body   :: w -> M.Map String String
Data/Factual/Write/Flag.hs view
@@ -13,6 +13,7 @@ import Data.Factual.Shared.Table import Data.Maybe (fromJust) import Data.Factual.Utils+import qualified Data.Map as M  -- | A Problem represents what is wrong with the row being flagged data Problem = Duplicate@@ -41,24 +42,25 @@ -- 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 ]+  params _  = M.empty+  body flag = M.fromList [ ("problem", show $ problem flag)+                         , ("user", user flag)+                         , commentPair flag+                         , debugPair flag+                         , referencePair flag ]  -- The following functions are helpers for the body function-commentString :: Flag -> String-commentString flag-  | comment flag == Nothing = ""-  | otherwise               = "comment=" ++ (fromJust $ comment flag)+commentPair :: Flag -> (String, String)+commentPair flag+  | comment flag == Nothing = ("comment", "")+  | otherwise               = ("comment", fromJust $ comment flag) -debugString :: Flag -> String-debugString flag-  | debug flag == True = "debug=true"-  | otherwise          = "debug=false"+debugPair :: Flag -> (String, String)+debugPair flag+  | debug flag == True = ("debug", "true")+  | otherwise          = ("debug", "false") -referenceString :: Flag -> String-referenceString flag-  | reference flag == Nothing = ""-  | otherwise                 = "reference=" ++ (fromJust $ reference flag)+referencePair :: Flag -> (String, String)+referencePair flag+  | reference flag == Nothing = ("reference", "")+  | otherwise                 = ("reference", fromJust $ reference flag)
Data/Factual/Write/Submit.hs view
@@ -26,9 +26,10 @@ -- 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)+  path   submit = pathString submit+  params _      = M.empty+  body   submit = M.fromList [ ("user", user submit) +                             , ("values", valuesString (values submit)) ]  -- The following functions are helpers for the Write typeclass functions. pathString :: Submit -> String
Network/Factual/API.hs view
@@ -1,15 +1,16 @@--- | This module exports functions which are used to execute queries and handle+-- | This module exports functions which are used to execute requests and handle --   the OAuth authentication process. module Network.Factual.API   (     -- * Authentication     generateToken     -- * Read functions-  , makeRequest-  , makeRawRequest-  , makeMultiRequest+  , executeQuery+  , executeMultiQuery+  , get     -- * Write functions-  , sendWrite+  , executeWrite+  , post     -- * Debug functions   , debugQuery   , debugWrite@@ -26,78 +27,111 @@ import Network.OAuth.Http.CurlHttpClient (CurlClient(..)) import Data.Aeson (Value, decode) import Data.Factual.Query-import Data.Factual.Write import Data.Factual.Utils+import qualified Data.Factual.Write as W import qualified Data.Map as M 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.+-- | Key and Secret are both Strings used to create a Token+type Key    = String type Secret = String+-- | Path, Params and Body are types passed to the get and post functions+type Path   = String+type Params = M.Map String String+type Body   = M.Map String String --- | This function takes a set of credentials and returns an OAuth token that---   can be used to make requests.+-- | This function takes a Key and Secret, and generates a Token that is passed+--   to the various methods 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---   the Factual API.-makeRequest :: (Query query) => Token -> query -> IO F.Response-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-  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 a Token and Query value and sends the query to the+--   Factual API. The resultant IO action contains a Response value which wraps+--   the resultant data.+executeQuery :: (Query query) => Token -> query -> IO F.Response+executeQuery token query = get token (path query) (params query) --- | 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 Response-sendWrite token write = do-  let fullpath = basePath ++ path write-  let request = generatePostRequest fullpath (body write)-  makeRequest' token request+-- | This function can be used to make a Multi Query (multiple queries in a single+--   request. It takes a Token, a Map of key Strings to Queries and returns a Map+--   from the same keys to Response values.+executeMultiQuery :: (Query query) => Token -> M.Map String query -> IO (M.Map String F.Response)+executeMultiQuery token multiMap = do+  let queryString = formMultiQueryString $ M.toList multiMap+  response <- get'' token queryString+  return $ formMultiResponse response $ M.keys multiMap --- | This function takes a query and prints out the path for debugging purposes+-- | This function can be used to debug Queries. It takes a Query value and prints+--   out the URL path generated by that query. debugQuery :: (Query query) => query -> IO ()-debugQuery query = putStrLn ("Query path: " ++ basePath ++ toPath query)+debugQuery query = putStrLn $ "Query path: " ++ basePath ++ (formQueryString (path query) (params query)) --- | This function takes a write and prints out the path and body for debugging---   purposes.-debugWrite :: (Write write) => write -> IO ()+-- | This function is used to execute Writes. The function takes a Token and a+--   Write value, and returns a Response value.+executeWrite :: (W.Write write) => Token -> write -> IO F.Response+executeWrite token write = post token (W.path write) (W.params write) (W.body write)++-- | This function can be used to debug Writes. It takes a Write value and prints+--   out the URL path, and post body generated by that write.+debugWrite :: (W.Write write) => write -> IO () debugWrite write = do-  putStrLn ("Write path: " ++ basePath ++ path write)+  putStrLn ("Write path: " ++ basePath ++ W.path write)   putStrLn "Write body:"-  putStrLn $ body write+  putStrLn $ formParamsString $ W.body write +-- | This function can be used to perform raw read queries to any API endpoint.+--   It takes a Token, a Path string and a Map of params (both keys and values+--   are strings). The function returns a standard Response value.+get :: Token -> Path -> Params -> IO F.Response+get token path params = get' token (formQueryString path params)++-- | This function can be used to perform raw post queries to any API endpoint.+--   It takes a Token, a Path string, a Map of params and a body Map. Both Maps+--   have String keys and values. The function returns a standard Response value.+post :: Token -> Path -> Params -> Body -> IO F.Response+post token path params body = post' token queryString bodyString+  where queryString = formQueryString path params+        bodyString  = formParamsString body+ -- The following helper functions aid the exported API functions-makeRawRequest' :: Token -> String -> IO Response-makeRawRequest' token queryString = do-  let fullpath = basePath ++ queryString-  let request = generateRequest fullpath-  makeRequest' token request+get' :: Token -> String -> IO F.Response+get' token queryString = do+  response <- get'' token queryString+  return $ F.fromValue $ extractJSON response -makeRequest' :: Token -> Request -> IO Response-makeRequest' token request = runOAuthM token $ setupOAuth request+get'' :: Token -> String -> IO Response+get'' token queryString = do+  let fullPath = basePath ++ queryString+  let request = generateRequest fullPath+  execute token request -multiQueryString :: (Query query) => [(String, query)] -> String-multiQueryString ps = "/multi?queries=" ++ (urlEncode $ "{" ++ (join "," $ map queryPair ps) ++ "}")-  where queryPair (n,q) = "\"" ++ n ++ "\":\"" ++ toPath q ++ "\""+post' :: Token -> String -> String -> IO F.Response+post' token queryString bodyString = do+  let fullPath = basePath ++ queryString+  let request = generatePostRequest fullPath bodyString+  response <- execute token request+  return $ F.fromValue $ extractJSON response +execute :: Token -> Request -> IO Response+execute token request = runOAuthM token $ setupOAuth request++formQueryString :: String -> M.Map String String -> String+formQueryString path params = path ++ "?" ++ (formParamsString params)++formParamsString :: M.Map String String -> String+formParamsString params = formParamsString' $ M.toList params++formParamsString' :: [(String, String)] -> String+formParamsString' paramList = join "&" $ map formParamParts filteredParams+  where filteredParams = filter (\(k,v) -> "" /= v) paramList++formParamParts :: (String, String) -> String+formParamParts (key, value) = key ++ "=" ++ (urlEncode value)++formMultiQueryString :: (Query query) => [(String, query)] -> String+formMultiQueryString ps = "/multi?queries=" ++ (urlEncode $ "{" ++ (join "," $ map queryPair ps) ++ "}")+  where queryPair (n,q) = "\"" ++ n ++ "\":\"" ++ (formQueryString (path q) (params 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)@@ -107,10 +141,11 @@ generateRequest url = (fromJust $ parseURL url) { reqHeaders = (fromList headersList) }  generatePostRequest :: String -> String -> Request-generatePostRequest url body = baseRequest { reqHeaders = (fromList headersList)+generatePostRequest url body = baseRequest { reqHeaders = (fromList postHeaders)                                            , method = POST                                            , reqPayload = B.pack body }   where baseRequest = (fromJust $ parseURL url)+        postHeaders = headersList ++ [("Content-Type", "application/x-www-form-urlencoded")]  setupOAuth :: Request -> OAuthMonadT IO Response setupOAuth request = do@@ -121,7 +156,7 @@ extractJSON = fromJust . decode . rspPayload  headersList :: [(String, String)]-headersList = [("X-Factual-Lib", "factual-haskell-driver-0.4.0")]+headersList = [("X-Factual-Lib", "factual-haskell-driver-0.5.0")]  basePath :: String basePath = "http://api.v3.factual.com"
README.md view
@@ -20,7 +20,9 @@ * Geocode Queries * Geopulse Queries * Resolve Queries+* Match Queries * Schema Queries+* Diffs Queries * Multi Queries * Raw Read Queries * Parametric Search@@ -56,16 +58,19 @@ can always run the unit tests:      *Main> runUnitTests-    Cases: 43  Tried: 43  Errors: 0  Failures: 0-    Counts {cases = 43, tried = 43, errors = 0, failures = 0}+    Cases: 51  Tried: 51  Errors: 0  Failures: 0+    Counts {cases = 51, tried = 51, errors = 0, failures = 0}     *Main> runIntegrationTests "mykey" "mysecret"-    Cases: 9  Tried: 9  Errors: 0  Failures: 0-    Counts {cases = 9, tried = 9, 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 or by visiting the [Hackage page](http://hackage.haskell.org/package/factual-api).++The [github wiki](https://github.com/rudyl313/factual-haskell-driver/wiki) also+provides thorough documentation and examples for each feature.  # Examples 
+ examples/DiffsExample.hs view
@@ -0,0 +1,18 @@+module Main where++import System.Environment (getArgs)+import Network.Factual.API+import Data.Factual.Query.DiffsQuery+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 = DiffsQuery { table = Custom "canada-stable", start = 1339123455775, end = 1339124455775 }+  result <- executeQuery token query+  putStrLn $ "Status: " ++ status result+  putStrLn $ "Version: " ++ show (version result)+  putStrLn $ "Data: " ++ show (response result)
examples/FacetsExample.hs view
@@ -19,7 +19,7 @@                           , limit        = Nothing                           , minCount     = Nothing                           , includeCount = False }-  result <- makeRequest token query+  result <- executeQuery token query   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
examples/GeocodeExample.hs view
@@ -12,7 +12,7 @@   let oauthSecret = last args   let token = generateToken oauthKey oauthSecret   let query = GeocodeQuery $ Point 34.06021 (-118.41828)-  result <- makeRequest token query+  result <- executeQuery token query   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
examples/GeopulseExample.hs view
@@ -13,7 +13,7 @@   let token = generateToken oauthKey oauthSecret   let query = GeopulseQuery { geo    = Point 34.06021 (-118.41828)                             , select = ["commercial_density"] }-  result <- makeRequest token query+  result <- executeQuery token query   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
+ examples/MatchExample.hs view
@@ -0,0 +1,19 @@+module Main where++import System.Environment (getArgs)+import Network.Factual.API+import Data.Factual.Query.MatchQuery+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 = MatchQuery [ MatchStr "name" "McDonalds"+                         , MatchStr "address" "10451 Santa Monica Blvd" ]+  result <- executeQuery token query+  putStrLn $ "Status: " ++ status result+  putStrLn $ "Version: " ++ show (version result)+  putStrLn $ "Data: " ++ show (response result)
examples/MultiExample.hs view
@@ -29,7 +29,7 @@                          , 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+  multiResult <- executeMultiQuery token multiQuery   let result1 = multiResult M.! "query1"   let result2 = multiResult M.! "query2"   putStrLn "query1:"
examples/RawReadExample.hs view
@@ -3,6 +3,7 @@ import System.Environment (getArgs) import Network.Factual.API import Data.Factual.Response+import qualified Data.Map as M  main :: IO() main = do@@ -10,7 +11,7 @@   let oauthKey = head args   let oauthSecret = last args   let token = generateToken oauthKey oauthSecret-  result <- makeRawRequest token "/t/places?q=starbucks"+  result <- get token "/t/places" $ M.fromList [("q", "starbucks")]   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
examples/ReadExample.hs view
@@ -18,8 +18,9 @@                         , offset = Nothing                         , includeCount = True                         , geo = Just (Circle 34.06021 (-118.41828) 5000.0)+                        , sort = []                         , filters = [EqualStr "name" "Stand"] }-  result <- makeRequest token query+  result <- executeQuery token query   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
examples/ResolveExample.hs view
@@ -11,9 +11,11 @@   let oauthKey = head args   let oauthSecret = last args   let token = generateToken oauthKey oauthSecret-  let query = ResolveQuery [ ResolveStr "name" "McDonalds"-                           , ResolveStr "address" "10451 Santa Monica Blvd" ]-  result <- makeRequest token query+  let resolveValues = [ ResolveStr "name" "McDonalds"+                      , ResolveStr "address" "10451 Santa Monica Blvd" ]+  let query = ResolveQuery { values = resolveValues+                           , debug  = False }+  result <- executeQuery token query   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
examples/SchemaExample.hs view
@@ -12,7 +12,7 @@   let oauthSecret = last args   let token = generateToken oauthKey oauthSecret   let query = SchemaQuery Places-  result <- makeRequest token query+  result <- executeQuery token query   putStrLn $ "Status: " ++ status result   putStrLn $ "Version: " ++ show (version result)   putStrLn $ "Data: " ++ show (response result)
factual-api.cabal view
@@ -1,5 +1,5 @@ name:            factual-api-version:         0.4.0+version:         0.5.0 license:         BSD3 license-file:    LICENSE.txt category:        API, Web@@ -27,9 +27,11 @@   exposed-modules:     Network.Factual.API     Data.Factual.Query+    Data.Factual.Query.DiffsQuery     Data.Factual.Query.FacetsQuery     Data.Factual.Query.GeocodeQuery     Data.Factual.Query.GeopulseQuery+    Data.Factual.Query.MatchQuery     Data.Factual.Query.ReadQuery     Data.Factual.Query.ResolveQuery     Data.Factual.Query.SchemaQuery@@ -40,6 +42,7 @@     Data.Factual.Shared.Filter     Data.Factual.Shared.Geo     Data.Factual.Shared.Search+    Data.Factual.Shared.SortOrder     Data.Factual.Shared.Table    other-modules:
test/Tests.hs view
@@ -1,11 +1,13 @@ import Test.HUnit import Network.Factual.API-import Data.Factual.Query import Data.Factual.Query.ReadQuery import Data.Factual.Query.SchemaQuery import Data.Factual.Query.ResolveQuery import Data.Factual.Query.GeocodeQuery+import Data.Factual.Query.MatchQuery import Data.Factual.Response+import qualified Data.Factual.Query.DiffsQuery as D+import qualified Data.Factual.Query as Q import qualified Data.Map as M import qualified Data.Factual.Write as W import qualified Data.Factual.Query.FacetsQuery as F@@ -28,13 +30,13 @@                      , TestLabel "Crosswalk Products table test" crosswalkProductsTablePathTest                      , TestLabel "Monetize table test" monetizeTablePathTest                      , TestLabel "Custom table test" customTablePathTest-                     , TestLabel "Geopulse test" geopulsePathTest-                     , TestLabel "Geocode test" geocodePathTest+                     , TestLabel "Geopulse test" geopulseTest+                     , TestLabel "Geocode test" geocodeTest                      , TestLabel "And search test" andSearchPathTest                      , TestLabel "Or search test" orSearchPathTest-                     , TestLabel "Select test" selectPathTest-                     , TestLabel "Limit test" limitPathTest-                     , TestLabel "Offset test" offsetPathTest+                     , TestLabel "Select test" selectTest+                     , TestLabel "Limit test" limitTest+                     , TestLabel "Offset test" offsetTest                      , TestLabel "Equal number filter test" equalNumFilterTest                      , TestLabel "Equal string filter test" equalStrFilterTest                      , TestLabel "Not equal number filter test" notEqualNumFilterTest@@ -49,13 +51,21 @@                      , TestLabel "Not begins with any filter test" notBeginsWithAnyFilterTest                      , TestLabel "Is blank filter test" isBlankFilterTest                      , TestLabel "Is not blank filter test" isNotBlankFilterTest+                     , TestLabel "Greater than number filter test" greaterThanFilterTest+                     , TestLabel "Greater than or equal to number filter test" greaterThanOrEqualToFilterTest+                     , TestLabel "Less than number filter test" lessThanFilterTest+                     , TestLabel "Less than or equal to number filter test" lessThanOrEqualToFilterTest+                     , TestLabel "Search filter test" searchFilterTest                      , TestLabel "And filter test" andFilterTest                      , TestLabel "Or filter test" orFilterTest                      , TestLabel "Geo test" geoTest+                     , TestLabel "Sort test" sortTest                      , TestLabel "Include count test" includeCountTest                      , TestLabel "Schema query test" schemaQueryTest                      , TestLabel "Resolve query test" resolveQueryTest+                     , TestLabel "Match query test" matchQueryTest                      , TestLabel "Facets test" facetsTest+                     , TestLabel "Diffs test" diffsTest                      , TestLabel "Submit path test" submitPathTest                      , TestLabel "Submit body test" submitBodyTest                      , TestLabel "Flag path test" flagPathTest@@ -64,225 +74,312 @@ integrationTests key secret = TestList [ TestLabel "Read test" (readIntegrationTest token)                                        , TestLabel "Schema test" (schemaIntegrationTest token)                                        , TestLabel "Resolve test" (resolveIntegrationTest token)+                                       , TestLabel "Match test" (matchIntegrationTest token)                                        , TestLabel "Raw read test" (rawIntegrationTest token)                                        , TestLabel "Facets test" (facetsIntegrationTest token)+                                       --, TestLabel "Diffs test" (diffsIntegrationTest token)                                        , TestLabel "Geopulse test" (geopulseIntegrationTest token)                                        , TestLabel "Geocode test" (geocodeIntegrationTest token)                                        , TestLabel "Multi test" (multiIntegrationTest token)+                                       --, TestLabel "Submit test" (submitIntegrationTest token)+                                       --, TestLabel "Flag test" (flagIntegrationTest token)                                        , TestLabel "Error test" (errorIntegrationTest token) ]                             where token = generateToken key secret  placeTablePathTest = TestCase (do-  let expected = "/t/places?include_count=false"-  let path = toPath $ blankReadQuery { table = Places }-  assertEqual "Correct path for places table" expected path)+  let expected = "/t/places"+  let queryPath = Q.path $ blankReadQuery { table = Places }+  assertEqual "Correct path for places table" expected queryPath)  restaurantsTablePathTest = TestCase (do-  let expected = "/t/restaurants-us?include_count=false"-  let path = toPath $ blankReadQuery { table = RestaurantsUS }-  assertEqual "Correct path for us restaurants table" expected path)+  let expected = "/t/restaurants-us"+  let queryPath = Q.path $ blankReadQuery { table = RestaurantsUS }+  assertEqual "Correct path for us restaurants table" expected queryPath)  hotelsTablePathTest = TestCase (do-  let expected = "/t/hotels-us?include_count=false"-  let path = toPath $ blankReadQuery { table = HotelsUS }-  assertEqual "Correct path for us hotels table" expected path)+  let expected = "/t/hotels-us"+  let queryPath = Q.path $ blankReadQuery { table = HotelsUS }+  assertEqual "Correct path for us hotels table" expected queryPath)  globalTablePathTest = TestCase (do-  let expected = "/t/global?include_count=false"-  let path = toPath $ blankReadQuery { table = Global }-  assertEqual "Correct path for global table" expected path)+  let expected = "/t/global"+  let queryPath = Q.path $ blankReadQuery { table = Global }+  assertEqual "Correct path for global table" expected queryPath)  crosswalkTablePathTest = TestCase (do-  let expected = "/t/crosswalk?include_count=false"-  let path = toPath $ blankReadQuery { table = Crosswalk }-  assertEqual "Correct path for crosswalk table" expected path)+  let expected = "/t/crosswalk"+  let queryPath = Q.path $ blankReadQuery { table = Crosswalk }+  assertEqual "Correct path for crosswalk table" expected queryPath)  healthcareTablePathTest = TestCase (do-  let expected = "/t/health-care-providers-us?include_count=false"-  let path = toPath $ blankReadQuery { table = HealthCareProviders }-  assertEqual "Correct path for health care providers table" expected path)+  let expected = "/t/health-care-providers-us"+  let queryPath = Q.path $ blankReadQuery { table = HealthCareProviders }+  assertEqual "Correct path for health care providers table" expected queryPath)  worldGeographiesTablePathTest = TestCase (do-  let expected = "/t/world-geographies?include_count=false"-  let path = toPath $ blankReadQuery { table = WorldGeographies }-  assertEqual "Correct path for world geographies table" expected path)+  let expected = "/t/world-geographies"+  let queryPath = Q.path $ blankReadQuery { table = WorldGeographies }+  assertEqual "Correct path for world geographies table" expected queryPath)  cpgTablePathTest = TestCase (do-  let expected = "/t/products-cpg?include_count=false"-  let path = toPath $ blankReadQuery { table = ProductsCPG }-  assertEqual "Correct path for products CPG table" expected path)+  let expected = "/t/products-cpg"+  let queryPath = Q.path $ blankReadQuery { table = ProductsCPG }+  assertEqual "Correct path for products CPG table" expected queryPath)  crosswalkProductsTablePathTest = TestCase (do-  let expected = "/t/products-crosswalk?include_count=false"-  let path = toPath $ blankReadQuery { table = ProductsCrosswalk }-  assertEqual "Correct path for products crosswalk table" expected path)+  let expected = "/t/products-crosswalk"+  let queryPath = Q.path $ blankReadQuery { table = ProductsCrosswalk }+  assertEqual "Correct path for products crosswalk table" expected queryPath)  monetizeTablePathTest = TestCase (do-  let expected = "/places/monetize?include_count=false"-  let path = toPath $ blankReadQuery { table = Monetize }-  assertEqual "Correct path for monetize table" expected path)+  let expected = "/places/monetize"+  let queryPath = Q.path $ blankReadQuery { table = Monetize }+  assertEqual "Correct path for monetize table" expected queryPath)  customTablePathTest = TestCase (do-  let expected = "/t/foo?include_count=false"-  let path = toPath $ blankReadQuery { table = Custom "foo" }-  assertEqual "Correct path for custom table" expected path)+  let expected = "/t/foo"+  let queryPath = Q.path $ blankReadQuery { table = Custom "foo" }+  assertEqual "Correct path for custom table" expected queryPath) -geopulsePathTest = TestCase (do-  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)+geopulseTest = TestCase (do+  let query = G.GeopulseQuery { G.geo = Point 34.06021 (-118.41828) , G.select = ["commercial_density"] }+  let queryPath = Q.path query+  let queryParams = Q.params query+  assertEqual "Correct path for geopulse" queryPath "/places/geopulse"+  assertEqual "Correct geo param" (queryParams M.! "geo") "{\"$point\":[34.06021,-118.41828]}"+  assertEqual "Correct select param" (queryParams M.! "select") "commercial_density") -geocodePathTest = TestCase (do-  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)+geocodeTest = TestCase (do+  let query = GeocodeQuery $ Point 34.06021 (-118.41828)+  let queryPath = Q.path query+  let queryParams = Q.params query+  assertEqual "Correct path for geocode" queryPath "/places/geocode"+  assertEqual "Correct geo param" (queryParams M.! "geo") "{\"$point\":[34.06021,-118.41828]}")  andSearchPathTest = TestCase (do-  let expected = "/t/places?q=foo%20bar&include_count=false"-  let path = toPath $ blankReadQuery { search = AndSearch ["foo", "bar"] }-  assertEqual "Correct path for ANDed search" expected path)+  let query = blankReadQuery { search = AndSearch ["foo", "bar"] }+  let queryParams = Q.params query+  assertEqual "Correct query value" (queryParams M.! "q")  "foo bar")  orSearchPathTest = TestCase (do-  let expected = "/t/places?q=foo%2Cbar&include_count=false"-  let path = toPath $ blankReadQuery { search = OrSearch ["foo", "bar"] }-  assertEqual "Correct path for ANDed search" expected path)+  let query = blankReadQuery { search = OrSearch ["foo", "bar"] }+  let queryParams = Q.params query+  assertEqual "Correct query value" (queryParams M.! "q")  "foo,bar") -selectPathTest = TestCase (do-  let expected = "/t/places?select=foo%2Cbar&include_count=false"-  let path = toPath $ blankReadQuery { select = ["foo", "bar"] }-  assertEqual "Correct path for select terms" expected path)+selectTest = TestCase (do+  let query = blankReadQuery { select = ["foo", "bar"] }+  let queryParams = Q.params query+  assertEqual "Correct select value" (queryParams M.! "select")  "foo,bar") -limitPathTest = TestCase (do-  let expected = "/t/places?limit=321&include_count=false"-  let path = toPath $ blankReadQuery { limit = Just 321 }-  assertEqual "Correct path for limit" expected path)+limitTest = TestCase (do+  let query = blankReadQuery { limit = Just 321 }+  let queryParams = Q.params query+  assertEqual "Correct limit value" (queryParams M.! "limit") "321") -offsetPathTest = TestCase (do-  let expected = "/t/places?offset=321&include_count=false"-  let path = toPath $ blankReadQuery { offset = Just 321 }-  assertEqual "Correct path for offset" expected path)+offsetTest = TestCase (do+  let query = blankReadQuery { offset = Just 321 }+  let queryParams = Q.params query+  assertEqual "Correct offset value" (queryParams M.! "offset") "321")  equalNumFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [EqualNum "field" 123.4] }+  let queryParams = Q.params query+  assertEqual "Correct filters value" (queryParams M.! "filters") "{\"field\":123.4}")  equalStrFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [EqualStr "field" "value"] }+  let queryParams = Q.params query+  assertEqual "Correct filters value" (queryParams M.! "filters") "{\"field\":\"value\"}")  notEqualNumFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [NotEqualNum "field" 123.4] }+  let queryParams = Q.params query+  assertEqual "Correct filters value" (queryParams M.! "filters") "{\"field\":{\"$neq\":123.4}}")  notEqualStrFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [NotEqualStr "field" "value"] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$neq\":\"value\"}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  inNumListFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [InNumList "field" [123.4, 5432.1]] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$in\":[123.4,5432.1]}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  inStrListFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [InStrList "field" ["value","other"]] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$in\":[\"value\",\"other\"]}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  notInNumListFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [NotInNumList "field" [123.4, 5432.1]] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$nin\":[123.4,5432.1]}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  notInStrListFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [NotInStrList "field" ["value","other"]] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$nin\":[\"value\",\"other\"]}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  beginsWithFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [BeginsWith "field" "val"] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$bw\":\"val\"}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  notBeginsWithFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [NotBeginsWith "field" "val"] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$nbw\":\"val\"}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  beginsWithAnyFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [BeginsWithAny "field" ["val","ot"]] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$bwin\":[\"val\",\"ot\"]}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  notBeginsWithAnyFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [NotBeginsWithAny "field" ["val","ot"]] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$nbwin\":[\"val\",\"ot\"]}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  isBlankFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [IsBlank "field"] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$blank\":true}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  isNotBlankFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [IsNotBlank "field"] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$blank\":false}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected) +greaterThanFilterTest = TestCase (do+  let query = blankReadQuery { filters = [GreaterThan "field" 10.0] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$gt\":10.0}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)++greaterThanOrEqualToFilterTest = TestCase (do+  let query = blankReadQuery { filters = [GreaterThanOrEqualTo "field" 10.0] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$gte\":10.0}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)++lessThanFilterTest = TestCase (do+  let query = blankReadQuery { filters = [LessThan "field" 10.0] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$lt\":10.0}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)++lessThanOrEqualToFilterTest = TestCase (do+  let query = blankReadQuery { filters = [LessThanOrEqualTo "field" 10.0] }+  let queryParams = Q.params query+  let expected = "{\"field\":{\"$lte\":10.0}}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)++searchFilterTest = TestCase (do+  let query = blankReadQuery { filters = [SearchFilter "field" "value"] }+  let queryParams = Q.params query+  assertEqual "Correct filters value" (queryParams M.! "filters") "{\"field\":{\"$search\":\"value\"}}")+ andFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [And [IsBlank "field1", IsNotBlank "field2"]] }+  let queryParams = Q.params query+  let expected = "{\"$and\":[{\"field1\":{\"$blank\":true}},{\"field2\":{\"$blank\":false}}]}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  orFilterTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { filters = [Or [IsBlank "field1", IsNotBlank "field2"]] }+  let queryParams = Q.params query+  let expected = "{\"$or\":[{\"field1\":{\"$blank\":true}},{\"field2\":{\"$blank\":false}}]}"+  assertEqual "Correct filters value" (queryParams M.! "filters") expected)  geoTest = TestCase (do-  let expected = "/t/places?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)+  let query = blankReadQuery { geo = Just (Circle 300.1 200.3 100.5) }+  let queryParams = Q.params query+  let expected = "{\"$circle\":{\"$center\":[300.1, 200.3],\"$meters\":100.5}}"+  assertEqual "Correct geo value" (queryParams M.! "geo") expected) +sortTest = TestCase (do+  let query = blankReadQuery { sort = [Asc "name", Desc "country"] }+  let queryParams = Q.params query+  let expected = "name:asc,country:desc"+  assertEqual "Correct sort value" (queryParams M.! "sort") expected)+ includeCountTest = TestCase (do-  let expected = "/t/places?include_count=true"-  let path = toPath $ blankReadQuery { includeCount = True }-  assertEqual "Correct path for include count" expected path)+  let query = blankReadQuery { includeCount = True }+  let queryParams = Q.params query+  assertEqual "Correct include_count value" (queryParams M.! "include_count") "true")  schemaQueryTest = TestCase (do+  let queryPath = Q.path $ SchemaQuery Places   let expected = "/t/places/schema"-  let path = toPath $ SchemaQuery Places-  assertEqual "Correct path for a schema query" expected path)+  assertEqual "Correct path for a schema query" queryPath expected)  resolveQueryTest = TestCase (do-  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)+  let query = ResolveQuery { values = [ResolveStr "field1" "value1", ResolveNum "field2" 32.1],+                             debug  = False }+  let queryPath = Q.path query+  let queryParams = Q.params query+  let expectedValues = "{\"field1\":\"value1\",\"field2\":32.1}"+  assertEqual "Correct path" queryPath "/places/resolve"+  assertEqual "Correct values" (queryParams M.! "values") expectedValues+  assertEqual "Correct debug" (queryParams M.! "debug") "false") +matchQueryTest = TestCase (do+  let query = MatchQuery [MatchStr "field1" "value1", MatchNum "field2" 32.1]+  let queryPath = Q.path query+  let queryParams = Q.params query+  let expectedValues = "{\"field1\":\"value1\",\"field2\":32.1}"+  assertEqual "Correct path" queryPath "/places/match"+  assertEqual "Correct values" (queryParams M.! "values") expectedValues)+ facetsTest = TestCase (do-  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"]-                                    , 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)+  let query = 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 }+  let queryPath = Q.path query+  let queryParams = Q.params query+  assertEqual "Correct path for a facets query" queryPath "/t/places/facets"+  assertEqual "Correct q" (queryParams M.! "q") "starbucks"+  assertEqual "Correct select" (queryParams M.! "select") "locality,region"+  assertEqual "Correct filters" (queryParams M.! "filters") "{\"country\":\"US\"}"+  assertEqual "Correct limit" (queryParams M.! "limit") "10"+  assertEqual "Correct min count" (queryParams M.! "min_count") "2"+  assertEqual "Correct include count" (queryParams M.! "include_count") "false") +diffsTest = TestCase (do+  let query = D.DiffsQuery { D.table = Places, D.start = 1318890505254, D.end = 1318890516892 }+  let queryPath = Q.path query+  let queryParams = Q.params query+  assertEqual "Correct path for a diffs query" queryPath "/t/places/diffs"+  assertEqual "Correct start" (queryParams M.! "start") "1318890505254"+  assertEqual "Correct end" (queryParams M.! "end") "1318890516892")+ submitPathTest = TestCase (do   let expected = "/t/places/foobar/submit"-  let path = W.path submitWrite-  assertEqual "Correct path for submit" expected path)+  let writePath = W.path submitWrite+  assertEqual "Correct path for submit" expected writePath)  submitBodyTest = TestCase (do   let expected = "user=user123&values={\"key\":\"val\"}"-  let body = W.body submitWrite-  assertEqual "Correct body for submit" expected body)+  let bodyParams = W.body submitWrite+  assertEqual "Correct user" (bodyParams M.! "user") "user123"+  assertEqual "Correct values" (bodyParams M.! "values") "{\"key\":\"val\"}")  flagPathTest = TestCase (do   let expected = "/t/places/foobar/flag"@@ -291,9 +388,13 @@  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)+  let bodyParams = W.body flagWrite+  assertEqual "Correct problem" (bodyParams M.! "problem") "Duplicate"+  assertEqual "Correct user" (bodyParams M.! "user") "user123"+  assertEqual "Correct comment" (bodyParams M.! "comment") "There was a problem"+  assertEqual "Correct debug" (bodyParams M.! "debug") "false") + readIntegrationTest :: Token -> Test readIntegrationTest token = TestCase (do   let query = ReadQuery { table = Places@@ -303,25 +404,33 @@                         , offset = Just 10                         , includeCount = True                         , geo = Just (Circle 34.06021 (-118.41828) 5000.0)+                        , sort = []                         , filters = [EqualStr "name" "Stand"] }-  result <- makeRequest token query+  result <- executeQuery token query   assertEqual "Valid read query" "ok" (status result))  schemaIntegrationTest :: Token -> Test schemaIntegrationTest token = TestCase (do   let query = SchemaQuery Places-  result <- makeRequest token query+  result <- executeQuery token query   assertEqual "Valid schema query" "ok" (status result))  resolveIntegrationTest :: Token -> Test resolveIntegrationTest token = TestCase (do-  let query = ResolveQuery [ResolveStr "name" "McDonalds"]-  result <- makeRequest token query+  let query = ResolveQuery { values = [ResolveStr "name" "McDonalds"],+                             debug = False }+  result <- executeQuery token query   assertEqual "Valid resolve query" "ok" (status result)) +matchIntegrationTest :: Token -> Test+matchIntegrationTest token = TestCase (do+  let query = MatchQuery [MatchStr "name" "McDonalds"]+  result <- executeQuery token query+  assertEqual "Valid match query" "ok" (status result))+ rawIntegrationTest :: Token -> Test rawIntegrationTest token = TestCase (do-  result <- makeRawRequest token "/t/places?q=starbucks"+  result <- get token "/t/places" (M.fromList [("q", "starbucks")])   assertEqual "Valid read query" "ok" (status result))  facetsIntegrationTest token = TestCase (do@@ -333,18 +442,23 @@                             , F.limit        = Just 100                             , F.minCount     = Just 1                             , F.includeCount = False }-  result <- makeRequest token query+  result <- executeQuery token query   assertEqual "Valid facets query" "ok" (status result)) +diffsIntegrationTest token = TestCase (do+  let query = D.DiffsQuery { D.table = Custom "canada-stable", D.start = 1339123455775, D.end = 1339124455775 }+  result <- executeQuery token query+  assertEqual "Valid diffs 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+  result <- executeQuery 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+  result <- executeQuery token query   assertEqual "Valid geopulse query" "ok" (status result))  @@ -357,16 +471,42 @@                          , offset = Just 10                          , includeCount = True                          , geo = Just (Circle 34.06021 (-118.41828) 5000.0)+                         , sort = []                          , filters = [EqualStr "name" "Stand"] }   let query2 = query1 { filters = [EqualStr "name" "Xerox"] }-  results <- makeMultiRequest token $ M.fromList [("query1", query1), ("query2", query2)]+  results <- executeMultiQuery 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]) +submitIntegrationTest :: Token -> Test+submitIntegrationTest token = TestCase (do+  let newValues = M.fromList [ ("name","Factual")+                             , ("address","1801 Avenue of the Stars, Suite 1450")+                             , ("country","USA")+                             , ("locality","Los Angeles") ]+  let write = S.Submit { S.table     = Custom "canada-edge"+                       , S.user      = "drivertest"+                       , S.factualId = Nothing+                       , S.values    = newValues }+  result <- executeWrite token write+  assertEqual "Valid submit" "ok" (status result))++flagIntegrationTest :: Token -> Test+flagIntegrationTest token = TestCase (do+  let write = L.Flag { L.table     = Custom "canada-edge"+                     , L.user      = "drivertest"+                     , L.factualId = "f33527e0-a8b4-4808-a820-2686f18cb00c"+                     , L.problem   = L.Inaccurate+                     , L.comment   = Nothing+                     , L.debug     = False+                     , L.reference = Nothing }+  result <- executeWrite token write+  assertEqual "Valid flag" "ok" (status result))+ errorIntegrationTest :: Token -> Test errorIntegrationTest token = TestCase (do-  result <- makeRawRequest token "/t/foobarbaz"+  result <- get token "/t/foobarbaz" (M.empty)   assertEqual "Invalud read query" "error" (status result))  blankReadQuery :: ReadQuery@@ -377,6 +517,7 @@                            , offset = Nothing                            , filters = []                            , geo = Nothing+                           , sort = []                            , includeCount = False }  submitWrite :: S.Submit