@@ -31,9 +31,11 @@ import Network.Wai ( Response, Request, ResponseReceived, Application
31import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
31import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
32 , MatrixParams, MatrixParam, MatrixFlag,
32 , MatrixParams, MatrixParam, MatrixFlag,
33 , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..))
33 , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..))
34−import Servant.Server.ContentTypes (AllCTRender(..), AcceptHeader(..))
34+import Servant.Server.ContentTypes ( AllCTRender(..), AcceptHeader(..)
35+ , AllCTUnrender(..) )
35import Servant.Common.Text (FromText, fromText)
36import Servant.Common.Text (FromText, fromText)
37data ReqBodyState = Uncalled
39data ReqBodyState = Uncalled
38 | Called !B.ByteString
40 | Called !B.ByteString
39 | Done !B.ByteString
41 | Done !B.ByteString
@@ -175,8 +177,8 @@ class HasServer layout where
175-- represented by @a@ and if it fails tries @b@. You must provide a request
177-- represented by @a@ and if it fails tries @b@. You must provide a request
176-- handler for each route.
178-- handler for each route.
178−-- > type MyApi = "books" :> Get [Book] -- GET /books
180+-- > type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
179−-- > :<|> "books" :> ReqBody Book :> Post Book -- POST /books
181+-- > :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
181-- > server :: Server MyApi
183-- > server :: Server MyApi
182-- > server = listAllBooks :<|> postBook
184-- > server = listAllBooks :<|> postBook
@@ -207,7 +209,7 @@ captured _ = fromText
208-- Example:
210-- Example:
210−-- > type MyApi = "books" :> Capture "isbn" Text :> Get Book
212+-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
212-- > server :: Server MyApi
214-- > server :: Server MyApi
213-- > server = getBook
215-- > server = getBook
@@ -265,10 +267,12 @@ instance HasServer Delete where
265-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
267-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
266-- to quickly fail if some conditions are not met.
268-- to quickly fail if some conditions are not met.
268−-- If successfully returning a value, we just require that its type has
270+-- If successfully returning a value, we use the type-level list, combined
269−-- a 'ToJSON' instance and servant takes care of encoding it for you,
271+-- with the request's @Accept@ header, to encode the value for you
270−-- yielding status code 200 along the way.
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
271−instance ( AllCTRender ctypes a, ToJSON a
275+instance ( AllCTRender ctypes a
272 ) => HasServer (Get ctypes a) where
276 ) => HasServer (Get ctypes a) where
273 type Server (Get ctypes a) = EitherT (Int, String) IO a
277 type Server (Get ctypes a) = EitherT (Int, String) IO a
274 route Proxy action request respond
278 route Proxy action request respond
@@ -301,7 +305,7 @@ instance ( AllCTRender ctypes a, ToJSON a
301-- > deriving (Eq, Show, FromText, ToText)
305-- > deriving (Eq, Show, FromText, ToText)
303-- > -- GET /view-my-referer
307-- > -- 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
306-- > server :: Server MyApi
310-- > server :: Server MyApi
307-- > server = viewReferer
311-- > server = viewReferer
@@ -327,11 +331,13 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
327-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
331-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
328-- to quickly fail if some conditions are not met.
332-- to quickly fail if some conditions are not met.
330−-- If successfully returning a value, we just require that its type has
334+-- If successfully returning a value, we use the type-level list, combined
331−-- a 'ToJSON' instance and servant takes care of encoding it for you,
335+-- with the request's @Accept@ header, to encode the value for you
332−-- yielding status code 201 along the way.
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
333−instance ( AllCTRender ctypes a, ToJSON a
339+instance ( AllCTRender ctypes a
334− )=> HasServer (Post ctypes a) where
340+ ) => HasServer (Post ctypes a) where
335 type Server (Post ctypes a) = EitherT (Int, String) IO a
341 type Server (Post ctypes a) = EitherT (Int, String) IO a
337 route Proxy action request respond
343 route Proxy action request respond
@@ -358,10 +364,12 @@ instance ( AllCTRender ctypes a, ToJSON a
358-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
364-- failure. You can quite handily use 'Control.Monad.Trans.EitherT.left'
359-- to quickly fail if some conditions are not met.
365-- to quickly fail if some conditions are not met.
361−-- If successfully returning a value, we just require that its type has
367+-- If successfully returning a value, we use the type-level list, combined
362−-- a 'ToJSON' instance and servant takes care of encoding it for you,
368+-- with the request's @Accept@ header, to encode the value for you
363−-- yielding status code 200 along the way.
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
364−instance ( AllCTRender ctypes a, ToJSON a
372+instance ( AllCTRender ctypes a
365 ) => HasServer (Put ctypes a) where
373 ) => HasServer (Put ctypes a) where
366 type Server (Put ctypes a) = EitherT (Int, String) IO a
374 type Server (Put ctypes a) = EitherT (Int, String) IO a
@@ -423,7 +431,7 @@ instance (Typeable a, ToJSON a) => HasServer (Patch a) where
424-- Example:
432-- Example:
426−-- > type MyApi = "books" :> QueryParam "author" Text :> Get [Book]
434+-- > type MyApi = "books" :> QueryParam "author" Text :> Get '[JSON] [Book]
428-- > server :: Server MyApi
436-- > server :: Server MyApi
429-- > server = getBooksBy
437-- > server = getBooksBy
@@ -462,7 +470,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
463-- Example:
471-- Example:
465−-- > type MyApi = "books" :> QueryParams "authors" Text :> Get [Book]
473+-- > type MyApi = "books" :> QueryParams "authors" Text :> Get '[JSON] [Book]
467-- > server :: Server MyApi
475-- > server :: Server MyApi
468-- > server = getBooksBy
476-- > server = getBooksBy
@@ -495,7 +503,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout)
496-- Example:
504-- Example:
498−-- > type MyApi = "books" :> QueryFlag "published" :> Get [Book]
506+-- > type MyApi = "books" :> QueryFlag "published" :> Get '[JSON] [Book]
500-- > server :: Server MyApi
508-- > server :: Server MyApi
501-- > server = getBooks
509-- > server = getBooks
@@ -654,27 +662,38 @@ instance HasServer Raw where
654-- | If you use 'ReqBody' in one of the endpoints for your API,
662-- | If you use 'ReqBody' in one of the endpoints for your API,
655-- this automatically requires your server-side handler to be a function
663-- this automatically requires your server-side handler to be a function
656-- that takes an argument of the type specified by 'ReqBody'.
664-- 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@.
657-- This lets servant worry about extracting it from the request and turning
668-- This lets servant worry about extracting it from the request and turning
658-- it into a value of the type you specify.
669-- it into a value of the type you specify.
660-- All it asks is for a 'FromJSON' instance.
672-- All it asks is for a 'FromJSON' instance.
662-- Example:
674-- Example:
664−-- > type MyApi = "books" :> ReqBody Book :> Post Book
676+-- > type MyApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book
666-- > server :: Server MyApi
678-- > server :: Server MyApi
667-- > server = postBook
679-- > server = postBook
668-- > where postBook :: Book -> EitherT (Int, String) IO Book
680-- > where postBook :: Book -> EitherT (Int, String) IO Book
669-- > postBook book = ...insert into your db...
681-- > postBook book = ...insert into your db...
670−instance (FromJSON a, HasServer sublayout)
682+instance ( AllCTUnrender list a, HasServer sublayout
671− => HasServer (ReqBody a :> sublayout) where
683+ ) => HasServer (ReqBody list a :> sublayout) where
673− type Server (ReqBody a :> sublayout) =
685+ type Server (ReqBody list a :> sublayout) =
674 a -> Server sublayout
686 a -> Server sublayout
676 route Proxy subserver request respond = do
688 route Proxy subserver request respond = do
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)
677− mrqbody <- eitherDecode' <$> lazyRequestBody request
696+ <$> lazyRequestBody request
678 case mrqbody of
697 case mrqbody of
679 Left e -> respond . failWith $ InvalidBody e
698 Left e -> respond . failWith $ InvalidBody e
680 Right v -> route (Proxy :: Proxy sublayout) (subserver v) request respond
699 Right v -> route (Proxy :: Proxy sublayout) (subserver v) request respond