From 77d52083f1ae94f1d97c9cb2a4e7813362e7cc89 Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Sat, 22 Nov 2014 18:53:02 +0100 Subject: [PATCH] haddocks for all 3 forms of query params (phew) --- src/Servant/API/Capture.hs | 2 +- src/Servant/API/QueryParam.hs | 161 ++++++++++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 10 deletions(-) diff --git a/src/Servant/API/Capture.hs b/src/Servant/API/Capture.hs index fb27600c..2453cf5b 100644 --- a/src/Servant/API/Capture.hs +++ b/src/Servant/API/Capture.hs @@ -94,7 +94,7 @@ instance (KnownSymbol capture, ToText a, HasClient sublayout) where p = unpack (toText val) -- | @"books" :> 'Capture' "isbn" Text@ will appear as --- @/books/:isbn@ in the docs +-- @/books/:isbn@ in the docs. instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs sublayout) => HasDocs (Capture sym a :> sublayout) where diff --git a/src/Servant/API/QueryParam.hs b/src/Servant/API/QueryParam.hs index 540c53a4..b8c51ffd 100644 --- a/src/Servant/API/QueryParam.hs +++ b/src/Servant/API/QueryParam.hs @@ -21,15 +21,36 @@ import Servant.Common.Text import Servant.Docs import Servant.Server --- * Single query string parameter lookup - --- | You must implement: +-- | Lookup the value associated to the @sym@ query string parameter +-- and try to extract it as a value of type @a@. -- --- - a @'FromText' a@ instance for serving --- - a @'ToText' a@ instance for (client-side) querying --- - a @'ToParam' ('QueryParam' sym a)@ instance for automatic documentation generation +-- Example: +-- +-- > -- /books?author= +-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book] data QueryParam sym a +-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API, +-- this automatically requires your server-side handler to be a function +-- that takes an argument of type @'Maybe' 'Text'@. +-- +-- This lets servant worry about looking it up in the query string +-- and turning it into a value of the type you specify, enclosed +-- in 'Maybe', because it may not be there and servant would then +-- hand you 'Nothing'. +-- +-- You can control how it'll be converted from 'Text' to your type +-- by simply providing an instance of 'FromText' for your type. +-- +-- Example: +-- +-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book] +-- > +-- > server :: Server MyApi +-- > server = getBooksBy +-- > where getBooksBy :: Maybe Text -> EitherT (Int, String) IO [Book] +-- > getBooksBy Nothing = ...return all books... +-- > getBooksBy (Just author) = ...return books by the given author... instance (KnownSymbol sym, FromText a, HasServer sublayout) => HasServer (QueryParam sym a :> sublayout) where @@ -49,6 +70,31 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) where paramname = cs $ symbolVal (Proxy :: Proxy sym) +-- | If you use a 'QueryParam' in one of your endpoints in your API, +-- the corresponding querying function will automatically take +-- an additional argument of the type specified by your 'QueryParam', +-- enclosed in Maybe. +-- +-- If you give Nothing, nothing will be added to the query string. +-- +-- If you give a non-'Nothing' value, this function will take care +-- of inserting a textual representation of this value in the query string. +-- +-- You can control how values for your type are turned into +-- text by specifying a 'ToText' instance for your type. +-- +-- Example: +-- +-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book] +-- > +-- > myApi :: Proxy MyApi +-- > myApi = Proxy +-- > +-- > getBooksBy :: Maybe Text -> BaseUrl -> EitherT String IO [Book] +-- > getBooksBy = client myApi +-- > -- then you can just use "getBooksBy" to query that endpoint. +-- > -- 'getBooksBy Nothing' for all books +-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov instance (KnownSymbol sym, ToText a, HasClient sublayout) => HasClient (QueryParam sym a :> sublayout) where @@ -74,9 +120,38 @@ instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs sublayout) paramP = Proxy :: Proxy (QueryParam sym a) action' = over params (|> toParam paramP) action --- | Retrieve a list from the query string. +-- | Lookup the values associated to the @sym@ query string parameter +-- and try to extract it as a value of type @[a]@. This is typically +-- meant to support query string parameters of the form +-- @param[]=val1¶m[]=val2@ and so on. Note that servant doesn't actually +-- require the @[]@s and will fetch the values just fine with +-- @param=val1¶m=val2@, too. +-- +-- Example: +-- +-- > -- /books?authors[]=&authors[]=&... +-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book] data QueryParams sym a +-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API, +-- this automatically requires your server-side handler to be a function +-- that takes an argument of type @['Text']@. +-- +-- This lets servant worry about looking up 0 or more values in the query string +-- associated to @authors@ and turning each of them into a value of +-- the type you specify. +-- +-- You can control how the individual values are converted from 'Text' to your type +-- by simply providing an instance of 'FromText' for your type. +-- +-- Example: +-- +-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book] +-- > +-- > server :: Server MyApi +-- > server = getBooksBy +-- > where getBooksBy :: [Text] -> EitherT (Int, String) IO [Book] +-- > getBooksBy authors = ...return all books by these authors... instance (KnownSymbol sym, FromText a, HasServer sublayout) => HasServer (QueryParams sym a :> sublayout) where @@ -98,6 +173,33 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) convert Nothing = Nothing convert (Just v) = fromText v +-- | If you use a 'QueryParams' in one of your endpoints in your API, +-- the corresponding querying function will automatically take +-- an additional argument, a list of values of the type specified +-- by your 'QueryParams'. +-- +-- If you give an empty list, nothing will be added to the query string. +-- +-- Otherwise, this function will take care +-- of inserting a textual representation of your values in the query string, +-- under the same query string parameter name. +-- +-- You can control how values for your type are turned into +-- text by specifying a 'ToText' instance for your type. +-- +-- Example: +-- +-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book] +-- > +-- > myApi :: Proxy MyApi +-- > myApi = Proxy +-- > +-- > getBooksBy :: [Text] -> BaseUrl -> EitherT String IO [Book] +-- > getBooksBy = client myApi +-- > -- then you can just use "getBooksBy" to query that endpoint. +-- > -- 'getBooksBy []' for all books +-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]' +-- > -- to get all books by Asimov and Heinlein instance (KnownSymbol sym, ToText a, HasClient sublayout) => HasClient (QueryParams sym a :> sublayout) where @@ -122,9 +224,29 @@ instance (KnownSymbol sym, ToParam (QueryParams sym a), HasDocs sublayout) paramP = Proxy :: Proxy (QueryParams sym a) action' = over params (|> toParam paramP) action --- | Retrieve a value-less boolean from the query string. -data QueryFlag a +-- | Lookup a potentially value-less query string parameter +-- with boolean semantics. If the param @sym@ is there without any value, +-- or if it's there with value "true" or "1", it's interpreted as 'True'. +-- Otherwise, it's interpreted as 'False'. +-- +-- Example: +-- +-- > -- /books?published +-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book] +data QueryFlag sym +-- | If you use @'QueryFlag' "published"@ in one of the endpoints for your API, +-- this automatically requires your server-side handler to be a function +-- that takes an argument of type 'Bool'. +-- +-- Example: +-- +-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book] +-- > +-- > server :: Server MyApi +-- > server = getBooks +-- > where getBooks :: Bool -> EitherT (Int, String) IO [Book] +-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument... instance (KnownSymbol sym, HasServer sublayout) => HasServer (QueryFlag sym :> sublayout) where @@ -144,6 +266,27 @@ instance (KnownSymbol sym, HasServer sublayout) examine v | v == "true" || v == "1" || v == "" = True | otherwise = False +-- | If you use a 'QueryFlag' in one of your endpoints in your API, +-- the corresponding querying function will automatically take +-- an additional 'Bool' argument. +-- +-- If you give 'False', nothing will be added to the query string. +-- +-- Otherwise, this function will insert a value-less query string +-- parameter under the name associated to your 'QueryFlag'. +-- +-- Example: +-- +-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book] +-- > +-- > myApi :: Proxy MyApi +-- > myApi = Proxy +-- > +-- > getBooks :: Bool -> BaseUrl -> EitherT String IO [Book] +-- > getBooks = client myApi +-- > -- then you can just use "getBooks" to query that endpoint. +-- > -- 'getBooksBy False' for all books +-- > -- 'getBooksBy True' to only get _already published_ books instance (KnownSymbol sym, HasClient sublayout) => HasClient (QueryFlag sym :> sublayout) where