@@ -24,9 +24,15 @@ import Prelude.Compat
25import Control.Concurrent
25import Control.Concurrent
26 (ThreadId, forkIO, killThread)
26 (ThreadId, forkIO, killThread)
27import Control.Monad.Error.Class
29import Control.Monad.Error.Class
28 (throwError)
30 (throwError)
29import Data.Aeson
31import Data.Aeson
32+import Data.ByteString
34+import Data.ByteString.Builder
30import qualified Data.ByteString.Lazy as LazyByteString
36import qualified Data.ByteString.Lazy as LazyByteString
31import Data.Char
37import Data.Char
32 (chr, isPrint)
38 (chr, isPrint)
@@ -54,10 +60,10 @@ import Web.FormUrlEncoded
54import Servant.API
60import Servant.API
55 ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth,
61 ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth,
56 BasicAuthData (..), Capture, CaptureAll, DeleteNoContent,
62 BasicAuthData (..), Capture, CaptureAll, DeleteNoContent,
57− EmptyAPI, FormUrlEncoded, Fragment, Get, Header, Headers,
63+ EmptyAPI, FormUrlEncoded, Fragment, FromHttpApiData (..), Get, Header, Headers,
58 JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),
64 JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),
59 NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,
65 NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,
60− QueryParams, Raw, ReqBody, StdMethod (GET), UVerb, Union,
66+ QueryParams, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..), UVerb, Union,
61 WithStatus (WithStatus), addHeader)
67 WithStatus (WithStatus), addHeader)
62import Servant.Client
68import Servant.Client
63import qualified Servant.Client.Core.Auth as Auth
69import qualified Servant.Client.Core.Auth as Auth
@@ -109,6 +115,10 @@ type Api =
109 :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person]
115 :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person]
110 :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person
116 :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person
111 :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person
117 :<|> "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
121+ :<|> "param-binary" :> QueryParam "payload" UrlEncodedByteString :> Raw
112 :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
122 :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
113 :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
123 :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
114 :<|> "fragment" :> Fragment String :> Get '[JSON] Person
124 :<|> "fragment" :> Fragment String :> Get '[JSON] Person
@@ -143,6 +153,7 @@ getCapture :: String -> ClientM Person
143getCaptureAll :: [String] -> ClientM [Person]
153getCaptureAll :: [String] -> ClientM [Person]
144getBody :: Person -> ClientM Person
154getBody :: Person -> ClientM Person
145getQueryParam :: Maybe String -> ClientM Person
155getQueryParam :: Maybe String -> ClientM Person
156+getQueryParamBinary :: Maybe UrlEncodedByteString -> HTTP.Method -> ClientM Response
146getQueryParams :: [String] -> ClientM [Person]
157getQueryParams :: [String] -> ClientM [Person]
147getQueryFlag :: Bool -> ClientM Bool
158getQueryFlag :: Bool -> ClientM Bool
148getFragment :: ClientM Person
159getFragment :: ClientM Person
@@ -167,6 +178,7 @@ getRoot
167 :<|> getCaptureAll
178 :<|> getCaptureAll
168 :<|> getBody
179 :<|> getBody
169 :<|> getQueryParam
180 :<|> getQueryParam
181+ :<|> getQueryParamBinary
170 :<|> getQueryParams
182 :<|> getQueryParams
171 :<|> getQueryFlag
183 :<|> getQueryFlag
172 :<|> getFragment
184 :<|> getFragment
@@ -194,6 +206,13 @@ server = serve api (
194 Just "alice" -> return alice
206 Just "alice" -> return alice
195 Just n -> throwError $ ServerError 400 (n ++ " not found") "" []
207 Just n -> throwError $ ServerError 400 (n ++ " not found") "" []
196 Nothing -> throwError $ ServerError 400 "missing parameter" "" [])
208 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)
214+ $ Wai.queryString request
197 :<|> (\ names -> return (zipWith Person names [0..]))
216 :<|> (\ names -> return (zipWith Person names [0..]))
198 :<|> return
217 :<|> return
199 :<|> return alice
218 :<|> return alice
@@ -310,3 +329,12 @@ pathGen = fmap NonEmpty path
310 filter (not . (`elem` ("?%[]/#;" :: String))) $
329 filter (not . (`elem` ("?%[]/#;" :: String))) $
311 filter isPrint $
330 filter isPrint $
312 map chr [0..127]
331 map chr [0..127]
333+newtype UrlEncodedByteString = UrlEncodedByteString { unUrlEncodedByteString :: ByteString }
335+instance ToHttpApiData UrlEncodedByteString where
336+ toEncodedUrlPiece = byteString . HTTP.urlEncode True . unUrlEncodedByteString
337+ toUrlPiece = decodeUtf8 . HTTP.urlEncode True . unUrlEncodedByteString
339+instance FromHttpApiData UrlEncodedByteString where
340+ parseUrlPiece = pure . UrlEncodedByteString . HTTP.urlDecode True . encodeUtf8