@@ -1,54 +1,51 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE ConstraintKinds #-}
31{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ConstraintKinds #-}
43{-# LANGUAGE DataKinds #-}
54{-# LANGUAGE DeriveDataTypeable #-}
65{-# LANGUAGE FlexibleContexts #-}
76{-# LANGUAGE FlexibleInstances #-}
8−{-# LANGUAGE InstanceSigs #-}
9−{-# LANGUAGE LambdaCase #-}
107{-# LANGUAGE MultiParamTypeClasses #-}
118{-# LANGUAGE OverloadedStrings #-}
129{-# LANGUAGE PolyKinds #-}
13−{-# LANGUAGE QuantifiedConstraints #-}
1410{-# LANGUAGE RankNTypes #-}
1511{-# LANGUAGE ScopedTypeVariables #-}
1612{-# LANGUAGE TupleSections #-}
17−{-# LANGUAGE TypeApplications #-}
1813{-# LANGUAGE TypeFamilies #-}
1914{-# LANGUAGE TypeOperators #-}
2015{-# LANGUAGE UndecidableInstances #-}
2116
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+
2223module Servant.Server.Internal
2324 ( module Servant.Server.Internal
2425 , module Servant.Server.Internal.BasicAuth
2526 , module Servant.Server.Internal.Context
26− , module Servant.Server.Internal.Delayed
27− , module Servant.Server.Internal.DelayedIO
28− , module Servant.Server.Internal.ErrorFormatter
2927 , module Servant.Server.Internal.Handler
3028 , module Servant.Server.Internal.Router
31− , module Servant.Server.Internal.RouteResult
3229 , module Servant.Server.Internal.RoutingApplication
33− , module Servant.Server.Internal.ServerError
30+ , module Servant.Server.Internal.ServantErr
3431 ) where
3532
3633import Control.Monad
3734 (join, when)
3835import Control.Monad.Trans
39− (liftIO, lift)
36+ (liftIO)
4037import Control.Monad.Trans.Resource
41− (runResourceT, ReleaseKey)
42−import Data.Acquire
38+ (runResourceT)
4339import qualified Data.ByteString as B
4440import qualified Data.ByteString.Builder as BB
4541import qualified Data.ByteString.Char8 as BC8
4642import qualified Data.ByteString.Lazy as BL
47−import Data.Constraint (Constraint, Dict(..))
4843import Data.Either
4944 (partitionEithers)
5045import Data.Maybe
5146 (fromMaybe, isNothing, mapMaybe, maybeToList)
47+import Data.Semigroup
48+ ((<>))
5249import Data.String
5350 (IsString (..))
5451import Data.String.Conversions
@@ -57,70 +54,53 @@ import Data.Tagged
5754 (Tagged (..), retag, untag)
5855import qualified Data.Text as T
5956import Data.Typeable
60−import GHC.Generics
6157import GHC.TypeLits
62− (KnownNat, KnownSymbol, TypeError, symbolVal)
58+ (KnownNat, KnownSymbol, natVal, symbolVal)
6359import qualified Network.HTTP.Media as NHM
6460import Network.HTTP.Types hiding
6561 (Header, ResponseHeaders)
6662import Network.Socket
6763 (SockAddr)
6864import Network.Wai
69− (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
70− queryString, remoteHost, getRequestBodyChunk, requestHeaders,
71− requestMethod, responseLBS, responseStream, vault)
65+ (Application, Request, httpVersion, isSecure, lazyRequestBody,
66+ rawQueryString, remoteHost, requestHeaders, requestMethod,
67+ responseLBS, responseStream, vault)
7268import Prelude ()
7369import Prelude.Compat
7470import Servant.API
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)
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)
8479import Servant.API.ContentTypes
8580 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
86− AllMime, MimeRender (..), MimeUnrender (..), NoContent,
87− canHandleAcceptH)
81+ AllMime, MimeRender (..), canHandleAcceptH)
8882import Servant.API.Modifiers
8983 (FoldLenient, FoldRequired, RequestArgument,
9084 unfoldRequestArgument)
9185import Servant.API.ResponseHeaders
9286 (GetHeaders, Headers, getHeaders, getResponse)
93−import Servant.API.Status
94− (statusFromNat)
95−import qualified Servant.Types.SourceT as S
96−import Servant.API.TypeErrors
9787import Web.HttpApiData
98− (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
99− parseUrlPieces)
100−import Data.Kind
101− (Type)
88+ (FromHttpApiData, parseHeader, parseQueryParam,
89+ parseUrlPieceMaybe, parseUrlPieces)
10290
10391import Servant.Server.Internal.BasicAuth
10492import Servant.Server.Internal.Context
105−import Servant.Server.Internal.Delayed
106−import Servant.Server.Internal.DelayedIO
107−import Servant.Server.Internal.ErrorFormatter
10893import Servant.Server.Internal.Handler
10994import Servant.Server.Internal.Router
110−import Servant.Server.Internal.RouteResult
11195import Servant.Server.Internal.RoutingApplication
112−import Servant.Server.Internal.ServerError
96+import Servant.Server.Internal.ServantErr
11397
98+#ifdef HAS_TYPE_ERROR
11499import GHC.TypeLits
115100 (ErrorMessage (..), TypeError)
116−import Servant.API.TypeLevel
117− (AtLeastOneFragment, FragmentUnique)
101+#endif
118102
119103class 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.
124104 type ServerT api (m :: * -> *) :: *
125105
126106 route ::
@@ -182,31 +162,22 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
182162-- > server = getBook
183163-- > where getBook :: Text -> Handler Book
184164-- > getBook isbn = ...
185−instance (KnownSymbol capture, FromHttpApiData a, Typeable a
186− , HasServer api context, SBoolI (FoldLenient mods)
187− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
188− )
165+instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
189166 => HasServer (Capture' mods capture a :> api) context where
190167
191168 type ServerT (Capture' mods capture a :> api) m =
192− If (FoldLenient mods) (Either String a) a -> ServerT api m
169+ a -> ServerT api m
193170
194171 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
195172
196173 route Proxy context d =
197− CaptureRouter [hint] $
174+ CaptureRouter $
198175 route (Proxy :: Proxy api)
199176 context
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))
177+ (addCapture d $ \ txt -> case parseUrlPieceMaybe txt of
178+ Nothing -> delayedFail err400
179+ Just v -> return v
180+ )
210181
211182-- | If you use 'CaptureAll' in one of the endpoints for your API,
212183-- this automatically requires your server-side handler to be a
@@ -225,10 +196,7 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
225196-- > server = getSourceFile
226197-- > where getSourceFile :: [Text] -> Handler Book
227198-- > getSourceFile pathSegments = ...
228−instance (KnownSymbol capture, FromHttpApiData a, Typeable a
229− , HasServer api context
230− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
231− )
199+instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
232200 => HasServer (CaptureAll capture a :> api) context where
233201
234202 type ServerT (CaptureAll capture a :> api) m =
@@ -237,53 +205,13 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
237205 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
238206
239207 route Proxy context d =
240− CaptureAllRouter [hint] $
208+ CaptureAllRouter $
241209 route (Proxy :: Proxy api)
242210 context
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
211+ (addCapture d $ \ txts -> case parseUrlPieces txts of
212+ Left _ -> delayedFail err400
213+ Right v -> return v
247214 )
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−
287215
288216
289217allowedMethodHead :: Method -> Request -> Bool
@@ -304,10 +232,10 @@ methodCheck method request
304232-- body check is no longer an option. However, we now run the accept
305233-- check before the body check and can therefore afford to make it
306234-- recoverable.
307−acceptCheck :: (AllMime list) => Proxy list -> AcceptHeader -> DelayedIO ()
235+acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
308236acceptCheck proxy accH
309− | canHandleAcceptH proxy accH = return ()
310− | otherwise = delayedFail err406
237+ | canHandleAcceptH proxy (AcceptHeader accH) = return ()
238+ | otherwise = delayedFail err406
311239
312240methodRouter :: (AllCTRender ctypes a)
313241 => (b -> ([(HeaderName, B.ByteString)], a))
@@ -317,29 +245,18 @@ methodRouter :: (AllCTRender ctypes a)
317245methodRouter splitHeaders method proxy status action = leafRouter route'
318246 where
319247 route' env request respond =
320− let accH = getAcceptHeader request
248+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
321249 in runAction (action `addMethodCheck` methodCheck method request
322250 `addAcceptCheck` acceptCheck proxy accH
323251 ) env request respond $ \ output -> do
324252 let (headers, b) = splitHeaders output
325− case handleAcceptH proxy accH b of
253+ case handleAcceptH proxy (AcceptHeader accH) b of
326254 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
327255 Just (contentT, body) ->
328256 let bdy = if allowedMethodHead method request then "" else body
329257 in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy
330258
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−
342−instance {-# OVERLAPPABLE #-}
259+instance OVERLAPPABLE_
343260 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
344261 ) => HasServer (Verb method status ctypes a) context where
345262
@@ -348,9 +265,9 @@ instance {-# OVERLAPPABLE #-}
348265
349266 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
350267 where method = reflectMethod (Proxy :: Proxy method)
351− status = statusFromNat (Proxy :: Proxy status)
268+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
352269
353−instance {-# OVERLAPPING #-}
270+instance OVERLAPPING_
354271 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
355272 , GetHeaders (Headers h a)
356273 ) => HasServer (Verb method status ctypes (Headers h a)) context where
@@ -360,46 +277,37 @@ instance {-# OVERLAPPING #-}
360277
361278 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
362279 where method = reflectMethod (Proxy :: Proxy method)
363− status = statusFromNat (Proxy :: Proxy status)
280+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
364281
365−instance (ReflectMethod method) =>
366− HasServer (NoContentVerb method) context where
367282
368− type ServerT (NoContentVerb method) m = m NoContent
369− hoistServerWithContext _ _ nt s = nt s
370−
371− route Proxy _ = noContentRouter method status204
372− where method = reflectMethod (Proxy :: Proxy method)
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
373287
374−instance {-# OVERLAPPABLE #-}
375− ( MimeRender ctype chunk, ReflectMethod method, KnownNat status,
376− FramingRender framing, ToSourceIO chunk a
377− ) => HasServer (Stream method status framing ctype a) context where
378−
379− type ServerT (Stream method status framing ctype a) m = m a
288+ type ServerT (Stream method status framing ctype b) m = m b
380289 hoistServerWithContext _ _ nt s = nt s
381290
382291 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
383292 where method = reflectMethod (Proxy :: Proxy method)
384− status = statusFromNat (Proxy :: Proxy status)
385−
293+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
386294
387−instance {-# OVERLAPPING #-}
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
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
392300
393− type ServerT (Stream method status framing ctype (Headers h a)) m = m (Headers h a)
301+ type ServerT (Stream method status framing ctype (Headers h b)) m = m (Headers h b)
394302 hoistServerWithContext _ _ nt s = nt s
395303
396304 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
397305 where method = reflectMethod (Proxy :: Proxy method)
398− status = statusFromNat (Proxy :: Proxy status)
306+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
399307
400308
401−streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
402− (c -> ([(HeaderName, B.ByteString)], a))
309+streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenerator b a) =>
310+ (c -> ([(HeaderName, B.ByteString)], b))
403311 -> Method
404312 -> Status
405313 -> Proxy framing
@@ -407,7 +315,7 @@ streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, Fra
407315 -> Delayed env (Handler c)
408316 -> Router env
409317streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRouter $ \env request respond ->
410− let AcceptHeader accH = getAcceptHeader request
318+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
411319 cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
412320 accCheck = when (isNothing cmediatype) $ delayedFail err406
413321 contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
@@ -415,19 +323,28 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
415323 `addAcceptCheck` accCheck
416324 ) env request respond $ \ output ->
417325 let (headers, fa) = splitHeaders output
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
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
431348
432349-- | If you use 'Header' in one of the endpoints for your API,
433350-- this automatically requires your server-side handler to be a function
@@ -452,7 +369,6 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
452369instance
453370 (KnownSymbol sym, FromHttpApiData a, HasServer api context
454371 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
455− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
456372 )
457373 => HasServer (Header' mods sym a :> api) context where
458374------
@@ -464,9 +380,6 @@ instance
464380 route Proxy context subserver = route (Proxy :: Proxy api) context $
465381 subserver `addHeaderCheck` withRequest headerCheck
466382 where
467− rep = typeRep (Proxy :: Proxy Header')
468− formatError = headerParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
469−
470383 headerName :: IsString n => n
471384 headerName = fromString $ symbolVal (Proxy :: Proxy sym)
472385
@@ -477,13 +390,15 @@ instance
477390 mev :: Maybe (Either T.Text a)
478391 mev = fmap parseHeader $ lookup headerName (requestHeaders req)
479392
480− errReq = delayedFailFatal $ formatError rep req
481− $ "Header " <> headerName <> " is required"
393+ errReq = delayedFailFatal err400
394+ { errBody = "Header " <> headerName <> " is required"
395+ }
482396
483− errSt e = delayedFailFatal $ formatError rep req
484− $ cs $ "Error parsing header "
485− <> headerName
486− <> " failed: " <> e
397+ errSt e = delayedFailFatal err400
398+ { errBody = cs $ "Error parsing header "
399+ <> headerName
400+ <> " failed: " <> e
401+ }
487402
488403-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
489404-- this automatically requires your server-side handler to be a function
@@ -509,7 +424,6 @@ instance
509424instance
510425 ( KnownSymbol sym, FromHttpApiData a, HasServer api context
511426 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
512− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
513427 )
514428 => HasServer (QueryParam' mods sym a :> api) context where
515429------
@@ -519,12 +433,9 @@ instance
519433 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
520434
521435 route Proxy context subserver =
522− let querytext = queryToQueryText . queryString
436+ let querytext req = parseQueryText $ rawQueryString req
523437 paramname = cs $ symbolVal (Proxy :: Proxy sym)
524438
525− rep = typeRep (Proxy :: Proxy QueryParam')
526− formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
527−
528439 parseParam :: Request -> DelayedIO (RequestArgument mods a)
529440 parseParam req =
530441 unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
@@ -532,12 +443,14 @@ instance
532443 mev :: Maybe (Either T.Text a)
533444 mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
534445
535− errReq = delayedFailFatal $ formatError rep req
536− $ cs $ "Query parameter " <> paramname <> " is required"
446+ errReq = delayedFailFatal err400
447+ { errBody = cs $ "Query parameter " <> paramname <> " is required"
448+ }
537449
538− errSt e = delayedFailFatal $ formatError rep req
539− $ cs $ "Error parsing query parameter "
540− <> paramname <> " failed: " <> e
450+ errSt e = delayedFailFatal err400
451+ { errBody = cs $ "Error parsing query parameter "
452+ <> paramname <> " failed: " <> e
453+ }
541454
542455 delayed = addParameterCheck subserver . withRequest $ \req ->
543456 parseParam req
@@ -563,8 +476,7 @@ instance
563476-- > server = getBooksBy
564477-- > where getBooksBy :: [Text] -> Handler [Book]
565478-- > getBooksBy authors = ...return all books by these authors...
566−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
567− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters)
479+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
568480 => HasServer (QueryParams sym a :> api) context where
569481
570482 type ServerT (QueryParams sym a :> api) m =
@@ -575,23 +487,21 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
575487 route Proxy context subserver = route (Proxy :: Proxy api) context $
576488 subserver `addParameterCheck` withRequest paramsCheck
577489 where
578− rep = typeRep (Proxy :: Proxy QueryParams)
579− formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
580−
581490 paramname = cs $ symbolVal (Proxy :: Proxy sym)
582491 paramsCheck req =
583492 case partitionEithers $ fmap parseQueryParam params of
584493 ([], parsed) -> return parsed
585− (errs, _) -> delayedFailFatal $ formatError rep req
586− $ cs $ "Error parsing query parameter(s) "
587− <> paramname <> " failed: "
588− <> T.intercalate ", " errs
494+ (errs, _) -> delayedFailFatal err400
495+ { errBody = cs $ "Error parsing query parameter(s) "
496+ <> paramname <> " failed: "
497+ <> T.intercalate ", " errs
498+ }
589499 where
590500 params :: [T.Text]
591501 params = mapMaybe snd
592502 . filter (looksLikeParam . fst)
593− . queryToQueryText
594− . queryString
503+ . parseQueryText
504+ . rawQueryString
595505 $ req
596506
597507 looksLikeParam name = name == paramname || name == (paramname <> "[]")
@@ -617,7 +527,7 @@ instance (KnownSymbol sym, HasServer api context)
617527 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
618528
619529 route Proxy context subserver =
620− let querytext = queryToQueryText . queryString
530+ let querytext r = parseQueryText $ rawQueryString r
621531 param r = case lookup paramname (querytext r) of
622532 Just Nothing -> True -- param is there, with no value
623533 Just (Just v) -> examine v -- param with a value
@@ -653,42 +563,13 @@ instance HasServer Raw context where
653563 Fail a -> respond $ Fail a
654564 FailFatal e -> respond $ FailFatal e
655565
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−
685566-- | If you use 'ReqBody' in one of the endpoints for your API,
686567-- this automatically requires your server-side handler to be a function
687568-- that takes an argument of the type specified by 'ReqBody'.
688569-- The @Content-Type@ header is inspected, and the list provided is used to
689570-- attempt deserialization. If the request does not have a @Content-Type@
690571-- header, it is treated as @application/octet-stream@ (as specified in
691−-- [RFC 7231 section 3.1.1.5](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)).
572+-- <http://tools.ietf.org/html/rfc7231#section-3.1.1.5 RFC7231>.
692573-- This lets servant worry about extracting it from the request and turning
693574-- it into a value of the type you specify.
694575--
@@ -704,7 +585,6 @@ instance HasServer RawM context where
704585-- > where postBook :: Book -> Handler Book
705586-- > postBook book = ...insert into your db...
706587instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
707− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
708588 ) => HasServer (ReqBody' mods list a :> api) context where
709589
710590 type ServerT (ReqBody' mods list a :> api) m =
@@ -716,9 +596,6 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
716596 = route (Proxy :: Proxy api) context $
717597 addBodyCheck subserver ctCheck bodyCheck
718598 where
719− rep = typeRep (Proxy :: Proxy ReqBody')
720− formatError = bodyParserErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
721−
722599 -- Content-Type check, we only lookup we can try to parse the request body
723600 ctCheck = withRequest $ \ request -> do
724601 -- See HTTP RFC 2616, section 7.2.1
@@ -737,34 +614,9 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
737614 case sbool :: SBool (FoldLenient mods) of
738615 STrue -> return mrqbody
739616 SFalse -> case mrqbody of
740− Left e -> delayedFailFatal $ formatError rep request e
617+ Left e -> delayedFailFatal err400 { errBody = cs e }
741618 Right v -> return v
742619
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−
768620-- | Make sure the incoming request starts with @"/path"@, strip it and
769621-- pass the rest of the request path to @api@.
770622instance (KnownSymbol path, HasServer api context) => HasServer (path :> api) context where
@@ -829,12 +681,12 @@ data EmptyServer = EmptyServer deriving (Typeable, Eq, Show, Bounded, Enum)
829681emptyServer :: ServerT EmptyAPI m
830682emptyServer = Tagged EmptyServer
831683
832−-- | The server for an `EmptyAPI` is `emptyServer`.
684+-- | The server for an `EmptyAPI` is `emptyAPIServer`.
833685--
834686-- > type MyApi = "nothing" :> EmptyApi
835687-- >
836688-- > server :: Server MyApi
837−-- > server = emptyServer
689+-- > server = emptyAPIServer
838690instance HasServer EmptyAPI context where
839691 type ServerT EmptyAPI m = Tagged m EmptyServer
840692
@@ -863,10 +715,7 @@ instance ( KnownSymbol realm
863715-- * helpers
864716
865717ct_wildcard :: B.ByteString
866−ct_wildcard = "*" <> "/" <> "*"
867−
868−getAcceptHeader :: Request -> AcceptHeader
869−getAcceptHeader = AcceptHeader . fromMaybe ct_wildcard . lookup hAccept . requestHeaders
718+ct_wildcard = "*" <> "/" <> "*" -- Because CPP
870719
871720-- * General Authentication
872721
@@ -891,19 +740,39 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
891740 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
892741
893742-------------------------------------------------------------------------------
894−-- Custom type errors
743+-- TypeError helpers
895744-------------------------------------------------------------------------------
896745
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
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
903765 where
904− type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
905− route = error "unreachable"
906− hoistServerWithContext _ _ _ _ = error "unreachable"
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
907776
908777-- | This instance prevents from accidentally using '->' instead of ':>'
909778--
@@ -937,107 +806,7 @@ type HasServerArrowTypeError a b =
937806 ':$$: 'ShowType a
938807 ':$$: 'Text "and"
939808 ':$$: '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)
949809#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)
972810
973811-- $setup
974812-- >>> 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