@@ -31,9 +31,11 @@ import Network.Wai ( Response, Request, ResponseReceived, Application
3131import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
3232 , MatrixParams, MatrixParam, MatrixFlag,
3333 , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..))
34−import Servant.Server.ContentTypes (AllCTRender(..), AcceptHeader(..))
34+import Servant.Server.ContentTypes ( AllCTRender(..), AcceptHeader(..)
35+ , AllCTUnrender(..) )
3536import Servant.Common.Text (FromText, fromText)
3637
38+
3739data ReqBodyState = Uncalled
3840 | Called !B.ByteString
3941 | Done !B.ByteString
@@ -175,8 +177,8 @@ class HasServer layout where
175177-- represented by @a@ and if it fails tries @b@. You must provide a request
176178-- handler for each route.
177179--
178−-- > type MyApi = "books" :> Get [Book] -- GET /books
179−-- > :<|> "books" :> ReqBody Book :> Post Book -- POST /books
180+-- > type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
181+-- > :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
180182-- >
181183-- > server :: Server MyApi
182184-- > server = listAllBooks :<|> postBook
@@ -207,7 +209,7 @@ captured _ = fromText
207209--
208210-- Example:
209211--
210−-- > type MyApi = "books" :> Capture "isbn" Text :> Get Book
212+-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
211213-- >
212214-- > server :: Server MyApi
213215-- > server = getBook
@@ -265,10 +267,12 @@ instance HasServer Delete where
265267-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
266268-- to quickly fail if some conditions are not met.
267269--
268−-- If successfully returning a value, we just require that its type has
269−-- a 'ToJSON' instance and servant takes care of encoding it for you,
270−-- yielding status code 200 along the way.
271−instance ( AllCTRender ctypes a, ToJSON a
270+-- If successfully returning a value, we use the type-level list, combined
271+-- with the request's @Accept@ header, to encode the value for you
272+-- (returning a status code of 200). If there was no @Accept@ header or it
273+-- was @*/*@, we return encode using the first @Content-Type@ type on the
274+-- list.
275+instance ( AllCTRender ctypes a
272276 ) => HasServer (Get ctypes a) where
273277 type Server (Get ctypes a) = EitherT (Int, String) IO a
274278 route Proxy action request respond
@@ -301,7 +305,7 @@ instance ( AllCTRender ctypes a, ToJSON a
301305-- > deriving (Eq, Show, FromText, ToText)
302306-- >
303307-- > -- GET /view-my-referer
304−-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get Referer
308+-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer
305309-- >
306310-- > server :: Server MyApi
307311-- > server = viewReferer
@@ -327,11 +331,13 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
327331-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
328332-- to quickly fail if some conditions are not met.
329333--
330−-- If successfully returning a value, we just require that its type has
331−-- a 'ToJSON' instance and servant takes care of encoding it for you,
332−-- yielding status code 201 along the way.
333−instance ( AllCTRender ctypes a, ToJSON a
334− )=> HasServer (Post ctypes a) where
334+-- If successfully returning a value, we use the type-level list, combined
335+-- with the request's @Accept@ header, to encode the value for you
336+-- (returning a status code of 201). If there was no @Accept@ header or it
337+-- was @*/*@, we return encode using the first @Content-Type@ type on the
338+-- list.
339+instance ( AllCTRender ctypes a
340+ ) => HasServer (Post ctypes a) where
335341 type Server (Post ctypes a) = EitherT (Int, String) IO a
336342
337343 route Proxy action request respond
@@ -358,10 +364,12 @@ instance ( AllCTRender ctypes a, ToJSON a
358364-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
359365-- to quickly fail if some conditions are not met.
360366--
361−-- If successfully returning a value, we just require that its type has
362−-- a 'ToJSON' instance and servant takes care of encoding it for you,
363−-- yielding status code 200 along the way.
364−instance ( AllCTRender ctypes a, ToJSON a
367+-- If successfully returning a value, we use the type-level list, combined
368+-- with the request's @Accept@ header, to encode the value for you
369+-- (returning a status code of 201). If there was no @Accept@ header or it
370+-- was @*/*@, we return encode using the first @Content-Type@ type on the
371+-- list.
372+instance ( AllCTRender ctypes a
365373 ) => HasServer (Put ctypes a) where
366374 type Server (Put ctypes a) = EitherT (Int, String) IO a
367375
@@ -423,7 +431,7 @@ instance (Typeable a, ToJSON a) => HasServer (Patch a) where
423431--
424432-- Example:
425433--
426−-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
434+-- > type MyApi = "books" :> QueryParam "author" Text :> Get '[JSON] [Book]
427435-- >
428436-- > server :: Server MyApi
429437-- > server = getBooksBy
@@ -462,7 +470,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
462470--
463471-- Example:
464472--
465−-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
473+-- > type MyApi = "books" :> QueryParams "authors" Text :> Get '[JSON] [Book]
466474-- >
467475-- > server :: Server MyApi
468476-- > server = getBooksBy
@@ -495,7 +503,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
495503--
496504-- Example:
497505--
498−-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book]
506+-- > type MyApi = "books" :> QueryFlag "published" :> Get '[JSON] [Book]
499507-- >
500508-- > server :: Server MyApi
501509-- > server = getBooks
@@ -654,27 +662,38 @@ instance HasServer Raw where
654662-- | If you use 'ReqBody' in one of the endpoints for your API,
655663-- this automatically requires your server-side handler to be a function
656664-- that takes an argument of the type specified by 'ReqBody'.
665+-- The @Content-Type@ header is inspected, and the list provided is used to
666+-- attempt deserialization. If the request does not have a @Content-Type@
667+-- header, it is treated as @application/octet-stream@.
657668-- This lets servant worry about extracting it from the request and turning
658669-- it into a value of the type you specify.
659670--
671+--
660672-- All it asks is for a 'FromJSON' instance.
661673--
662674-- Example:
663675--
664−-- > type MyApi = "books" :> ReqBody Book :> Post Book
676+-- > type MyApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book
665677-- >
666678-- > server :: Server MyApi
667679-- > server = postBook
668680-- > where postBook :: Book -> EitherT (Int, String) IO Book
669681-- > postBook book = ...insert into your db...
670−instance (FromJSON a, HasServer sublayout)
671− => HasServer (ReqBody a :> sublayout) where
682+instance ( AllCTUnrender list a, HasServer sublayout
683+ ) => HasServer (ReqBody list a :> sublayout) where
672684
673− type Server (ReqBody a :> sublayout) =
685+ type Server (ReqBody list a :> sublayout) =
674686 a -> Server sublayout
675687
676688 route Proxy subserver request respond = do
677− mrqbody <- eitherDecode' <$> lazyRequestBody request
689+ -- See HTTP RFC 2616, section 7.2.1
690+ -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
691+ -- See also "W3C Internet Media Type registration, consistency of use"
692+ -- http://www.w3.org/2001/tag/2002/0129-mime
693+ let contentTypeH = fromMaybe "application/octet-stream"
694+ $ lookup hContentType $ requestHeaders request
695+ mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
696+ <$> lazyRequestBody request
678697 case mrqbody of
679698 Left e -> respond . failWith $ InvalidBody e
680699 Right v -> route (Proxy :: Proxy sublayout) (subserver v) request respond