servant-client: add spec for QueryString and DeepQuery

This commit is contained in:
Clément Delafargue 2022-12-15 16:30:10 +01:00
parent c45002a021
commit 1ae85d1ee9
No known key found for this signature in database
2 changed files with 46 additions and 3 deletions

View file

@ -31,11 +31,13 @@ import Control.Monad.Error.Class
import Data.Aeson import Data.Aeson
import Data.ByteString import Data.ByteString
(ByteString) (ByteString)
import qualified Data.ByteString.Char8 as C8
import Data.ByteString.Builder import Data.ByteString.Builder
(byteString) (byteString)
import qualified Data.ByteString.Lazy as LazyByteString import qualified Data.ByteString.Lazy as LazyByteString
import Data.Char import Data.Char
(chr, isPrint) (chr, isPrint)
import Data.Maybe (fromMaybe)
import Data.Monoid () import Data.Monoid ()
import Data.Proxy import Data.Proxy
import Data.SOP import Data.SOP
@ -54,17 +56,18 @@ import Network.Wai.Handler.Warp
import System.IO.Unsafe import System.IO.Unsafe
(unsafePerformIO) (unsafePerformIO)
import Test.QuickCheck import Test.QuickCheck
import Text.Read (readMaybe)
import Web.FormUrlEncoded import Web.FormUrlEncoded
(FromForm, ToForm) (FromForm, ToForm)
import Servant.API import Servant.API
((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth, ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth,
BasicAuthData (..), Capture, CaptureAll, DeleteNoContent, BasicAuthData (..), Capture, CaptureAll, DeepQuery, DeleteNoContent,
EmptyAPI, FormUrlEncoded, Fragment, FromHttpApiData (..), Get, Header, Headers, EmptyAPI, FormUrlEncoded, Fragment, FromHttpApiData (..), Get, Header, Headers,
JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender), JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),
NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam, NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,
QueryParams, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..), UVerb, Union, QueryParams, QueryString, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..),
Verb, WithStatus (WithStatus), NamedRoutes, addHeader) UVerb, Union, Verb, WithStatus (WithStatus), NamedRoutes, addHeader)
import Servant.API.Generic ((:-)) import Servant.API.Generic ((:-))
import Servant.Client import Servant.Client
import qualified Servant.Client.Core.Auth as Auth import qualified Servant.Client.Core.Auth as Auth
@ -121,6 +124,25 @@ data OtherRoutes mode = OtherRoutes
-- Get for HTTP 307 Temporary Redirect -- Get for HTTP 307 Temporary Redirect
type Get307 = Verb 'GET 307 type Get307 = Verb 'GET 307
data Filter = Filter
{ ageFilter :: Integer
, nameFilter :: String
}
deriving Show
instance FromDeepQuery Filter where
fromDeepQuery params = do
let maybeToRight l = maybe (Left l) Right
age' <- maybeToRight "missing age" $ readMaybe . Text.unpack =<< join (lookup ["age"] params)
name' <- maybeToRight "missing name" $ join $ lookup ["name"] params
return $ Filter age' (Text.unpack name')
instance ToDeepQuery Filter where
toDeepQuery (Filter age' name') =
[ (["age"], Just (Text.pack $ show age'))
, (["name"], Just (Text.pack name'))
]
type Api = type Api =
Get '[JSON] Person Get '[JSON] Person
:<|> "get" :> Get '[JSON] Person :<|> "get" :> Get '[JSON] Person
@ -139,6 +161,8 @@ type Api =
:<|> "param-binary" :> QueryParam "payload" UrlEncodedByteString :> Raw :<|> "param-binary" :> QueryParam "payload" UrlEncodedByteString :> Raw
:<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person] :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
:<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
:<|> "query-string" :> QueryString :> Get '[JSON] Person
:<|> "deep-query" :> DeepQuery "filter" Filter :> Get '[JSON] Person
:<|> "fragment" :> Fragment String :> Get '[JSON] Person :<|> "fragment" :> Fragment String :> Get '[JSON] Person
:<|> "rawSuccess" :> Raw :<|> "rawSuccess" :> Raw
:<|> "rawSuccessPassHeaders" :> Raw :<|> "rawSuccessPassHeaders" :> Raw
@ -176,6 +200,8 @@ getQueryParam :: Maybe String -> ClientM Person
getQueryParamBinary :: Maybe UrlEncodedByteString -> HTTP.Method -> ClientM Response getQueryParamBinary :: Maybe UrlEncodedByteString -> HTTP.Method -> ClientM Response
getQueryParams :: [String] -> ClientM [Person] getQueryParams :: [String] -> ClientM [Person]
getQueryFlag :: Bool -> ClientM Bool getQueryFlag :: Bool -> ClientM Bool
getQueryString :: [(ByteString, Maybe ByteString)] -> ClientM Person
getDeepQuery :: Filter -> ClientM Person
getFragment :: ClientM Person getFragment :: ClientM Person
getRawSuccess :: HTTP.Method -> ClientM Response getRawSuccess :: HTTP.Method -> ClientM Response
getRawSuccessPassHeaders :: HTTP.Method -> ClientM Response getRawSuccessPassHeaders :: HTTP.Method -> ClientM Response
@ -203,6 +229,8 @@ getRoot
:<|> getQueryParamBinary :<|> getQueryParamBinary
:<|> getQueryParams :<|> getQueryParams
:<|> getQueryFlag :<|> getQueryFlag
:<|> getQueryString
:<|> getDeepQuery
:<|> getFragment :<|> getFragment
:<|> getRawSuccess :<|> getRawSuccess
:<|> getRawSuccessPassHeaders :<|> getRawSuccessPassHeaders
@ -240,6 +268,14 @@ server = serve api (
) )
:<|> (\ names -> return (zipWith Person names [0..])) :<|> (\ names -> return (zipWith Person names [0..]))
:<|> return :<|> return
:<|> (\ q -> return alice { _name = maybe mempty C8.unpack $ join (lookup "name" q)
, _age = fromMaybe 0 (readMaybe . C8.unpack =<< join (lookup "age" q))
}
)
:<|> (\ filter' -> return alice { _name = nameFilter filter'
, _age = ageFilter filter'
}
)
:<|> return alice :<|> return alice
:<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "rawSuccess") :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "rawSuccess")
:<|> (Tagged $ \ request respond -> (respond $ Wai.responseLBS HTTP.ok200 (Wai.requestHeaders $ request) "rawSuccess")) :<|> (Tagged $ \ request respond -> (respond $ Wai.responseLBS HTTP.ok200 (Wai.requestHeaders $ request) "rawSuccess"))

View file

@ -115,6 +115,13 @@ successSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do
left show <$> runClient (getQueryFlag flag) baseUrl `shouldReturn` Right flag left show <$> runClient (getQueryFlag flag) baseUrl `shouldReturn` Right flag
it "Servant.API.QueryParam.QueryString" $ \(_, baseUrl) -> do
let qs = [("name", Just "bob"), ("age", Just "1")]
left show <$> runClient (getQueryString qs) baseUrl `shouldReturn` (Right (Person "bob" 1))
it "Servant.API.QueryParam.DeepQuery" $ \(_, baseUrl) -> do
left show <$> runClient (getDeepQuery $ Filter 1 "bob") baseUrl `shouldReturn` (Right (Person "bob" 1))
it "Servant.API.Fragment" $ \(_, baseUrl) -> do it "Servant.API.Fragment" $ \(_, baseUrl) -> do
left id <$> runClient getFragment baseUrl `shouldReturn` Right alice left id <$> runClient getFragment baseUrl `shouldReturn` Right alice