@@ -24,9 +24,15 @@ import Prelude.Compat
2424
2525import Control.Concurrent
2626 (ThreadId, forkIO, killThread)
27+import Control.Monad
28+ (join)
2729import Control.Monad.Error.Class
2830 (throwError)
2931import Data.Aeson
32+import Data.ByteString
33+ (ByteString)
34+import Data.ByteString.Builder
35+ (byteString)
3036import qualified Data.ByteString.Lazy as LazyByteString
3137import Data.Char
3238 (chr, isPrint)
@@ -54,10 +60,10 @@ import Web.FormUrlEncoded
5460import Servant.API
5561 ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth,
5662 BasicAuthData (..), Capture, CaptureAll, DeleteNoContent,
57− EmptyAPI, FormUrlEncoded, Fragment, Get, Header, Headers,
63+ EmptyAPI, FormUrlEncoded, Fragment, FromHttpApiData (..), Get, Header, Headers,
5864 JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),
5965 NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,
60− QueryParams, Raw, ReqBody, StdMethod (GET), UVerb, Union,
66+ QueryParams, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..), UVerb, Union,
6167 WithStatus (WithStatus), addHeader)
6268import Servant.Client
6369import qualified Servant.Client.Core.Auth as Auth
@@ -109,6 +115,10 @@ type Api =
109115 :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person]
110116 :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person
111117 :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person
118+ -- This endpoint makes use of a 'Raw' server because it is not currently
119+ -- possible to handle arbitrary binary query param values with
120+ -- @servant-server@
121+ :<|> "param-binary" :> QueryParam "payload" UrlEncodedByteString :> Raw
112122 :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
113123 :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
114124 :<|> "fragment" :> Fragment String :> Get '[JSON] Person
@@ -143,6 +153,7 @@ getCapture :: String -> ClientM Person
143153getCaptureAll :: [String] -> ClientM [Person]
144154getBody :: Person -> ClientM Person
145155getQueryParam :: Maybe String -> ClientM Person
156+getQueryParamBinary :: Maybe UrlEncodedByteString -> HTTP.Method -> ClientM Response
146157getQueryParams :: [String] -> ClientM [Person]
147158getQueryFlag :: Bool -> ClientM Bool
148159getFragment :: ClientM Person
@@ -167,6 +178,7 @@ getRoot
167178 :<|> getCaptureAll
168179 :<|> getBody
169180 :<|> getQueryParam
181+ :<|> getQueryParamBinary
170182 :<|> getQueryParams
171183 :<|> getQueryFlag
172184 :<|> getFragment
@@ -194,6 +206,13 @@ server = serve api (
194206 Just "alice" -> return alice
195207 Just n -> throwError $ ServerError 400 (n ++ " not found") "" []
196208 Nothing -> throwError $ ServerError 400 "missing parameter" "" [])
209+ :<|> const (Tagged $ \request respond ->
210+ respond . maybe (Wai.responseLBS HTTP.notFound404 [] "Missing: payload")
211+ (Wai.responseLBS HTTP.ok200 [] . LazyByteString.fromStrict)
212+ . join
213+ . lookup "payload"
214+ $ Wai.queryString request
215+ )
197216 :<|> (\ names -> return (zipWith Person names [0..]))
198217 :<|> return
199218 :<|> return alice
@@ -310,3 +329,12 @@ pathGen = fmap NonEmpty path
310329 filter (not . (`elem` ("?%[]/#;" :: String))) $
311330 filter isPrint $
312331 map chr [0..127]
332+
333+newtype UrlEncodedByteString = UrlEncodedByteString { unUrlEncodedByteString :: ByteString }
334+
335+instance ToHttpApiData UrlEncodedByteString where
336+ toEncodedUrlPiece = byteString . HTTP.urlEncode True . unUrlEncodedByteString
337+ toUrlPiece = decodeUtf8 . HTTP.urlEncode True . unUrlEncodedByteString
338+
339+instance FromHttpApiData UrlEncodedByteString where
340+ parseUrlPiece = pure . UrlEncodedByteString . HTTP.urlDecode True . encodeUtf8