@@ -1,33 +1,34 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE ConstraintKinds #-}
33{-# LANGUAGE DataKinds #-}
44{-# LANGUAGE DeriveDataTypeable #-}
55{-# LANGUAGE FlexibleContexts #-}
66{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE InstanceSigs #-}
78{-# LANGUAGE MultiParamTypeClasses #-}
89{-# LANGUAGE OverloadedStrings #-}
910{-# LANGUAGE PolyKinds #-}
11+{-# LANGUAGE QuantifiedConstraints #-}
1012{-# LANGUAGE RankNTypes #-}
1113{-# LANGUAGE ScopedTypeVariables #-}
1214{-# LANGUAGE TupleSections #-}
15+{-# LANGUAGE TypeApplications #-}
1316{-# LANGUAGE TypeFamilies #-}
1417{-# LANGUAGE TypeOperators #-}
1518{-# LANGUAGE UndecidableInstances #-}
1619
17−#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802
18−#define HAS_TYPE_ERROR
19−#endif
20−
21−#include "overlapping-compat.h"
22−
2320module Servant.Server.Internal
2421 ( module Servant.Server.Internal
2522 , module Servant.Server.Internal.BasicAuth
2623 , module Servant.Server.Internal.Context
24+ , module Servant.Server.Internal.Delayed
25+ , module Servant.Server.Internal.DelayedIO
26+ , module Servant.Server.Internal.ErrorFormatter
2727 , module Servant.Server.Internal.Handler
2828 , module Servant.Server.Internal.Router
29+ , module Servant.Server.Internal.RouteResult
2930 , module Servant.Server.Internal.RoutingApplication
30− , module Servant.Server.Internal.ServantErr
31+ , module Servant.Server.Internal.ServerError
3132 ) where
3233
3334import Control.Monad
@@ -40,12 +41,11 @@ import qualified Data.ByteString as B
4041import qualified Data.ByteString.Builder as BB
4142import qualified Data.ByteString.Char8 as BC8
4243import qualified Data.ByteString.Lazy as BL
44+import Data.Constraint (Constraint, Dict(..))
4345import Data.Either
4446 (partitionEithers)
4547import Data.Maybe
4648 (fromMaybe, isNothing, mapMaybe, maybeToList)
47−import Data.Semigroup
48− ((<>))
4949import Data.String
5050 (IsString (..))
5151import Data.String.Conversions
@@ -54,8 +54,9 @@ import Data.Tagged
5454 (Tagged (..), retag, untag)
5555import qualified Data.Text as T
5656import Data.Typeable
57+import GHC.Generics
5758import GHC.TypeLits
58− (KnownNat, KnownSymbol, natVal, symbolVal)
59+ (KnownNat, KnownSymbol, TypeError, symbolVal)
5960import qualified Network.HTTP.Media as NHM
6061import Network.HTTP.Types hiding
6162 (Header, ResponseHeaders)
@@ -63,42 +64,52 @@ import Network.Socket
6364 (SockAddr)
6465import Network.Wai
6566 (Application, Request, httpVersion, isSecure, lazyRequestBody,
66− rawQueryString, remoteHost, requestHeaders, requestMethod,
67− responseLBS, responseStream, vault)
67+ queryString, remoteHost, getRequestBodyChunk, requestHeaders,
68+ requestMethod, responseLBS, responseStream, vault)
6869import Prelude ()
6970import Prelude.Compat
7071import Servant.API
71− ((:<|>) (..), (:>), Accept (..), BasicAuth,
72− BoundaryStrategy (..), Capture', CaptureAll, Description,
73− EmptyAPI, FramingRender (..), Header', If, IsSecure (..),
74− QueryFlag, QueryParam', QueryParams, Raw,
75− ReflectMethod (reflectMethod), RemoteHost, ReqBody',
76− SBool (..), SBoolI (..), Stream, StreamGenerator (..),
77− Summary, ToStreamGenerator (..), Vault, Verb,
78− WithNamedContext)
72+ ((:<|>) (..), (:>), Accept (..), BasicAuth, Capture',
73+ CaptureAll, Description, EmptyAPI, Fragment,
74+ FramingRender (..), FramingUnrender (..), FromSourceIO (..),
75+ Header', If, IsSecure (..), NoContentVerb, QueryFlag,
76+ QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
77+ RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
78+ Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
79+ WithNamedContext, NamedRoutes)
80+import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
7981import Servant.API.ContentTypes
8082 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
81− AllMime, MimeRender (..), canHandleAcceptH)
83+ AllMime, MimeRender (..), MimeUnrender (..), NoContent,
84+ canHandleAcceptH)
8285import Servant.API.Modifiers
8386 (FoldLenient, FoldRequired, RequestArgument,
8487 unfoldRequestArgument)
8588import Servant.API.ResponseHeaders
8689 (GetHeaders, Headers, getHeaders, getResponse)
90+import Servant.API.Status
91+ (statusFromNat)
92+import qualified Servant.Types.SourceT as S
93+import Servant.API.TypeErrors
8794import Web.HttpApiData
88− (FromHttpApiData, parseHeader, parseQueryParam,
89− parseUrlPieceMaybe, parseUrlPieces)
95+ (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
96+ parseUrlPieces)
9097
9198import Servant.Server.Internal.BasicAuth
9299import Servant.Server.Internal.Context
100+import Servant.Server.Internal.Delayed
101+import Servant.Server.Internal.DelayedIO
102+import Servant.Server.Internal.ErrorFormatter
93103import Servant.Server.Internal.Handler
94104import Servant.Server.Internal.Router
105+import Servant.Server.Internal.RouteResult
95106import Servant.Server.Internal.RoutingApplication
96−import Servant.Server.Internal.ServantErr
107+import Servant.Server.Internal.ServerError
97108
98−#ifdef HAS_TYPE_ERROR
99109import GHC.TypeLits
100110 (ErrorMessage (..), TypeError)
101−#endif
111+import Servant.API.TypeLevel
112+ (AtLeastOneFragment, FragmentUnique)
102113
103114class HasServer api context where
104115 type ServerT api (m :: * -> *) :: *
@@ -162,22 +173,31 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
162173-- > server = getBook
163174-- > where getBook :: Text -> Handler Book
164175-- > getBook isbn = ...
165−instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
176+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
177+ , HasServer api context, SBoolI (FoldLenient mods)
178+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
179+ )
166180 => HasServer (Capture' mods capture a :> api) context where
167181
168182 type ServerT (Capture' mods capture a :> api) m =
169− a -> ServerT api m
183+ If (FoldLenient mods) (Either String a) a -> ServerT api m
170184
171185 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
172186
173187 route Proxy context d =
174− CaptureRouter $
188+ CaptureRouter [hint] $
175189 route (Proxy :: Proxy api)
176190 context
177− (addCapture d $ \ txt -> case parseUrlPieceMaybe txt of
178− Nothing -> delayedFail err400
179− Just v -> return v
180− )
191+ (addCapture d $ \ txt -> withRequest $ \ request ->
192+ case ( sbool :: SBool (FoldLenient mods)
193+ , parseUrlPiece txt :: Either T.Text a) of
194+ (SFalse, Left e) -> delayedFail $ formatError rep request $ cs e
195+ (SFalse, Right v) -> return v
196+ (STrue, piece) -> return $ (either (Left . cs) Right) piece)
197+ where
198+ rep = typeRep (Proxy :: Proxy Capture')
199+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
200+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
181201
182202-- | If you use 'CaptureAll' in one of the endpoints for your API,
183203-- this automatically requires your server-side handler to be a
@@ -196,7 +216,10 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
196216-- > server = getSourceFile
197217-- > where getSourceFile :: [Text] -> Handler Book
198218-- > getSourceFile pathSegments = ...
199−instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
219+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
220+ , HasServer api context
221+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
222+ )
200223 => HasServer (CaptureAll capture a :> api) context where
201224
202225 type ServerT (CaptureAll capture a :> api) m =
@@ -205,14 +228,18 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
205228 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
206229
207230 route Proxy context d =
208− CaptureAllRouter $
231+ CaptureAllRouter [hint] $
209232 route (Proxy :: Proxy api)
210233 context
211− (addCapture d $ \ txts -> case parseUrlPieces txts of
212− Left _ -> delayedFail err400
213− Right v -> return v
234+ (addCapture d $ \ txts -> withRequest $ \ request ->
235+ case parseUrlPieces txts of
236+ Left e -> delayedFail $ formatError rep request $ cs e
237+ Right v -> return v
214238 )
215−
239+ where
240+ rep = typeRep (Proxy :: Proxy CaptureAll)
241+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
242+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
216243
217244allowedMethodHead :: Method -> Request -> Bool
218245allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -232,10 +259,10 @@ methodCheck method request
232259-- body check is no longer an option. However, we now run the accept
233260-- check before the body check and can therefore afford to make it
234261-- recoverable.
235−acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
262+acceptCheck :: (AllMime list) => Proxy list -> AcceptHeader -> DelayedIO ()
236263acceptCheck proxy accH
237− | canHandleAcceptH proxy (AcceptHeader accH) = return ()
238− | otherwise = delayedFail err406
264+ | canHandleAcceptH proxy accH = return ()
265+ | otherwise = delayedFail err406
239266
240267methodRouter :: (AllCTRender ctypes a)
241268 => (b -> ([(HeaderName, B.ByteString)], a))
@@ -245,18 +272,29 @@ methodRouter :: (AllCTRender ctypes a)
245272methodRouter splitHeaders method proxy status action = leafRouter route'
246273 where
247274 route' env request respond =
248− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
275+ let accH = getAcceptHeader request
249276 in runAction (action `addMethodCheck` methodCheck method request
250277 `addAcceptCheck` acceptCheck proxy accH
251278 ) env request respond $ \ output -> do
252279 let (headers, b) = splitHeaders output
253− case handleAcceptH proxy (AcceptHeader accH) b of
280+ case handleAcceptH proxy accH b of
254281 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
255282 Just (contentT, body) ->
256283 let bdy = if allowedMethodHead method request then "" else body
257284 in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy
258285
259−instance OVERLAPPABLE_
286+noContentRouter :: Method
287+ -> Status
288+ -> Delayed env (Handler b)
289+ -> Router env
290+noContentRouter method status action = leafRouter route'
291+ where
292+ route' env request respond =
293+ runAction (action `addMethodCheck` methodCheck method request)
294+ env request respond $ \ _output ->
295+ Route $ responseLBS status [] ""
296+
297+instance {-# OVERLAPPABLE #-}
260298 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
261299 ) => HasServer (Verb method status ctypes a) context where
262300
@@ -265,9 +303,9 @@ instance OVERLAPPABLE_
265303
266304 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
267305 where method = reflectMethod (Proxy :: Proxy method)
268− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
306+ status = statusFromNat (Proxy :: Proxy status)
269307
270−instance OVERLAPPING_
308+instance {-# OVERLAPPING #-}
271309 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
272310 , GetHeaders (Headers h a)
273311 ) => HasServer (Verb method status ctypes (Headers h a)) context where
@@ -277,37 +315,46 @@ instance OVERLAPPING_
277315
278316 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
279317 where method = reflectMethod (Proxy :: Proxy method)
280− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
318+ status = statusFromNat (Proxy :: Proxy status)
319+
320+instance (ReflectMethod method) =>
321+ HasServer (NoContentVerb method) context where
322+
323+ type ServerT (NoContentVerb method) m = m NoContent
324+ hoistServerWithContext _ _ nt s = nt s
281325
326+ route Proxy _ = noContentRouter method status204
327+ where method = reflectMethod (Proxy :: Proxy method)
282328
283−instance OVERLAPPABLE_
284− ( MimeRender ctype a, ReflectMethod method, KnownNat status,
285− FramingRender framing ctype, ToStreamGenerator b a
286− ) => HasServer (Stream method status framing ctype b) context where
329+instance {-# OVERLAPPABLE #-}
330+ ( MimeRender ctype chunk, ReflectMethod method, KnownNat status,
331+ FramingRender framing, ToSourceIO chunk a
332+ ) => HasServer (Stream method status framing ctype a) context where
287333
288− type ServerT (Stream method status framing ctype b) m = m b
334+ type ServerT (Stream method status framing ctype a) m = m a
289335 hoistServerWithContext _ _ nt s = nt s
290336
291337 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
292338 where method = reflectMethod (Proxy :: Proxy method)
293− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
339+ status = statusFromNat (Proxy :: Proxy status)
340+
294341
295−instance OVERLAPPING_
296− ( MimeRender ctype a, ReflectMethod method, KnownNat status,
297− FramingRender framing ctype, ToStreamGenerator b a,
298− GetHeaders (Headers h b)
299− ) => HasServer (Stream method status framing ctype (Headers h b)) context where
342+instance {-# OVERLAPPING #-}
343+ ( MimeRender ctype chunk, ReflectMethod method, KnownNat status,
344+ FramingRender framing, ToSourceIO chunk a,
345+ GetHeaders (Headers h a)
346+ ) => HasServer (Stream method status framing ctype (Headers h a)) context where
300347
301− type ServerT (Stream method status framing ctype (Headers h b)) m = m (Headers h b)
348+ type ServerT (Stream method status framing ctype (Headers h a)) m = m (Headers h a)
302349 hoistServerWithContext _ _ nt s = nt s
303350
304351 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
305352 where method = reflectMethod (Proxy :: Proxy method)
306− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
353+ status = statusFromNat (Proxy :: Proxy status)
307354
308355
309−streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenerator b a) =>
310− (c -> ([(HeaderName, B.ByteString)], b))
356+streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
357+ (c -> ([(HeaderName, B.ByteString)], a))
311358 -> Method
312359 -> Status
313360 -> Proxy framing
@@ -315,7 +362,7 @@ streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenera
315362 -> Delayed env (Handler c)
316363 -> Router env
317364streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRouter $ \env request respond ->
318− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
365+ let AcceptHeader accH = getAcceptHeader request
319366 cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
320367 accCheck = when (isNothing cmediatype) $ delayedFail err406
321368 contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
@@ -323,28 +370,19 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
323370 `addAcceptCheck` accCheck
324371 ) env request respond $ \ output ->
325372 let (headers, fa) = splitHeaders output
326− k = getStreamGenerator . toStreamGenerator $ fa in
327− Route $ responseStream status (contentHeader : headers) $ \write flush -> do
328− write . BB.lazyByteString $ header framingproxy ctypeproxy
329− case boundary framingproxy ctypeproxy of
330− BoundaryStrategyBracket f ->
331− let go x = let bs = mimeRender ctypeproxy x
332− (before, after) = f bs
333− in write ( BB.lazyByteString before
334− <> BB.lazyByteString bs
335− <> BB.lazyByteString after) >> flush
336− in k go go
337− BoundaryStrategyIntersperse sep -> k
338− (\x -> do
339− write . BB.lazyByteString . mimeRender ctypeproxy $ x
340− flush)
341− (\x -> do
342− write . (BB.lazyByteString sep <>) . BB.lazyByteString . mimeRender ctypeproxy $ x
343− flush)
344− BoundaryStrategyGeneral f ->
345− let go = (>> flush) . write . BB.lazyByteString . f . mimeRender ctypeproxy
346− in k go go
347− write . BB.lazyByteString $ trailer framingproxy ctypeproxy
373+ sourceT = toSourceIO fa
374+ S.SourceT kStepLBS = framingRender framingproxy (mimeRender ctypeproxy :: chunk -> BL.ByteString) sourceT
375+ in Route $ responseStream status (contentHeader : headers) $ \write flush -> do
376+ let loop S.Stop = flush
377+ loop (S.Error err) = fail err -- TODO: throw better error
378+ loop (S.Skip s) = loop s
379+ loop (S.Effect ms) = ms >>= loop
380+ loop (S.Yield lbs s) = do
381+ write (BB.lazyByteString lbs)
382+ flush
383+ loop s
384+
385+ kStepLBS loop
348386
349387-- | If you use 'Header' in one of the endpoints for your API,
350388-- this automatically requires your server-side handler to be a function
@@ -369,6 +407,7 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
369407instance
370408 (KnownSymbol sym, FromHttpApiData a, HasServer api context
371409 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
410+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
372411 )
373412 => HasServer (Header' mods sym a :> api) context where
374413------
@@ -380,6 +419,9 @@ instance
380419 route Proxy context subserver = route (Proxy :: Proxy api) context $
381420 subserver `addHeaderCheck` withRequest headerCheck
382421 where
422+ rep = typeRep (Proxy :: Proxy Header')
423+ formatError = headerParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
424+
383425 headerName :: IsString n => n
384426 headerName = fromString $ symbolVal (Proxy :: Proxy sym)
385427
@@ -390,15 +432,13 @@ instance
390432 mev :: Maybe (Either T.Text a)
391433 mev = fmap parseHeader $ lookup headerName (requestHeaders req)
392434
393− errReq = delayedFailFatal err400
394− { errBody = "Header " <> headerName <> " is required"
395− }
435+ errReq = delayedFailFatal $ formatError rep req
436+ $ "Header " <> headerName <> " is required"
396437
397− errSt e = delayedFailFatal err400
398− { errBody = cs $ "Error parsing header "
399− <> headerName
400− <> " failed: " <> e
401− }
438+ errSt e = delayedFailFatal $ formatError rep req
439+ $ cs $ "Error parsing header "
440+ <> headerName
441+ <> " failed: " <> e
402442
403443-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
404444-- this automatically requires your server-side handler to be a function
@@ -424,6 +464,7 @@ instance
424464instance
425465 ( KnownSymbol sym, FromHttpApiData a, HasServer api context
426466 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
467+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
427468 )
428469 => HasServer (QueryParam' mods sym a :> api) context where
429470------
@@ -433,9 +474,12 @@ instance
433474 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
434475
435476 route Proxy context subserver =
436− let querytext req = parseQueryText $ rawQueryString req
477+ let querytext = queryToQueryText . queryString
437478 paramname = cs $ symbolVal (Proxy :: Proxy sym)
438479
480+ rep = typeRep (Proxy :: Proxy QueryParam')
481+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
482+
439483 parseParam :: Request -> DelayedIO (RequestArgument mods a)
440484 parseParam req =
441485 unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
@@ -443,14 +487,12 @@ instance
443487 mev :: Maybe (Either T.Text a)
444488 mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
445489
446− errReq = delayedFailFatal err400
447− { errBody = cs $ "Query parameter " <> paramname <> " is required"
448− }
490+ errReq = delayedFailFatal $ formatError rep req
491+ $ cs $ "Query parameter " <> paramname <> " is required"
449492
450− errSt e = delayedFailFatal err400
451− { errBody = cs $ "Error parsing query parameter "
452− <> paramname <> " failed: " <> e
453− }
493+ errSt e = delayedFailFatal $ formatError rep req
494+ $ cs $ "Error parsing query parameter "
495+ <> paramname <> " failed: " <> e
454496
455497 delayed = addParameterCheck subserver . withRequest $ \req ->
456498 parseParam req
@@ -476,7 +518,8 @@ instance
476518-- > server = getBooksBy
477519-- > where getBooksBy :: [Text] -> Handler [Book]
478520-- > getBooksBy authors = ...return all books by these authors...
479−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
521+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
522+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters)
480523 => HasServer (QueryParams sym a :> api) context where
481524
482525 type ServerT (QueryParams sym a :> api) m =
@@ -487,21 +530,23 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
487530 route Proxy context subserver = route (Proxy :: Proxy api) context $
488531 subserver `addParameterCheck` withRequest paramsCheck
489532 where
533+ rep = typeRep (Proxy :: Proxy QueryParams)
534+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
535+
490536 paramname = cs $ symbolVal (Proxy :: Proxy sym)
491537 paramsCheck req =
492538 case partitionEithers $ fmap parseQueryParam params of
493539 ([], parsed) -> return parsed
494− (errs, _) -> delayedFailFatal err400
495− { errBody = cs $ "Error parsing query parameter(s) "
496− <> paramname <> " failed: "
497− <> T.intercalate ", " errs
498− }
540+ (errs, _) -> delayedFailFatal $ formatError rep req
541+ $ cs $ "Error parsing query parameter(s) "
542+ <> paramname <> " failed: "
543+ <> T.intercalate ", " errs
499544 where
500545 params :: [T.Text]
501546 params = mapMaybe snd
502547 . filter (looksLikeParam . fst)
503− . parseQueryText
504− . rawQueryString
548+ . queryToQueryText
549+ . queryString
505550 $ req
506551
507552 looksLikeParam name = name == paramname || name == (paramname <> "[]")
@@ -527,7 +572,7 @@ instance (KnownSymbol sym, HasServer api context)
527572 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
528573
529574 route Proxy context subserver =
530− let querytext r = parseQueryText $ rawQueryString r
575+ let querytext = queryToQueryText . queryString
531576 param r = case lookup paramname (querytext r) of
532577 Just Nothing -> True -- param is there, with no value
533578 Just (Just v) -> examine v -- param with a value
@@ -569,7 +614,7 @@ instance HasServer Raw context where
569614-- The @Content-Type@ header is inspected, and the list provided is used to
570615-- attempt deserialization. If the request does not have a @Content-Type@
571616-- header, it is treated as @application/octet-stream@ (as specified in
572−-- <http://tools.ietf.org/html/rfc7231#section-3.1.1.5 RFC7231>.
617+-- [RFC 7231 section 3.1.1.5](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)).
573618-- This lets servant worry about extracting it from the request and turning
574619-- it into a value of the type you specify.
575620--
@@ -585,6 +630,7 @@ instance HasServer Raw context where
585630-- > where postBook :: Book -> Handler Book
586631-- > postBook book = ...insert into your db...
587632instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
633+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
588634 ) => HasServer (ReqBody' mods list a :> api) context where
589635
590636 type ServerT (ReqBody' mods list a :> api) m =
@@ -596,6 +642,9 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
596642 = route (Proxy :: Proxy api) context $
597643 addBodyCheck subserver ctCheck bodyCheck
598644 where
645+ rep = typeRep (Proxy :: Proxy ReqBody')
646+ formatError = bodyParserErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
647+
599648 -- Content-Type check, we only lookup we can try to parse the request body
600649 ctCheck = withRequest $ \ request -> do
601650 -- See HTTP RFC 2616, section 7.2.1
@@ -614,9 +663,34 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
614663 case sbool :: SBool (FoldLenient mods) of
615664 STrue -> return mrqbody
616665 SFalse -> case mrqbody of
617− Left e -> delayedFailFatal err400 { errBody = cs e }
666+ Left e -> delayedFailFatal $ formatError rep request e
618667 Right v -> return v
619668
669+instance
670+ ( FramingUnrender framing, FromSourceIO chunk a, MimeUnrender ctype chunk
671+ , HasServer api context
672+ ) => HasServer (StreamBody' mods framing ctype a :> api) context
673+ where
674+ type ServerT (StreamBody' mods framing ctype a :> api) m = a -> ServerT api m
675+
676+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
677+
678+ route Proxy context subserver = route (Proxy :: Proxy api) context $
679+ addBodyCheck subserver ctCheck bodyCheck
680+ where
681+ ctCheck :: DelayedIO (SourceIO chunk -> a)
682+ -- TODO: do content-type check
683+ ctCheck = return fromSourceIO
684+
685+ bodyCheck :: (SourceIO chunk -> a) -> DelayedIO a
686+ bodyCheck fromRS = withRequest $ \req -> do
687+ let mimeUnrender' = mimeUnrender (Proxy :: Proxy ctype) :: BL.ByteString -> Either String chunk
688+ let framingUnrender' = framingUnrender (Proxy :: Proxy framing) mimeUnrender' :: SourceIO B.ByteString -> SourceIO chunk
689+ let body = getRequestBodyChunk req
690+ let rs = S.fromAction B.null body
691+ let rs' = fromRS $ framingUnrender' rs
692+ return rs'
693+
620694-- | Make sure the incoming request starts with @"/path"@, strip it and
621695-- pass the rest of the request path to @api@.
622696instance (KnownSymbol path, HasServer api context) => HasServer (path :> api) context where
@@ -681,12 +755,12 @@ data EmptyServer = EmptyServer deriving (Typeable, Eq, Show, Bounded, Enum)
681755emptyServer :: ServerT EmptyAPI m
682756emptyServer = Tagged EmptyServer
683757
684−-- | The server for an `EmptyAPI` is `emptyAPIServer`.
758+-- | The server for an `EmptyAPI` is `emptyServer`.
685759--
686760-- > type MyApi = "nothing" :> EmptyApi
687761-- >
688762-- > server :: Server MyApi
689−-- > server = emptyAPIServer
763+-- > server = emptyServer
690764instance HasServer EmptyAPI context where
691765 type ServerT EmptyAPI m = Tagged m EmptyServer
692766
@@ -715,7 +789,10 @@ instance ( KnownSymbol realm
715789-- * helpers
716790
717791ct_wildcard :: B.ByteString
718−ct_wildcard = "*" <> "/" <> "*" -- Because CPP
792+ct_wildcard = "*" <> "/" <> "*"
793+
794+getAcceptHeader :: Request -> AcceptHeader
795+getAcceptHeader = AcceptHeader . fromMaybe ct_wildcard . lookup hAccept . requestHeaders
719796
720797-- * General Authentication
721798
@@ -740,39 +817,15 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
740817 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
741818
742819-------------------------------------------------------------------------------
743−-- TypeError helpers
820+-- Custom type errors
744821-------------------------------------------------------------------------------
745822
746−#ifdef HAS_TYPE_ERROR
747−-- | This instance catches mistakes when there are non-saturated
748−-- type applications on LHS of ':>'.
749−--
750−-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
751−-- ...
752−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
753−-- ...Maybe you haven't applied enough arguments to
754−-- ...Capture' '[] "foo"
755−-- ...
756−--
757−-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
758−-- ...
759−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
760−-- ...Maybe you haven't applied enough arguments to
761−-- ...Capture' '[] "foo"
762−-- ...
763−--
764−instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
823+-- Erroring instance for 'HasServer' when a combinator is not fully applied
824+instance TypeError (PartialApplication HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
765825 where
766− type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
767− -- it doens't really matter what sub route we peak
768− route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
769− hoistServerWithContext _ _ _ = id
770−
771−-- Cannot have TypeError here, otherwise use of this symbol will error :)
772−type HasServerArrowKindError arr =
773− 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
774− ':$$: 'Text "Maybe you haven't applied enough arguments to"
775− ':$$: 'ShowType arr
826+ type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
827+ route = error "unreachable"
828+ hoistServerWithContext _ _ _ _ = error "unreachable"
776829
777830-- | This instance prevents from accidentally using '->' instead of ':>'
778831--
@@ -806,7 +859,103 @@ type HasServerArrowTypeError a b =
806859 ':$$: 'ShowType a
807860 ':$$: 'Text "and"
808861 ':$$: 'ShowType b
809−#endif
862+
863+-- Erroring instances for 'HasServer' for unknown API combinators
864+
865+-- XXX: This omits the @context@ parameter, e.g.:
866+--
867+-- "There is no instance for HasServer (Bool :> …)". Do we care ?
868+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub HasServer ty) => HasServer (ty :> sub) context
869+
870+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
871+
872+-- | Ignore @'Fragment'@ in server handlers.
873+-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
874+--
875+-- Example:
876+--
877+-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
878+-- >
879+-- > server :: Server MyApi
880+-- > server = getBooks
881+-- > where getBooks :: Handler [Book]
882+-- > getBooks = ...return all books...
883+instance (AtLeastOneFragment api, FragmentUnique (Fragment a1 :> api), HasServer api context)
884+ => HasServer (Fragment a1 :> api) context where
885+ type ServerT (Fragment a1 :> api) m = ServerT api m
886+
887+ route _ = route (Proxy :: Proxy api)
888+
889+ hoistServerWithContext _ = hoistServerWithContext (Proxy :: Proxy api)
810890
811891-- $setup
812892-- >>> import Servant
893+
894+-- | A type that specifies that an API record contains a server implementation.
895+data AsServerT (m :: * -> *)
896+instance GenericMode (AsServerT m) where
897+ type AsServerT m :- api = ServerT api m
898+
899+type AsServer = AsServerT Handler
900+
901+
902+-- | Set of constraints required to convert to / from vanilla server types.
903+type GServerConstraints api m =
904+ ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m
905+ , GServantProduct (Rep (api (AsServerT m)))
906+ )
907+
908+-- | This class is a necessary evil: in the implementation of 'HasServer' for
909+-- @'NamedRoutes' api@, we essentially need the quantified constraint @forall
910+-- m. 'GServerConstraints' m@ to hold.
911+--
912+-- We cannot require do that directly as the definition of 'GServerConstraints'
913+-- contains type family applications ('Rep' and 'ServerT'). The trick is to hide
914+-- those type family applications behind a typeclass providing evidence for
915+-- @'GServerConstraints' api m@ in the form of a dictionary, and require that
916+-- @forall m. 'GServer' api m@ instead.
917+--
918+-- Users shouldn't have to worry about this class, as the only possible instance
919+-- is provided in this module for all record APIs.
920+
921+class GServer (api :: * -> *) (m :: * -> *) where
922+ gServerProof :: Dict (GServerConstraints api m)
923+
924+instance
925+ ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m
926+ , GServantProduct (Rep (api (AsServerT m)))
927+ ) => GServer api m where
928+ gServerProof = Dict
929+
930+instance
931+ ( HasServer (ToServantApi api) context
932+ , forall m. Generic (api (AsServerT m))
933+ , forall m. GServer api m
934+ ) => HasServer (NamedRoutes api) context where
935+
936+ type ServerT (NamedRoutes api) m = api (AsServerT m)
937+
938+ route
939+ :: Proxy (NamedRoutes api)
940+ -> Context context
941+ -> Delayed env (api (AsServerT Handler))
942+ -> Router env
943+ route _ ctx delayed =
944+ case gServerProof @api @Handler of
945+ Dict -> route (Proxy @(ToServantApi api)) ctx (toServant <$> delayed)
946+
947+ hoistServerWithContext
948+ :: forall m n. Proxy (NamedRoutes api)
949+ -> Proxy context
950+ -> (forall x. m x -> n x)
951+ -> api (AsServerT m)
952+ -> api (AsServerT n)
953+ hoistServerWithContext _ pctx nat server =
954+ case (gServerProof @api @m, gServerProof @api @n) of
955+ (Dict, Dict) ->
956+ fromServant servantSrvN
957+ where
958+ servantSrvM :: ServerT (ToServantApi api) m =
959+ toServant server
960+ servantSrvN :: ServerT (ToServantApi api) n =
961+ hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM