@@ -1,49 +1,54 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE CPP #-}
34{-# LANGUAGE DataKinds #-}
45{-# LANGUAGE DeriveDataTypeable #-}
56{-# LANGUAGE FlexibleContexts #-}
67{-# LANGUAGE FlexibleInstances #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE LambdaCase #-}
710{-# LANGUAGE MultiParamTypeClasses #-}
811{-# LANGUAGE OverloadedStrings #-}
912{-# LANGUAGE PolyKinds #-}
13+{-# LANGUAGE QuantifiedConstraints #-}
1014{-# LANGUAGE RankNTypes #-}
1115{-# LANGUAGE ScopedTypeVariables #-}
1216{-# LANGUAGE TupleSections #-}
17+{-# LANGUAGE TypeApplications #-}
1318{-# LANGUAGE TypeFamilies #-}
1419{-# LANGUAGE TypeOperators #-}
1520{-# LANGUAGE UndecidableInstances #-}
1621
17−#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802
18−#define HAS_TYPE_ERROR
19−#endif
20−
2122module Servant.Server.Internal
2223 ( module Servant.Server.Internal
2324 , module Servant.Server.Internal.BasicAuth
2425 , module Servant.Server.Internal.Context
26+ , module Servant.Server.Internal.Delayed
27+ , module Servant.Server.Internal.DelayedIO
28+ , module Servant.Server.Internal.ErrorFormatter
2529 , module Servant.Server.Internal.Handler
2630 , module Servant.Server.Internal.Router
31+ , module Servant.Server.Internal.RouteResult
2732 , module Servant.Server.Internal.RoutingApplication
28− , module Servant.Server.Internal.ServantErr
33+ , module Servant.Server.Internal.ServerError
2934 ) where
3035
3136import Control.Monad
3237 (join, when)
3338import Control.Monad.Trans
34− (liftIO)
39+ (liftIO, lift)
3540import Control.Monad.Trans.Resource
36− (runResourceT)
41+ (runResourceT, ReleaseKey)
42+import Data.Acquire
3743import qualified Data.ByteString as B
3844import qualified Data.ByteString.Builder as BB
3945import qualified Data.ByteString.Char8 as BC8
4046import qualified Data.ByteString.Lazy as BL
47+import Data.Constraint (Constraint, Dict(..))
4148import Data.Either
4249 (partitionEithers)
4350import Data.Maybe
4451 (fromMaybe, isNothing, mapMaybe, maybeToList)
45−import Data.Semigroup
46− ((<>))
4752import Data.String
4853 (IsString (..))
4954import Data.String.Conversions
@@ -52,53 +57,70 @@ import Data.Tagged
5257 (Tagged (..), retag, untag)
5358import qualified Data.Text as T
5459import Data.Typeable
60+import GHC.Generics
5561import GHC.TypeLits
56− (KnownNat, KnownSymbol, natVal, symbolVal)
62+ (KnownNat, KnownSymbol, TypeError, symbolVal)
5763import qualified Network.HTTP.Media as NHM
5864import Network.HTTP.Types hiding
5965 (Header, ResponseHeaders)
6066import Network.Socket
6167 (SockAddr)
6268import Network.Wai
63− (Application, Request, httpVersion, isSecure, lazyRequestBody,
64− rawQueryString, remoteHost, requestHeaders, requestMethod,
65− responseLBS, responseStream, vault)
69+ (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
70+ queryString, remoteHost, getRequestBodyChunk, requestHeaders,
71+ requestMethod, responseLBS, responseStream, vault)
6672import Prelude ()
6773import Prelude.Compat
6874import Servant.API
69− ((:<|>) (..), (:>), Accept (..), BasicAuth,
70− BoundaryStrategy (..), Capture', CaptureAll, Description,
71− EmptyAPI, FramingRender (..), Header', If, IsSecure (..),
72− QueryFlag, QueryParam', QueryParams, Raw,
73− ReflectMethod (reflectMethod), RemoteHost, ReqBody',
74− SBool (..), SBoolI (..), Stream, StreamGenerator (..),
75− Summary, ToStreamGenerator (..), Vault, Verb,
76− WithNamedContext)
75+ ((:<|>) (..), (:>), Accept (..), BasicAuth, Capture',
76+ CaptureAll, Description, EmptyAPI, Fragment,
77+ FramingRender (..), FramingUnrender (..), FromSourceIO (..),
78+ Header', If, IsSecure (..), NoContentVerb, QueryFlag,
79+ QueryParam', QueryParams, Raw, RawM, ReflectMethod (reflectMethod),
80+ RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
81+ Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
82+ WithNamedContext, WithResource, NamedRoutes)
83+import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
7784import Servant.API.ContentTypes
7885 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
79− AllMime, MimeRender (..), canHandleAcceptH)
86+ AllMime, MimeRender (..), MimeUnrender (..), NoContent,
87+ canHandleAcceptH)
8088import Servant.API.Modifiers
8189 (FoldLenient, FoldRequired, RequestArgument,
8290 unfoldRequestArgument)
8391import Servant.API.ResponseHeaders
8492 (GetHeaders, Headers, getHeaders, getResponse)
93+import Servant.API.Status
94+ (statusFromNat)
95+import qualified Servant.Types.SourceT as S
96+import Servant.API.TypeErrors
8597import Web.HttpApiData
86− (FromHttpApiData, parseHeader, parseQueryParam,
87− parseUrlPieceMaybe, parseUrlPieces)
98+ (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
99+ parseUrlPieces)
100+import Data.Kind
101+ (Type)
88102
89103import Servant.Server.Internal.BasicAuth
90104import Servant.Server.Internal.Context
105+import Servant.Server.Internal.Delayed
106+import Servant.Server.Internal.DelayedIO
107+import Servant.Server.Internal.ErrorFormatter
91108import Servant.Server.Internal.Handler
92109import Servant.Server.Internal.Router
110+import Servant.Server.Internal.RouteResult
93111import Servant.Server.Internal.RoutingApplication
94−import Servant.Server.Internal.ServantErr
112+import Servant.Server.Internal.ServerError
95113
96−#ifdef HAS_TYPE_ERROR
97114import GHC.TypeLits
98115 (ErrorMessage (..), TypeError)
99−#endif
116+import Servant.API.TypeLevel
117+ (AtLeastOneFragment, FragmentUnique)
100118
101119class HasServer api context where
120+ -- | The type of a server for this API, given a monad to run effects in.
121+ --
122+ -- Note that the result kind is @*@, so it is /not/ a monad transformer, unlike
123+ -- what the @T@ in the name might suggest.
102124 type ServerT api (m :: * -> *) :: *
103125
104126 route ::
@@ -160,22 +182,31 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
160182-- > server = getBook
161183-- > where getBook :: Text -> Handler Book
162184-- > getBook isbn = ...
163−instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
185+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
186+ , HasServer api context, SBoolI (FoldLenient mods)
187+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
188+ )
164189 => HasServer (Capture' mods capture a :> api) context where
165190
166191 type ServerT (Capture' mods capture a :> api) m =
167− a -> ServerT api m
192+ If (FoldLenient mods) (Either String a) a -> ServerT api m
168193
169194 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
170195
171196 route Proxy context d =
172− CaptureRouter $
197+ CaptureRouter [hint] $
173198 route (Proxy :: Proxy api)
174199 context
175− (addCapture d $ \ txt -> case parseUrlPieceMaybe txt of
176− Nothing -> delayedFail err400
177− Just v -> return v
178− )
200+ (addCapture d $ \ txt -> withRequest $ \ request ->
201+ case ( sbool :: SBool (FoldLenient mods)
202+ , parseUrlPiece txt :: Either T.Text a) of
203+ (SFalse, Left e) -> delayedFail $ formatError rep request $ cs e
204+ (SFalse, Right v) -> return v
205+ (STrue, piece) -> return $ (either (Left . cs) Right) piece)
206+ where
207+ rep = typeRep (Proxy :: Proxy Capture')
208+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
209+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
179210
180211-- | If you use 'CaptureAll' in one of the endpoints for your API,
181212-- this automatically requires your server-side handler to be a
@@ -194,7 +225,10 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
194225-- > server = getSourceFile
195226-- > where getSourceFile :: [Text] -> Handler Book
196227-- > getSourceFile pathSegments = ...
197−instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
228+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
229+ , HasServer api context
230+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
231+ )
198232 => HasServer (CaptureAll capture a :> api) context where
199233
200234 type ServerT (CaptureAll capture a :> api) m =
@@ -203,13 +237,53 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
203237 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
204238
205239 route Proxy context d =
206− CaptureAllRouter $
240+ CaptureAllRouter [hint] $
207241 route (Proxy :: Proxy api)
208242 context
209− (addCapture d $ \ txts -> case parseUrlPieces txts of
210− Left _ -> delayedFail err400
211− Right v -> return v
243+ (addCapture d $ \ txts -> withRequest $ \ request ->
244+ case parseUrlPieces txts of
245+ Left e -> delayedFail $ formatError rep request $ cs e
246+ Right v -> return v
212247 )
248+ where
249+ rep = typeRep (Proxy :: Proxy CaptureAll)
250+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
251+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
252+
253+-- | If you use 'WithResource' in one of the endpoints for your API Servant
254+-- will provide the handler for this endpoint an argument of the specified type.
255+-- The lifespan of this resource will be automatically managed by Servant. This
256+-- resource will be created before the handler starts and it will be destoyed
257+-- after it ends. A new resource is created for each request to the endpoint.
258+
259+-- The creation and destruction are done using a 'Data.Acquire.Acquire'
260+-- provided via server 'Context'.
261+--
262+-- Example
263+--
264+-- > type MyApi = WithResource Handle :> "writeToFile" :> Post '[JSON] NoContent
265+-- >
266+-- > server :: Server MyApi
267+-- > server = writeToFile
268+-- > where writeToFile :: (ReleaseKey, Handle) -> Handler NoContent
269+-- > writeToFile (_, h) = hPutStrLn h "message"
270+--
271+-- In addition to the resource, the handler will also receive a 'ReleaseKey'
272+-- which can be used to deallocate the resource before the end of the request
273+-- if desired.
274+
275+instance (HasServer api ctx, HasContextEntry ctx (Acquire a))
276+ => HasServer (WithResource a :> api) ctx where
277+
278+ type ServerT (WithResource a :> api) m = (ReleaseKey, a) -> ServerT api m
279+
280+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy @api) pc nt . s
281+
282+ route Proxy context d = route (Proxy @api) context (d `addParameterCheck` allocateResource)
283+ where
284+ allocateResource :: DelayedIO (ReleaseKey, a)
285+ allocateResource = DelayedIO $ lift $ allocateAcquire (getContextEntry context)
286+
213287
214288
215289allowedMethodHead :: Method -> Request -> Bool
@@ -230,10 +304,10 @@ methodCheck method request
230304-- body check is no longer an option. However, we now run the accept
231305-- check before the body check and can therefore afford to make it
232306-- recoverable.
233−acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
307+acceptCheck :: (AllMime list) => Proxy list -> AcceptHeader -> DelayedIO ()
234308acceptCheck proxy accH
235− | canHandleAcceptH proxy (AcceptHeader accH) = return ()
236− | otherwise = delayedFail err406
309+ | canHandleAcceptH proxy accH = return ()
310+ | otherwise = delayedFail err406
237311
238312methodRouter :: (AllCTRender ctypes a)
239313 => (b -> ([(HeaderName, B.ByteString)], a))
@@ -243,17 +317,28 @@ methodRouter :: (AllCTRender ctypes a)
243317methodRouter splitHeaders method proxy status action = leafRouter route'
244318 where
245319 route' env request respond =
246− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
320+ let accH = getAcceptHeader request
247321 in runAction (action `addMethodCheck` methodCheck method request
248322 `addAcceptCheck` acceptCheck proxy accH
249323 ) env request respond $ \ output -> do
250324 let (headers, b) = splitHeaders output
251− case handleAcceptH proxy (AcceptHeader accH) b of
325+ case handleAcceptH proxy accH b of
252326 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
253327 Just (contentT, body) ->
254328 let bdy = if allowedMethodHead method request then "" else body
255329 in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy
256330
331+noContentRouter :: Method
332+ -> Status
333+ -> Delayed env (Handler b)
334+ -> Router env
335+noContentRouter method status action = leafRouter route'
336+ where
337+ route' env request respond =
338+ runAction (action `addMethodCheck` methodCheck method request)
339+ env request respond $ \ _output ->
340+ Route $ responseLBS status [] ""
341+
257342instance {-# OVERLAPPABLE #-}
258343 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
259344 ) => HasServer (Verb method status ctypes a) context where
@@ -263,7 +348,7 @@ instance {-# OVERLAPPABLE #-}
263348
264349 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
265350 where method = reflectMethod (Proxy :: Proxy method)
266− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
351+ status = statusFromNat (Proxy :: Proxy status)
267352
268353instance {-# OVERLAPPING #-}
269354 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
@@ -275,37 +360,46 @@ instance {-# OVERLAPPING #-}
275360
276361 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
277362 where method = reflectMethod (Proxy :: Proxy method)
278− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
363+ status = statusFromNat (Proxy :: Proxy status)
364+
365+instance (ReflectMethod method) =>
366+ HasServer (NoContentVerb method) context where
367+
368+ type ServerT (NoContentVerb method) m = m NoContent
369+ hoistServerWithContext _ _ nt s = nt s
279370
371+ route Proxy _ = noContentRouter method status204
372+ where method = reflectMethod (Proxy :: Proxy method)
280373
281374instance {-# OVERLAPPABLE #-}
282− ( MimeRender ctype a, ReflectMethod method, KnownNat status,
283− FramingRender framing ctype, ToStreamGenerator b a
284− ) => HasServer (Stream method status framing ctype b) context where
375+ ( MimeRender ctype chunk, ReflectMethod method, KnownNat status,
376+ FramingRender framing, ToSourceIO chunk a
377+ ) => HasServer (Stream method status framing ctype a) context where
285378
286− type ServerT (Stream method status framing ctype b) m = m b
379+ type ServerT (Stream method status framing ctype a) m = m a
287380 hoistServerWithContext _ _ nt s = nt s
288381
289382 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
290383 where method = reflectMethod (Proxy :: Proxy method)
291− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
384+ status = statusFromNat (Proxy :: Proxy status)
385+
292386
293387instance {-# OVERLAPPING #-}
294− ( MimeRender ctype a, ReflectMethod method, KnownNat status,
295− FramingRender framing ctype, ToStreamGenerator b a,
296− GetHeaders (Headers h b)
297− ) => HasServer (Stream method status framing ctype (Headers h b)) context where
388+ ( MimeRender ctype chunk, ReflectMethod method, KnownNat status,
389+ FramingRender framing, ToSourceIO chunk a,
390+ GetHeaders (Headers h a)
391+ ) => HasServer (Stream method status framing ctype (Headers h a)) context where
298392
299− type ServerT (Stream method status framing ctype (Headers h b)) m = m (Headers h b)
393+ type ServerT (Stream method status framing ctype (Headers h a)) m = m (Headers h a)
300394 hoistServerWithContext _ _ nt s = nt s
301395
302396 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
303397 where method = reflectMethod (Proxy :: Proxy method)
304− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
398+ status = statusFromNat (Proxy :: Proxy status)
305399
306400
307−streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenerator b a) =>
308− (c -> ([(HeaderName, B.ByteString)], b))
401+streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
402+ (c -> ([(HeaderName, B.ByteString)], a))
309403 -> Method
310404 -> Status
311405 -> Proxy framing
@@ -313,7 +407,7 @@ streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenera
313407 -> Delayed env (Handler c)
314408 -> Router env
315409streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRouter $ \env request respond ->
316− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
410+ let AcceptHeader accH = getAcceptHeader request
317411 cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
318412 accCheck = when (isNothing cmediatype) $ delayedFail err406
319413 contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
@@ -321,28 +415,19 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
321415 `addAcceptCheck` accCheck
322416 ) env request respond $ \ output ->
323417 let (headers, fa) = splitHeaders output
324− k = getStreamGenerator . toStreamGenerator $ fa in
325− Route $ responseStream status (contentHeader : headers) $ \write flush -> do
326− write . BB.lazyByteString $ header framingproxy ctypeproxy
327− case boundary framingproxy ctypeproxy of
328− BoundaryStrategyBracket f ->
329− let go x = let bs = mimeRender ctypeproxy x
330− (before, after) = f bs
331− in write ( BB.lazyByteString before
332− <> BB.lazyByteString bs
333− <> BB.lazyByteString after) >> flush
334− in k go go
335− BoundaryStrategyIntersperse sep -> k
336− (\x -> do
337− write . BB.lazyByteString . mimeRender ctypeproxy $ x
338− flush)
339− (\x -> do
340− write . (BB.lazyByteString sep <>) . BB.lazyByteString . mimeRender ctypeproxy $ x
341− flush)
342− BoundaryStrategyGeneral f ->
343− let go = (>> flush) . write . BB.lazyByteString . f . mimeRender ctypeproxy
344− in k go go
345− write . BB.lazyByteString $ trailer framingproxy ctypeproxy
418+ sourceT = toSourceIO fa
419+ S.SourceT kStepLBS = framingRender framingproxy (mimeRender ctypeproxy :: chunk -> BL.ByteString) sourceT
420+ in Route $ responseStream status (contentHeader : headers) $ \write flush -> do
421+ let loop S.Stop = flush
422+ loop (S.Error err) = fail err -- TODO: throw better error
423+ loop (S.Skip s) = loop s
424+ loop (S.Effect ms) = ms >>= loop
425+ loop (S.Yield lbs s) = do
426+ write (BB.lazyByteString lbs)
427+ flush
428+ loop s
429+
430+ kStepLBS loop
346431
347432-- | If you use 'Header' in one of the endpoints for your API,
348433-- this automatically requires your server-side handler to be a function
@@ -367,6 +452,7 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
367452instance
368453 (KnownSymbol sym, FromHttpApiData a, HasServer api context
369454 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
455+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
370456 )
371457 => HasServer (Header' mods sym a :> api) context where
372458------
@@ -378,6 +464,9 @@ instance
378464 route Proxy context subserver = route (Proxy :: Proxy api) context $
379465 subserver `addHeaderCheck` withRequest headerCheck
380466 where
467+ rep = typeRep (Proxy :: Proxy Header')
468+ formatError = headerParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
469+
381470 headerName :: IsString n => n
382471 headerName = fromString $ symbolVal (Proxy :: Proxy sym)
383472
@@ -388,15 +477,13 @@ instance
388477 mev :: Maybe (Either T.Text a)
389478 mev = fmap parseHeader $ lookup headerName (requestHeaders req)
390479
391− errReq = delayedFailFatal err400
392− { errBody = "Header " <> headerName <> " is required"
393− }
480+ errReq = delayedFailFatal $ formatError rep req
481+ $ "Header " <> headerName <> " is required"
394482
395− errSt e = delayedFailFatal err400
396− { errBody = cs $ "Error parsing header "
397− <> headerName
398− <> " failed: " <> e
399− }
483+ errSt e = delayedFailFatal $ formatError rep req
484+ $ cs $ "Error parsing header "
485+ <> headerName
486+ <> " failed: " <> e
400487
401488-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
402489-- this automatically requires your server-side handler to be a function
@@ -422,6 +509,7 @@ instance
422509instance
423510 ( KnownSymbol sym, FromHttpApiData a, HasServer api context
424511 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
512+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
425513 )
426514 => HasServer (QueryParam' mods sym a :> api) context where
427515------
@@ -431,9 +519,12 @@ instance
431519 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
432520
433521 route Proxy context subserver =
434− let querytext req = parseQueryText $ rawQueryString req
522+ let querytext = queryToQueryText . queryString
435523 paramname = cs $ symbolVal (Proxy :: Proxy sym)
436524
525+ rep = typeRep (Proxy :: Proxy QueryParam')
526+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
527+
437528 parseParam :: Request -> DelayedIO (RequestArgument mods a)
438529 parseParam req =
439530 unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
@@ -441,14 +532,12 @@ instance
441532 mev :: Maybe (Either T.Text a)
442533 mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
443534
444− errReq = delayedFailFatal err400
445− { errBody = cs $ "Query parameter " <> paramname <> " is required"
446− }
535+ errReq = delayedFailFatal $ formatError rep req
536+ $ cs $ "Query parameter " <> paramname <> " is required"
447537
448− errSt e = delayedFailFatal err400
449− { errBody = cs $ "Error parsing query parameter "
450− <> paramname <> " failed: " <> e
451− }
538+ errSt e = delayedFailFatal $ formatError rep req
539+ $ cs $ "Error parsing query parameter "
540+ <> paramname <> " failed: " <> e
452541
453542 delayed = addParameterCheck subserver . withRequest $ \req ->
454543 parseParam req
@@ -474,7 +563,8 @@ instance
474563-- > server = getBooksBy
475564-- > where getBooksBy :: [Text] -> Handler [Book]
476565-- > getBooksBy authors = ...return all books by these authors...
477−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
566+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
567+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters)
478568 => HasServer (QueryParams sym a :> api) context where
479569
480570 type ServerT (QueryParams sym a :> api) m =
@@ -485,21 +575,23 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
485575 route Proxy context subserver = route (Proxy :: Proxy api) context $
486576 subserver `addParameterCheck` withRequest paramsCheck
487577 where
578+ rep = typeRep (Proxy :: Proxy QueryParams)
579+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
580+
488581 paramname = cs $ symbolVal (Proxy :: Proxy sym)
489582 paramsCheck req =
490583 case partitionEithers $ fmap parseQueryParam params of
491584 ([], parsed) -> return parsed
492− (errs, _) -> delayedFailFatal err400
493− { errBody = cs $ "Error parsing query parameter(s) "
494− <> paramname <> " failed: "
495− <> T.intercalate ", " errs
496− }
585+ (errs, _) -> delayedFailFatal $ formatError rep req
586+ $ cs $ "Error parsing query parameter(s) "
587+ <> paramname <> " failed: "
588+ <> T.intercalate ", " errs
497589 where
498590 params :: [T.Text]
499591 params = mapMaybe snd
500592 . filter (looksLikeParam . fst)
501− . parseQueryText
502− . rawQueryString
593+ . queryToQueryText
594+ . queryString
503595 $ req
504596
505597 looksLikeParam name = name == paramname || name == (paramname <> "[]")
@@ -525,7 +617,7 @@ instance (KnownSymbol sym, HasServer api context)
525617 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
526618
527619 route Proxy context subserver =
528− let querytext r = parseQueryText $ rawQueryString r
620+ let querytext = queryToQueryText . queryString
529621 param r = case lookup paramname (querytext r) of
530622 Just Nothing -> True -- param is there, with no value
531623 Just (Just v) -> examine v -- param with a value
@@ -561,13 +653,42 @@ instance HasServer Raw context where
561653 Fail a -> respond $ Fail a
562654 FailFatal e -> respond $ FailFatal e
563655
656+-- | Just pass the request to the underlying application and serve its response.
657+--
658+-- Example:
659+--
660+-- > type MyApi = "images" :> Raw
661+-- >
662+-- > server :: Server MyApi
663+-- > server = serveDirectory "/var/www/images"
664+instance HasServer RawM context where
665+ type ServerT RawM m = Request -> (Response -> IO ResponseReceived) -> m ResponseReceived
666+
667+ route
668+ :: Proxy RawM
669+ -> Context context
670+ -> Delayed env (Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived) -> Router env
671+ route _ _ handleDelayed = RawRouter $ \env request respond -> runResourceT $ do
672+ routeResult <- runDelayed handleDelayed env request
673+ let respond' = liftIO . respond
674+ liftIO $ case routeResult of
675+ Route handler -> runHandler (handler request (respond . Route)) >>=
676+ \case
677+ Left e -> respond' $ FailFatal e
678+ Right a -> pure a
679+ Fail e -> respond' $ Fail e
680+ FailFatal e -> respond' $ FailFatal e
681+
682+ hoistServerWithContext _ _ f srvM = \req respond -> f (srvM req respond)
683+
684+
564685-- | If you use 'ReqBody' in one of the endpoints for your API,
565686-- this automatically requires your server-side handler to be a function
566687-- that takes an argument of the type specified by 'ReqBody'.
567688-- The @Content-Type@ header is inspected, and the list provided is used to
568689-- attempt deserialization. If the request does not have a @Content-Type@
569690-- header, it is treated as @application/octet-stream@ (as specified in
570−-- <http://tools.ietf.org/html/rfc7231#section-3.1.1.5 RFC7231>.
691+-- [RFC 7231 section 3.1.1.5](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)).
571692-- This lets servant worry about extracting it from the request and turning
572693-- it into a value of the type you specify.
573694--
@@ -583,6 +704,7 @@ instance HasServer Raw context where
583704-- > where postBook :: Book -> Handler Book
584705-- > postBook book = ...insert into your db...
585706instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
707+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
586708 ) => HasServer (ReqBody' mods list a :> api) context where
587709
588710 type ServerT (ReqBody' mods list a :> api) m =
@@ -594,6 +716,9 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
594716 = route (Proxy :: Proxy api) context $
595717 addBodyCheck subserver ctCheck bodyCheck
596718 where
719+ rep = typeRep (Proxy :: Proxy ReqBody')
720+ formatError = bodyParserErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
721+
597722 -- Content-Type check, we only lookup we can try to parse the request body
598723 ctCheck = withRequest $ \ request -> do
599724 -- See HTTP RFC 2616, section 7.2.1
@@ -612,9 +737,34 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
612737 case sbool :: SBool (FoldLenient mods) of
613738 STrue -> return mrqbody
614739 SFalse -> case mrqbody of
615− Left e -> delayedFailFatal err400 { errBody = cs e }
740+ Left e -> delayedFailFatal $ formatError rep request e
616741 Right v -> return v
617742
743+instance
744+ ( FramingUnrender framing, FromSourceIO chunk a, MimeUnrender ctype chunk
745+ , HasServer api context
746+ ) => HasServer (StreamBody' mods framing ctype a :> api) context
747+ where
748+ type ServerT (StreamBody' mods framing ctype a :> api) m = a -> ServerT api m
749+
750+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
751+
752+ route Proxy context subserver = route (Proxy :: Proxy api) context $
753+ addBodyCheck subserver ctCheck bodyCheck
754+ where
755+ ctCheck :: DelayedIO (SourceIO chunk -> a)
756+ -- TODO: do content-type check
757+ ctCheck = return fromSourceIO
758+
759+ bodyCheck :: (SourceIO chunk -> a) -> DelayedIO a
760+ bodyCheck fromRS = withRequest $ \req -> do
761+ let mimeUnrender' = mimeUnrender (Proxy :: Proxy ctype) :: BL.ByteString -> Either String chunk
762+ let framingUnrender' = framingUnrender (Proxy :: Proxy framing) mimeUnrender' :: SourceIO B.ByteString -> SourceIO chunk
763+ let body = getRequestBodyChunk req
764+ let rs = S.fromAction B.null body
765+ let rs' = fromRS $ framingUnrender' rs
766+ return rs'
767+
618768-- | Make sure the incoming request starts with @"/path"@, strip it and
619769-- pass the rest of the request path to @api@.
620770instance (KnownSymbol path, HasServer api context) => HasServer (path :> api) context where
@@ -679,12 +829,12 @@ data EmptyServer = EmptyServer deriving (Typeable, Eq, Show, Bounded, Enum)
679829emptyServer :: ServerT EmptyAPI m
680830emptyServer = Tagged EmptyServer
681831
682−-- | The server for an `EmptyAPI` is `emptyAPIServer`.
832+-- | The server for an `EmptyAPI` is `emptyServer`.
683833--
684834-- > type MyApi = "nothing" :> EmptyApi
685835-- >
686836-- > server :: Server MyApi
687−-- > server = emptyAPIServer
837+-- > server = emptyServer
688838instance HasServer EmptyAPI context where
689839 type ServerT EmptyAPI m = Tagged m EmptyServer
690840
@@ -713,7 +863,10 @@ instance ( KnownSymbol realm
713863-- * helpers
714864
715865ct_wildcard :: B.ByteString
716−ct_wildcard = "*" <> "/" <> "*" -- Because CPP
866+ct_wildcard = "*" <> "/" <> "*"
867+
868+getAcceptHeader :: Request -> AcceptHeader
869+getAcceptHeader = AcceptHeader . fromMaybe ct_wildcard . lookup hAccept . requestHeaders
717870
718871-- * General Authentication
719872
@@ -738,39 +891,19 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
738891 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
739892
740893-------------------------------------------------------------------------------
741−-- TypeError helpers
894+-- Custom type errors
742895-------------------------------------------------------------------------------
743896
744−#ifdef HAS_TYPE_ERROR
745−-- | This instance catches mistakes when there are non-saturated
746−-- type applications on LHS of ':>'.
747−--
748−-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
749−-- ...
750−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
751−-- ...Maybe you haven't applied enough arguments to
752−-- ...Capture' '[] "foo"
753−-- ...
754−--
755−-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
756−-- ...
757−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
758−-- ...Maybe you haven't applied enough arguments to
759−-- ...Capture' '[] "foo"
760−-- ...
761−--
762−instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
897+-- Erroring instance for 'HasServer' when a combinator is not fully applied
898+instance TypeError (PartialApplication
899+#if __GLASGOW_HASKELL__ >= 904
900+ @(Type -> [Type] -> Constraint)
901+#endif
902+ HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
763903 where
764− type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
765− -- it doens't really matter what sub route we peak
766− route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
767− hoistServerWithContext _ _ _ = id
768−
769−-- Cannot have TypeError here, otherwise use of this symbol will error :)
770−type HasServerArrowKindError arr =
771− 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
772− ':$$: 'Text "Maybe you haven't applied enough arguments to"
773− ':$$: 'ShowType arr
904+ type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
905+ route = error "unreachable"
906+ hoistServerWithContext _ _ _ _ = error "unreachable"
774907
775908-- | This instance prevents from accidentally using '->' instead of ':>'
776909--
@@ -804,7 +937,107 @@ type HasServerArrowTypeError a b =
804937 ':$$: 'ShowType a
805938 ':$$: 'Text "and"
806939 ':$$: 'ShowType b
940+
941+-- Erroring instances for 'HasServer' for unknown API combinators
942+
943+-- XXX: This omits the @context@ parameter, e.g.:
944+--
945+-- "There is no instance for HasServer (Bool :> …)". Do we care ?
946+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub
947+#if __GLASGOW_HASKELL__ >= 904
948+ @(Type -> [Type] -> Constraint)
807949#endif
950+ HasServer ty) => HasServer (ty :> sub) context
951+
952+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
953+
954+-- | Ignore @'Fragment'@ in server handlers.
955+-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
956+--
957+-- Example:
958+--
959+-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
960+-- >
961+-- > server :: Server MyApi
962+-- > server = getBooks
963+-- > where getBooks :: Handler [Book]
964+-- > getBooks = ...return all books...
965+instance (AtLeastOneFragment api, FragmentUnique (Fragment a1 :> api), HasServer api context)
966+ => HasServer (Fragment a1 :> api) context where
967+ type ServerT (Fragment a1 :> api) m = ServerT api m
968+
969+ route _ = route (Proxy :: Proxy api)
970+
971+ hoistServerWithContext _ = hoistServerWithContext (Proxy :: Proxy api)
808972
809973-- $setup
810974-- >>> import Servant
975+
976+-- | A type that specifies that an API record contains a server implementation.
977+data AsServerT (m :: * -> *)
978+instance GenericMode (AsServerT m) where
979+ type AsServerT m :- api = ServerT api m
980+
981+type AsServer = AsServerT Handler
982+
983+
984+-- | Set of constraints required to convert to / from vanilla server types.
985+type GServerConstraints api m =
986+ ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m
987+ , GServantProduct (Rep (api (AsServerT m)))
988+ )
989+
990+-- | This class is a necessary evil: in the implementation of 'HasServer' for
991+-- @'NamedRoutes' api@, we essentially need the quantified constraint @forall
992+-- m. 'GServerConstraints' m@ to hold.
993+--
994+-- We cannot require do that directly as the definition of 'GServerConstraints'
995+-- contains type family applications ('Rep' and 'ServerT'). The trick is to hide
996+-- those type family applications behind a typeclass providing evidence for
997+-- @'GServerConstraints' api m@ in the form of a dictionary, and require that
998+-- @forall m. 'GServer' api m@ instead.
999+--
1000+-- Users shouldn't have to worry about this class, as the only possible instance
1001+-- is provided in this module for all record APIs.
1002+
1003+class GServer (api :: * -> *) (m :: * -> *) where
1004+ gServerProof :: Dict (GServerConstraints api m)
1005+
1006+instance
1007+ ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m
1008+ , GServantProduct (Rep (api (AsServerT m)))
1009+ ) => GServer api m where
1010+ gServerProof = Dict
1011+
1012+instance
1013+ ( HasServer (ToServantApi api) context
1014+ , forall m. Generic (api (AsServerT m))
1015+ , forall m. GServer api m
1016+ ) => HasServer (NamedRoutes api) context where
1017+
1018+ type ServerT (NamedRoutes api) m = api (AsServerT m)
1019+
1020+ route
1021+ :: Proxy (NamedRoutes api)
1022+ -> Context context
1023+ -> Delayed env (api (AsServerT Handler))
1024+ -> Router env
1025+ route _ ctx delayed =
1026+ case gServerProof @api @Handler of
1027+ Dict -> route (Proxy @(ToServantApi api)) ctx (toServant <$> delayed)
1028+
1029+ hoistServerWithContext
1030+ :: forall m n. Proxy (NamedRoutes api)
1031+ -> Proxy context
1032+ -> (forall x. m x -> n x)
1033+ -> api (AsServerT m)
1034+ -> api (AsServerT n)
1035+ hoistServerWithContext _ pctx nat server =
1036+ case (gServerProof @api @m, gServerProof @api @n) of
1037+ (Dict, Dict) ->
1038+ fromServant servantSrvN
1039+ where
1040+ servantSrvM :: ServerT (ToServantApi api) m =
1041+ toServant server
1042+ servantSrvN :: ServerT (ToServantApi api) n =
1043+ hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM