@@ -1,29 +1,29 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
1+{-# LANGUAGE CPP #-}
2{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DeriveDataTypeable #-}
4{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
7−{-# LANGUAGE InstanceSigs #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE OverloadedStrings #-}
10{-# LANGUAGE PolyKinds #-}
9{-# LANGUAGE PolyKinds #-}
11−{-# LANGUAGE QuantifiedConstraints #-}
12{-# LANGUAGE RankNTypes #-}
10{-# LANGUAGE RankNTypes #-}
13{-# LANGUAGE ScopedTypeVariables #-}
11{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE TupleSections #-}
12{-# LANGUAGE TupleSections #-}
15−{-# LANGUAGE TypeApplications #-}
16{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE TypeFamilies #-}
17{-# LANGUAGE TypeOperators #-}
14{-# LANGUAGE TypeOperators #-}
18{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
17+#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802
18+#define HAS_TYPE_ERROR
20module Servant.Server.Internal
21module Servant.Server.Internal
21 ( module Servant.Server.Internal
22 ( module Servant.Server.Internal
22 , module Servant.Server.Internal.BasicAuth
23 , module Servant.Server.Internal.BasicAuth
23 , module Servant.Server.Internal.Context
24 , module Servant.Server.Internal.Context
24 , module Servant.Server.Internal.Delayed
25 , module Servant.Server.Internal.Delayed
25 , module Servant.Server.Internal.DelayedIO
26 , module Servant.Server.Internal.DelayedIO
26− , module Servant.Server.Internal.ErrorFormatter
27 , module Servant.Server.Internal.Handler
27 , module Servant.Server.Internal.Handler
28 , module Servant.Server.Internal.Router
28 , module Servant.Server.Internal.Router
29 , module Servant.Server.Internal.RouteResult
29 , module Servant.Server.Internal.RouteResult
@@ -41,11 +41,12 @@ import qualified Data.ByteString as B
41import qualified Data.ByteString.Builder as BB
41import qualified Data.ByteString.Builder as BB
42import qualified Data.ByteString.Char8 as BC8
42import qualified Data.ByteString.Char8 as BC8
43import qualified Data.ByteString.Lazy as BL
43import qualified Data.ByteString.Lazy as BL
44−import Data.Constraint (Constraint, Dict(..))
45import Data.Either
44import Data.Either
46 (partitionEithers)
45 (partitionEithers)
47import Data.Maybe
46import Data.Maybe
48 (fromMaybe, isNothing, mapMaybe, maybeToList)
47 (fromMaybe, isNothing, mapMaybe, maybeToList)
49import Data.String
50import Data.String
50 (IsString (..))
51 (IsString (..))
51import Data.String.Conversions
52import Data.String.Conversions
@@ -54,9 +55,8 @@ import Data.Tagged
54 (Tagged (..), retag, untag)
55 (Tagged (..), retag, untag)
55import qualified Data.Text as T
56import qualified Data.Text as T
56import Data.Typeable
57import Data.Typeable
58import GHC.TypeLits
58import GHC.TypeLits
59− (KnownNat, KnownSymbol, TypeError, symbolVal)
59+ (KnownNat, KnownSymbol, natVal, symbolVal)
60import qualified Network.HTTP.Media as NHM
60import qualified Network.HTTP.Media as NHM
61import Network.HTTP.Types hiding
61import Network.HTTP.Types hiding
62 (Header, ResponseHeaders)
62 (Header, ResponseHeaders)
@@ -64,52 +64,47 @@ import Network.Socket
64 (SockAddr)
64 (SockAddr)
65import Network.Wai
65import Network.Wai
66 (Application, Request, httpVersion, isSecure, lazyRequestBody,
66 (Application, Request, httpVersion, isSecure, lazyRequestBody,
67− queryString, remoteHost, getRequestBodyChunk, requestHeaders,
67+ rawQueryString, remoteHost, requestBody, requestHeaders,
68 requestMethod, responseLBS, responseStream, vault)
68 requestMethod, responseLBS, responseStream, vault)
69import Prelude ()
69import Prelude ()
70import Prelude.Compat
70import Prelude.Compat
71import Servant.API
71import Servant.API
72 ((:<|>) (..), (:>), Accept (..), BasicAuth, Capture',
72 ((:<|>) (..), (:>), Accept (..), BasicAuth, Capture',
73− CaptureAll, Description, EmptyAPI, Fragment,
73+ CaptureAll, Description, EmptyAPI, FramingRender (..),
74− FramingRender (..), FramingUnrender (..), FromSourceIO (..),
74+ FramingUnrender (..), FromSourceIO (..), Header', If,
75− Header', If, IsSecure (..), NoContentVerb, QueryFlag,
75+ IsSecure (..), QueryFlag, QueryParam', QueryParams, Raw,
76− QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
76+ ReflectMethod (reflectMethod), RemoteHost, ReqBody',
77− RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
77+ SBool (..), SBoolI (..), SourceIO, Stream, StreamBody',
78− Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
78+ Summary, ToSourceIO (..), Vault, Verb, NoContentVerb,
79− WithNamedContext, NamedRoutes)
79+ WithNamedContext)
80−import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
81import Servant.API.ContentTypes
80import Servant.API.ContentTypes
82 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
81 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
83− AllMime, MimeRender (..), MimeUnrender (..), NoContent,
82+ AllMime, MimeRender (..), MimeUnrender (..), canHandleAcceptH,
84− canHandleAcceptH)
83+ NoContent (NoContent))
85import Servant.API.Modifiers
84import Servant.API.Modifiers
86 (FoldLenient, FoldRequired, RequestArgument,
85 (FoldLenient, FoldRequired, RequestArgument,
87 unfoldRequestArgument)
86 unfoldRequestArgument)
88import Servant.API.ResponseHeaders
87import Servant.API.ResponseHeaders
89 (GetHeaders, Headers, getHeaders, getResponse)
88 (GetHeaders, Headers, getHeaders, getResponse)
90−import Servant.API.Status
92import qualified Servant.Types.SourceT as S
89import qualified Servant.Types.SourceT as S
93−import Servant.API.TypeErrors
94import Web.HttpApiData
90import Web.HttpApiData
95− (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
91+ (FromHttpApiData, parseHeader, parseQueryParam,
96− parseUrlPieces)
92+ parseUrlPieceMaybe, parseUrlPieces, parseUrlPiece)
98import Servant.Server.Internal.BasicAuth
94import Servant.Server.Internal.BasicAuth
99import Servant.Server.Internal.Context
95import Servant.Server.Internal.Context
100import Servant.Server.Internal.Delayed
96import Servant.Server.Internal.Delayed
101import Servant.Server.Internal.DelayedIO
97import Servant.Server.Internal.DelayedIO
102−import Servant.Server.Internal.ErrorFormatter
103import Servant.Server.Internal.Handler
98import Servant.Server.Internal.Handler
104import Servant.Server.Internal.Router
99import Servant.Server.Internal.Router
105import Servant.Server.Internal.RouteResult
100import Servant.Server.Internal.RouteResult
106import Servant.Server.Internal.RoutingApplication
101import Servant.Server.Internal.RoutingApplication
107import Servant.Server.Internal.ServerError
102import Servant.Server.Internal.ServerError
104+#ifdef HAS_TYPE_ERROR
109import GHC.TypeLits
105import GHC.TypeLits
110 (ErrorMessage (..), TypeError)
106 (ErrorMessage (..), TypeError)
111−import Servant.API.TypeLevel
107+#endif
112− (AtLeastOneFragment, FragmentUnique)
114class HasServer api context where
109class HasServer api context where
115 type ServerT api (m :: * -> *) :: *
110 type ServerT api (m :: * -> *) :: *
@@ -173,10 +168,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
173-- > server = getBook
168-- > server = getBook
174-- > where getBook :: Text -> Handler Book
169-- > where getBook :: Text -> Handler Book
175-- > getBook isbn = ...
170-- > getBook isbn = ...
176−instance (KnownSymbol capture, FromHttpApiData a, Typeable a
177− , HasServer api context, SBoolI (FoldLenient mods)
171+instance (KnownSymbol capture, FromHttpApiData a, HasServer api context, SBoolI (FoldLenient mods))
178− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
180 => HasServer (Capture' mods capture a :> api) context where
172 => HasServer (Capture' mods capture a :> api) context where
182 type ServerT (Capture' mods capture a :> api) m =
174 type ServerT (Capture' mods capture a :> api) m =
@@ -185,19 +177,15 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
185 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
177 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
187 route Proxy context d =
179 route Proxy context d =
188− CaptureRouter [hint] $
180+ CaptureRouter $
189 route (Proxy :: Proxy api)
181 route (Proxy :: Proxy api)
191− (addCapture d $ \ txt -> withRequest $ \ request ->
192− case ( sbool :: SBool (FoldLenient mods)
183+ (addCapture d $ \ txt -> case ( sbool :: SBool (FoldLenient mods)
193− , parseUrlPiece txt :: Either T.Text a) of
184+ , parseUrlPiece txt :: Either T.Text a) of
194− (SFalse, Left e) -> delayedFail $ formatError rep request $ cs e
185+ (SFalse, Left e) -> delayedFail err400 { errBody = cs e }
195− (SFalse, Right v) -> return v
186+ (SFalse, Right v) -> return v
196− (STrue, piece) -> return $ (either (Left . cs) Right) piece)
187+ (STrue, piece) -> return $ (either (Left . cs) Right) piece
198− rep = typeRep (Proxy :: Proxy Capture')
199− formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
200− hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
202-- | If you use 'CaptureAll' in one of the endpoints for your API,
190-- | If you use 'CaptureAll' in one of the endpoints for your API,
203-- this automatically requires your server-side handler to be a
191-- this automatically requires your server-side handler to be a
@@ -216,10 +204,7 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
216-- > server = getSourceFile
204-- > server = getSourceFile
217-- > where getSourceFile :: [Text] -> Handler Book
205-- > where getSourceFile :: [Text] -> Handler Book
218-- > getSourceFile pathSegments = ...
206-- > getSourceFile pathSegments = ...
219−instance (KnownSymbol capture, FromHttpApiData a, Typeable a
207+instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
220− , HasServer api context
221− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
223 => HasServer (CaptureAll capture a :> api) context where
208 => HasServer (CaptureAll capture a :> api) context where
225 type ServerT (CaptureAll capture a :> api) m =
210 type ServerT (CaptureAll capture a :> api) m =
@@ -228,18 +213,14 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
228 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
213 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
230 route Proxy context d =
215 route Proxy context d =
231− CaptureAllRouter [hint] $
216+ CaptureAllRouter $
232 route (Proxy :: Proxy api)
217 route (Proxy :: Proxy api)
234− (addCapture d $ \ txts -> withRequest $ \ request ->
235− case parseUrlPieces txts of
219+ (addCapture d $ \ txts -> case parseUrlPieces txts of
236− Left e -> delayedFail $ formatError rep request $ cs e
220+ Left _ -> delayedFail err400
237− Right v -> return v
221+ Right v -> return v
240− rep = typeRep (Proxy :: Proxy CaptureAll)
241− formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
242− hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
244allowedMethodHead :: Method -> Request -> Bool
225allowedMethodHead :: Method -> Request -> Bool
245allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
226allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -259,10 +240,10 @@ methodCheck method request
259-- body check is no longer an option. However, we now run the accept
240-- body check is no longer an option. However, we now run the accept
260-- check before the body check and can therefore afford to make it
241-- check before the body check and can therefore afford to make it
261-- recoverable.
242-- recoverable.
262−acceptCheck :: (AllMime list) => Proxy list -> AcceptHeader -> DelayedIO ()
243+acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
263acceptCheck proxy accH
244acceptCheck proxy accH
264− | canHandleAcceptH proxy accH = return ()
245+ | canHandleAcceptH proxy (AcceptHeader accH) = return ()
265− | otherwise = delayedFail err406
246+ | otherwise = delayedFail err406
267methodRouter :: (AllCTRender ctypes a)
248methodRouter :: (AllCTRender ctypes a)
268 => (b -> ([(HeaderName, B.ByteString)], a))
249 => (b -> ([(HeaderName, B.ByteString)], a))
@@ -272,12 +253,12 @@ methodRouter :: (AllCTRender ctypes a)
272methodRouter splitHeaders method proxy status action = leafRouter route'
253methodRouter splitHeaders method proxy status action = leafRouter route'
274 route' env request respond =
255 route' env request respond =
275− let accH = getAcceptHeader request
256+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
276 in runAction (action `addMethodCheck` methodCheck method request
257 in runAction (action `addMethodCheck` methodCheck method request
277 `addAcceptCheck` acceptCheck proxy accH
258 `addAcceptCheck` acceptCheck proxy accH
278 ) env request respond $ \ output -> do
259 ) env request respond $ \ output -> do
279 let (headers, b) = splitHeaders output
260 let (headers, b) = splitHeaders output
280− case handleAcceptH proxy accH b of
261+ case handleAcceptH proxy (AcceptHeader accH) b of
281 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
262 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
282 Just (contentT, body) ->
263 Just (contentT, body) ->
283 let bdy = if allowedMethodHead method request then "" else body
264 let bdy = if allowedMethodHead method request then "" else body
@@ -291,7 +272,7 @@ noContentRouter method status action = leafRouter route'
292 route' env request respond =
273 route' env request respond =
293 runAction (action `addMethodCheck` methodCheck method request)
274 runAction (action `addMethodCheck` methodCheck method request)
294− env request respond $ \ _output ->
275+ env request respond $ \ output ->
295 Route $ responseLBS status [] ""
276 Route $ responseLBS status [] ""
297instance {-# OVERLAPPABLE #-}
278instance {-# OVERLAPPABLE #-}
@@ -303,7 +284,7 @@ instance {-# OVERLAPPABLE #-}
304 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
285 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
305 where method = reflectMethod (Proxy :: Proxy method)
286 where method = reflectMethod (Proxy :: Proxy method)
306− status = statusFromNat (Proxy :: Proxy status)
287+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
308instance {-# OVERLAPPING #-}
289instance {-# OVERLAPPING #-}
309 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
290 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
@@ -315,7 +296,7 @@ instance {-# OVERLAPPING #-}
316 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
297 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
317 where method = reflectMethod (Proxy :: Proxy method)
298 where method = reflectMethod (Proxy :: Proxy method)
318− status = statusFromNat (Proxy :: Proxy status)
299+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
320instance (ReflectMethod method) =>
301instance (ReflectMethod method) =>
321 HasServer (NoContentVerb method) context where
302 HasServer (NoContentVerb method) context where
@@ -336,7 +317,7 @@ instance {-# OVERLAPPABLE #-}
337 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
318 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
338 where method = reflectMethod (Proxy :: Proxy method)
319 where method = reflectMethod (Proxy :: Proxy method)
339− status = statusFromNat (Proxy :: Proxy status)
320+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
342instance {-# OVERLAPPING #-}
323instance {-# OVERLAPPING #-}
@@ -350,7 +331,7 @@ instance {-# OVERLAPPING #-}
351 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
332 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
352 where method = reflectMethod (Proxy :: Proxy method)
333 where method = reflectMethod (Proxy :: Proxy method)
353− status = statusFromNat (Proxy :: Proxy status)
334+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
356streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
337streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
@@ -362,7 +343,7 @@ streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, Fra
362 -> Delayed env (Handler c)
343 -> Delayed env (Handler c)
363 -> Router env
344 -> Router env
364streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRouter $ \env request respond ->
345streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRouter $ \env request respond ->
365− let AcceptHeader accH = getAcceptHeader request
346+ let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
366 cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
347 cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
367 accCheck = when (isNothing cmediatype) $ delayedFail err406
348 accCheck = when (isNothing cmediatype) $ delayedFail err406
368 contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
349 contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
@@ -407,7 +388,6 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
408 (KnownSymbol sym, FromHttpApiData a, HasServer api context
389 (KnownSymbol sym, FromHttpApiData a, HasServer api context
409 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
390 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
410− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
412 => HasServer (Header' mods sym a :> api) context where
392 => HasServer (Header' mods sym a :> api) context where
@@ -419,9 +399,6 @@ instance
419 route Proxy context subserver = route (Proxy :: Proxy api) context $
399 route Proxy context subserver = route (Proxy :: Proxy api) context $
420 subserver `addHeaderCheck` withRequest headerCheck
400 subserver `addHeaderCheck` withRequest headerCheck
422− rep = typeRep (Proxy :: Proxy Header')
423− formatError = headerParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
425 headerName :: IsString n => n
402 headerName :: IsString n => n
426 headerName = fromString $ symbolVal (Proxy :: Proxy sym)
403 headerName = fromString $ symbolVal (Proxy :: Proxy sym)
@@ -432,13 +409,15 @@ instance
432 mev :: Maybe (Either T.Text a)
409 mev :: Maybe (Either T.Text a)
433 mev = fmap parseHeader $ lookup headerName (requestHeaders req)
410 mev = fmap parseHeader $ lookup headerName (requestHeaders req)
435− errReq = delayedFailFatal $ formatError rep req
412+ errReq = delayedFailFatal err400
436− $ "Header " <> headerName <> " is required"
413+ { errBody = "Header " <> headerName <> " is required"
438− errSt e = delayedFailFatal $ formatError rep req
416+ errSt e = delayedFailFatal err400
439− $ cs $ "Error parsing header "
417+ { errBody = cs $ "Error parsing header "
440− <> headerName
418+ <> headerName
441− <> " failed: " <> e
419+ <> " failed: " <> e
443-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
422-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
444-- this automatically requires your server-side handler to be a function
423-- this automatically requires your server-side handler to be a function
@@ -464,7 +443,6 @@ instance
465 ( KnownSymbol sym, FromHttpApiData a, HasServer api context
444 ( KnownSymbol sym, FromHttpApiData a, HasServer api context
466 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
445 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
467− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
469 => HasServer (QueryParam' mods sym a :> api) context where
447 => HasServer (QueryParam' mods sym a :> api) context where
@@ -474,12 +452,9 @@ instance
474 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
452 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
476 route Proxy context subserver =
454 route Proxy context subserver =
477− let querytext = queryToQueryText . queryString
455+ let querytext req = parseQueryText $ rawQueryString req
478 paramname = cs $ symbolVal (Proxy :: Proxy sym)
456 paramname = cs $ symbolVal (Proxy :: Proxy sym)
480− rep = typeRep (Proxy :: Proxy QueryParam')
481− formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
483 parseParam :: Request -> DelayedIO (RequestArgument mods a)
458 parseParam :: Request -> DelayedIO (RequestArgument mods a)
484 parseParam req =
459 parseParam req =
485 unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
460 unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
@@ -487,12 +462,14 @@ instance
487 mev :: Maybe (Either T.Text a)
462 mev :: Maybe (Either T.Text a)
488 mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
463 mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
490− errReq = delayedFailFatal $ formatError rep req
465+ errReq = delayedFailFatal err400
491− $ cs $ "Query parameter " <> paramname <> " is required"
466+ { errBody = cs $ "Query parameter " <> paramname <> " is required"
493− errSt e = delayedFailFatal $ formatError rep req
469+ errSt e = delayedFailFatal err400
494− $ cs $ "Error parsing query parameter "
470+ { errBody = cs $ "Error parsing query parameter "
495− <> paramname <> " failed: " <> e
471+ <> paramname <> " failed: " <> e
497 delayed = addParameterCheck subserver . withRequest $ \req ->
474 delayed = addParameterCheck subserver . withRequest $ \req ->
498 parseParam req
475 parseParam req
@@ -518,8 +495,7 @@ instance
518-- > server = getBooksBy
495-- > server = getBooksBy
519-- > where getBooksBy :: [Text] -> Handler [Book]
496-- > where getBooksBy :: [Text] -> Handler [Book]
520-- > getBooksBy authors = ...return all books by these authors...
497-- > getBooksBy authors = ...return all books by these authors...
521−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
498+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
522− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters)
523 => HasServer (QueryParams sym a :> api) context where
499 => HasServer (QueryParams sym a :> api) context where
525 type ServerT (QueryParams sym a :> api) m =
501 type ServerT (QueryParams sym a :> api) m =
@@ -530,23 +506,21 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
530 route Proxy context subserver = route (Proxy :: Proxy api) context $
506 route Proxy context subserver = route (Proxy :: Proxy api) context $
531 subserver `addParameterCheck` withRequest paramsCheck
507 subserver `addParameterCheck` withRequest paramsCheck
533− rep = typeRep (Proxy :: Proxy QueryParams)
534− formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
536 paramname = cs $ symbolVal (Proxy :: Proxy sym)
509 paramname = cs $ symbolVal (Proxy :: Proxy sym)
537 paramsCheck req =
510 paramsCheck req =
538 case partitionEithers $ fmap parseQueryParam params of
511 case partitionEithers $ fmap parseQueryParam params of
539 ([], parsed) -> return parsed
512 ([], parsed) -> return parsed
540− (errs, _) -> delayedFailFatal $ formatError rep req
513+ (errs, _) -> delayedFailFatal err400
541− $ cs $ "Error parsing query parameter(s) "
514+ { errBody = cs $ "Error parsing query parameter(s) "
542− <> paramname <> " failed: "
515+ <> paramname <> " failed: "
543− <> T.intercalate ", " errs
516+ <> T.intercalate ", " errs
545 params :: [T.Text]
519 params :: [T.Text]
546 params = mapMaybe snd
520 params = mapMaybe snd
547 . filter (looksLikeParam . fst)
521 . filter (looksLikeParam . fst)
548− . queryToQueryText
522+ . parseQueryText
549− . queryString
523+ . rawQueryString
552 looksLikeParam name = name == paramname || name == (paramname <> "[]")
526 looksLikeParam name = name == paramname || name == (paramname <> "[]")
@@ -572,7 +546,7 @@ instance (KnownSymbol sym, HasServer api context)
572 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
546 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
574 route Proxy context subserver =
548 route Proxy context subserver =
575− let querytext = queryToQueryText . queryString
549+ let querytext r = parseQueryText $ rawQueryString r
576 param r = case lookup paramname (querytext r) of
550 param r = case lookup paramname (querytext r) of
577 Just Nothing -> True -- param is there, with no value
551 Just Nothing -> True -- param is there, with no value
578 Just (Just v) -> examine v -- param with a value
552 Just (Just v) -> examine v -- param with a value
@@ -614,7 +588,7 @@ instance HasServer Raw context where
614-- The @Content-Type@ header is inspected, and the list provided is used to
588-- The @Content-Type@ header is inspected, and the list provided is used to
615-- attempt deserialization. If the request does not have a @Content-Type@
589-- attempt deserialization. If the request does not have a @Content-Type@
616-- header, it is treated as @application/octet-stream@ (as specified in
590-- header, it is treated as @application/octet-stream@ (as specified in
617−-- [RFC 7231 section 3.1.1.5](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)).
591+-- <http://tools.ietf.org/html/rfc7231#section-3.1.1.5 RFC7231>.
618-- This lets servant worry about extracting it from the request and turning
592-- This lets servant worry about extracting it from the request and turning
619-- it into a value of the type you specify.
593-- it into a value of the type you specify.
@@ -630,7 +604,6 @@ instance HasServer Raw context where
630-- > where postBook :: Book -> Handler Book
604-- > where postBook :: Book -> Handler Book
631-- > postBook book = ...insert into your db...
605-- > postBook book = ...insert into your db...
632instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
606instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
633− , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
634 ) => HasServer (ReqBody' mods list a :> api) context where
607 ) => HasServer (ReqBody' mods list a :> api) context where
636 type ServerT (ReqBody' mods list a :> api) m =
609 type ServerT (ReqBody' mods list a :> api) m =
@@ -642,9 +615,6 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
642 = route (Proxy :: Proxy api) context $
615 = route (Proxy :: Proxy api) context $
643 addBodyCheck subserver ctCheck bodyCheck
616 addBodyCheck subserver ctCheck bodyCheck
645− rep = typeRep (Proxy :: Proxy ReqBody')
646− formatError = bodyParserErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
648 -- Content-Type check, we only lookup we can try to parse the request body
618 -- Content-Type check, we only lookup we can try to parse the request body
649 ctCheck = withRequest $ \ request -> do
619 ctCheck = withRequest $ \ request -> do
650 -- See HTTP RFC 2616, section 7.2.1
620 -- See HTTP RFC 2616, section 7.2.1
@@ -663,7 +633,7 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
663 case sbool :: SBool (FoldLenient mods) of
633 case sbool :: SBool (FoldLenient mods) of
664 STrue -> return mrqbody
634 STrue -> return mrqbody
665 SFalse -> case mrqbody of
635 SFalse -> case mrqbody of
666− Left e -> delayedFailFatal $ formatError rep request e
636+ Left e -> delayedFailFatal err400 { errBody = cs e }
667 Right v -> return v
637 Right v -> return v
@@ -686,7 +656,7 @@ instance
686 bodyCheck fromRS = withRequest $ \req -> do
656 bodyCheck fromRS = withRequest $ \req -> do
687 let mimeUnrender' = mimeUnrender (Proxy :: Proxy ctype) :: BL.ByteString -> Either String chunk
657 let mimeUnrender' = mimeUnrender (Proxy :: Proxy ctype) :: BL.ByteString -> Either String chunk
688 let framingUnrender' = framingUnrender (Proxy :: Proxy framing) mimeUnrender' :: SourceIO B.ByteString -> SourceIO chunk
658 let framingUnrender' = framingUnrender (Proxy :: Proxy framing) mimeUnrender' :: SourceIO B.ByteString -> SourceIO chunk
689− let body = getRequestBodyChunk req
659+ let body = requestBody req
690 let rs = S.fromAction B.null body
660 let rs = S.fromAction B.null body
691 let rs' = fromRS $ framingUnrender' rs
661 let rs' = fromRS $ framingUnrender' rs
692 return rs'
662 return rs'
@@ -755,12 +725,12 @@ data EmptyServer = EmptyServer deriving (Typeable, Eq, Show, Bounded, Enum)
755emptyServer :: ServerT EmptyAPI m
725emptyServer :: ServerT EmptyAPI m
756emptyServer = Tagged EmptyServer
726emptyServer = Tagged EmptyServer
758−-- | The server for an `EmptyAPI` is `emptyServer`.
728+-- | The server for an `EmptyAPI` is `emptyAPIServer`.
760-- > type MyApi = "nothing" :> EmptyApi
730-- > type MyApi = "nothing" :> EmptyApi
762-- > server :: Server MyApi
732-- > server :: Server MyApi
763−-- > server = emptyServer
733+-- > server = emptyAPIServer
764instance HasServer EmptyAPI context where
734instance HasServer EmptyAPI context where
765 type ServerT EmptyAPI m = Tagged m EmptyServer
735 type ServerT EmptyAPI m = Tagged m EmptyServer
@@ -789,10 +759,7 @@ instance ( KnownSymbol realm
789-- * helpers
759-- * helpers
791ct_wildcard :: B.ByteString
761ct_wildcard :: B.ByteString
792−ct_wildcard = "*" <> "/" <> "*"
762+ct_wildcard = "*" <> "/" <> "*" -- Because CPP
794−getAcceptHeader :: Request -> AcceptHeader
795−getAcceptHeader = AcceptHeader . fromMaybe ct_wildcard . lookup hAccept . requestHeaders
797-- * General Authentication
764-- * General Authentication
@@ -817,15 +784,39 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
817 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
784 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
819-------------------------------------------------------------------------------
786-------------------------------------------------------------------------------
820−-- Custom type errors
787+-- TypeError helpers
821-------------------------------------------------------------------------------
788-------------------------------------------------------------------------------
823−-- Erroring instance for 'HasServer' when a combinator is not fully applied
790+#ifdef HAS_TYPE_ERROR
791+-- | This instance catches mistakes when there are non-saturated
792+-- type applications on LHS of ':>'.
794+-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
796+-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
797+-- ...Maybe you haven't applied enough arguments to
798+-- ...Capture' '[] "foo"
801+-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
803+-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
804+-- ...Maybe you haven't applied enough arguments to
805+-- ...Capture' '[] "foo"
824−instance TypeError (PartialApplication HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
808+instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
826− type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
810+ type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
827− route = error "unreachable"
811+ -- it doens't really matter what sub route we peak
812+ route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
828− hoistServerWithContext _ _ _ _ = error "unreachable"
813+ hoistServerWithContext _ _ _ = id
815+-- Cannot have TypeError here, otherwise use of this symbol will error :)
816+type HasServerArrowKindError arr =
817+ 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
818+ ':$$: 'Text "Maybe you haven't applied enough arguments to"
830-- | This instance prevents from accidentally using '->' instead of ':>'
821-- | This instance prevents from accidentally using '->' instead of ':>'
@@ -859,103 +850,7 @@ type HasServerArrowTypeError a b =
859 ':$$: 'ShowType a
850 ':$$: 'ShowType a
860 ':$$: 'Text "and"
851 ':$$: 'Text "and"
861 ':$$: 'ShowType b
852 ':$$: 'ShowType b
863−-- Erroring instances for 'HasServer' for unknown API combinators
865−-- XXX: This omits the @context@ parameter, e.g.:
867−-- "There is no instance for HasServer (Bool :> …)". Do we care ?
868−instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub HasServer ty) => HasServer (ty :> sub) context
870−instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
872−-- | Ignore @'Fragment'@ in server handlers.
873−-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
877−-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
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
887− route _ = route (Proxy :: Proxy api)
889− hoistServerWithContext _ = hoistServerWithContext (Proxy :: Proxy api)
892-- >>> import Servant
856-- >>> import Servant
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
899−type AsServer = AsServerT Handler
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)))
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.
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.
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.
921−class GServer (api :: * -> *) (m :: * -> *) where
922− gServerProof :: Dict (GServerConstraints api m)
925− ( ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m
926− , GServantProduct (Rep (api (AsServerT m)))
927− ) => GServer api m where
931− ( HasServer (ToServantApi api) context
932− , forall m. Generic (api (AsServerT m))
933− , forall m. GServer api m
934− ) => HasServer (NamedRoutes api) context where
936− type ServerT (NamedRoutes api) m = api (AsServerT m)
939− :: Proxy (NamedRoutes api)
941− -> Delayed env (api (AsServerT Handler))
943− route _ ctx delayed =
944− case gServerProof @api @Handler of
945− Dict -> route (Proxy @(ToServantApi api)) ctx (toServant <$> delayed)
947− hoistServerWithContext
948− :: forall m n. Proxy (NamedRoutes api)
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
956− fromServant servantSrvN
958− servantSrvM :: ServerT (ToServantApi api) m =
960− servantSrvN :: ServerT (ToServantApi api) n =
961− hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM