@@ -24,6 +24,7 @@ module Servant.Server.Internal
2424 , module Servant.Server.Internal.Context
2525 , module Servant.Server.Internal.Delayed
2626 , module Servant.Server.Internal.DelayedIO
27+ , module Servant.Server.Internal.ErrorFormatter
2728 , module Servant.Server.Internal.Handler
2829 , module Servant.Server.Internal.Router
2930 , module Servant.Server.Internal.RouteResult
@@ -95,6 +96,7 @@ import Servant.Server.Internal.BasicAuth
9596import Servant.Server.Internal.Context
9697import Servant.Server.Internal.Delayed
9798import Servant.Server.Internal.DelayedIO
99+import Servant.Server.Internal.ErrorFormatter
98100import Servant.Server.Internal.Handler
99101import Servant.Server.Internal.Router
100102import Servant.Server.Internal.RouteResult
@@ -168,7 +170,10 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
168170-- > server = getBook
169171-- > where getBook :: Text -> Handler Book
170172-- > getBook isbn = ...
171−instance (KnownSymbol capture, FromHttpApiData a, HasServer api context, SBoolI (FoldLenient mods))
173+instance (KnownSymbol capture, FromHttpApiData a
174+ , HasServer api context, SBoolI (FoldLenient mods)
175+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
176+ )
172177 => HasServer (Capture' mods capture a :> api) context where
173178
174179 type ServerT (Capture' mods capture a :> api) m =
@@ -180,12 +185,15 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context, SBoolI
180185 CaptureRouter $
181186 route (Proxy :: Proxy api)
182187 context
183− (addCapture d $ \ txt -> case ( sbool :: SBool (FoldLenient mods)
184− , parseUrlPiece txt :: Either T.Text a) of
185− (SFalse, Left e) -> delayedFail err400 { errBody = cs e }
186− (SFalse, Right v) -> return v
187− (STrue, piece) -> return $ (either (Left . cs) Right) piece
188− )
188+ (addCapture d $ \ txt -> withRequest $ \ request ->
189+ case ( sbool :: SBool (FoldLenient mods)
190+ , parseUrlPiece txt :: Either T.Text a) of
191+ (SFalse, Left e) -> delayedFail $ formatError rep request $ cs e
192+ (SFalse, Right v) -> return v
193+ (STrue, piece) -> return $ (either (Left . cs) Right) piece)
194+ where
195+ rep = typeRep (Proxy :: Proxy Capture')
196+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
189197
190198-- | If you use 'CaptureAll' in one of the endpoints for your API,
191199-- this automatically requires your server-side handler to be a
@@ -204,7 +212,10 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context, SBoolI
204212-- > server = getSourceFile
205213-- > where getSourceFile :: [Text] -> Handler Book
206214-- > getSourceFile pathSegments = ...
207−instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
215+instance (KnownSymbol capture, FromHttpApiData a
216+ , HasServer api context
217+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
218+ )
208219 => HasServer (CaptureAll capture a :> api) context where
209220
210221 type ServerT (CaptureAll capture a :> api) m =
@@ -216,11 +227,14 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
216227 CaptureAllRouter $
217228 route (Proxy :: Proxy api)
218229 context
219− (addCapture d $ \ txts -> case parseUrlPieces txts of
220− Left _ -> delayedFail err400
221− Right v -> return v
230+ (addCapture d $ \ txts -> withRequest $ \ request ->
231+ case parseUrlPieces txts of
232+ Left e -> delayedFail $ formatError rep request $ cs e
233+ Right v -> return v
222234 )
223−
235+ where
236+ rep = typeRep (Proxy :: Proxy CaptureAll)
237+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
224238
225239allowedMethodHead :: Method -> Request -> Bool
226240allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -240,10 +254,10 @@ methodCheck method request
240254-- body check is no longer an option. However, we now run the accept
241255-- check before the body check and can therefore afford to make it
242256-- recoverable.
243−acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
257+acceptCheck :: (AllMime list) => Proxy list -> AcceptHeader -> DelayedIO ()
244258acceptCheck proxy accH
245− | canHandleAcceptH proxy (AcceptHeader accH) = return ()
246− | otherwise = delayedFail err406
259+ | canHandleAcceptH proxy accH = return ()
260+ | otherwise = delayedFail err406
247261
248262methodRouter :: (AllCTRender ctypes a)
249263 => (b -> ([(HeaderName, B.ByteString)], a))
@@ -253,12 +267,12 @@ methodRouter :: (AllCTRender ctypes a)
253267methodRouter splitHeaders method proxy status action = leafRouter route'
254268 where
255269 route' env request respond =
256− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
270+ let accH = getAcceptHeader request
257271 in runAction (action `addMethodCheck` methodCheck method request
258272 `addAcceptCheck` acceptCheck proxy accH
259273 ) env request respond $ \ output -> do
260274 let (headers, b) = splitHeaders output
261− case handleAcceptH proxy (AcceptHeader accH) b of
275+ case handleAcceptH proxy accH b of
262276 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
263277 Just (contentT, body) ->
264278 let bdy = if allowedMethodHead method request then "" else body
@@ -343,7 +357,7 @@ streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, Fra
343357 -> Delayed env (Handler c)
344358 -> Router env
345359streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRouter $ \env request respond ->
346− let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
360+ let AcceptHeader accH = getAcceptHeader request
347361 cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
348362 accCheck = when (isNothing cmediatype) $ delayedFail err406
349363 contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
@@ -388,6 +402,7 @@ streamRouter splitHeaders method status framingproxy ctypeproxy action = leafRou
388402instance
389403 (KnownSymbol sym, FromHttpApiData a, HasServer api context
390404 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
405+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
391406 )
392407 => HasServer (Header' mods sym a :> api) context where
393408------
@@ -399,6 +414,9 @@ instance
399414 route Proxy context subserver = route (Proxy :: Proxy api) context $
400415 subserver `addHeaderCheck` withRequest headerCheck
401416 where
417+ rep = typeRep (Proxy :: Proxy Header')
418+ formatError = headerParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
419+
402420 headerName :: IsString n => n
403421 headerName = fromString $ symbolVal (Proxy :: Proxy sym)
404422
@@ -409,15 +427,13 @@ instance
409427 mev :: Maybe (Either T.Text a)
410428 mev = fmap parseHeader $ lookup headerName (requestHeaders req)
411429
412− errReq = delayedFailFatal err400
413− { errBody = "Header " <> headerName <> " is required"
414− }
430+ errReq = delayedFailFatal $ formatError rep req
431+ $ "Header " <> headerName <> " is required"
415432
416− errSt e = delayedFailFatal err400
417− { errBody = cs $ "Error parsing header "
418− <> headerName
419− <> " failed: " <> e
420− }
433+ errSt e = delayedFailFatal $ formatError rep req
434+ $ cs $ "Error parsing header "
435+ <> headerName
436+ <> " failed: " <> e
421437
422438-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
423439-- this automatically requires your server-side handler to be a function
@@ -443,6 +459,7 @@ instance
443459instance
444460 ( KnownSymbol sym, FromHttpApiData a, HasServer api context
445461 , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
462+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
446463 )
447464 => HasServer (QueryParam' mods sym a :> api) context where
448465------
@@ -455,6 +472,9 @@ instance
455472 let querytext = queryToQueryText . queryString
456473 paramname = cs $ symbolVal (Proxy :: Proxy sym)
457474
475+ rep = typeRep (Proxy :: Proxy QueryParam')
476+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
477+
458478 parseParam :: Request -> DelayedIO (RequestArgument mods a)
459479 parseParam req =
460480 unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
@@ -462,14 +482,12 @@ instance
462482 mev :: Maybe (Either T.Text a)
463483 mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
464484
465− errReq = delayedFailFatal err400
466− { errBody = cs $ "Query parameter " <> paramname <> " is required"
467− }
485+ errReq = delayedFailFatal $ formatError rep req
486+ $ cs $ "Query parameter " <> paramname <> " is required"
468487
469− errSt e = delayedFailFatal err400
470− { errBody = cs $ "Error parsing query parameter "
471− <> paramname <> " failed: " <> e
472− }
488+ errSt e = delayedFailFatal $ formatError rep req
489+ $ cs $ "Error parsing query parameter "
490+ <> paramname <> " failed: " <> e
473491
474492 delayed = addParameterCheck subserver . withRequest $ \req ->
475493 parseParam req
@@ -495,7 +513,8 @@ instance
495513-- > server = getBooksBy
496514-- > where getBooksBy :: [Text] -> Handler [Book]
497515-- > getBooksBy authors = ...return all books by these authors...
498−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
516+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context
517+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters)
499518 => HasServer (QueryParams sym a :> api) context where
500519
501520 type ServerT (QueryParams sym a :> api) m =
@@ -506,21 +525,23 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
506525 route Proxy context subserver = route (Proxy :: Proxy api) context $
507526 subserver `addParameterCheck` withRequest paramsCheck
508527 where
528+ rep = typeRep (Proxy :: Proxy QueryParams)
529+ formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
530+
509531 paramname = cs $ symbolVal (Proxy :: Proxy sym)
510532 paramsCheck req =
511533 case partitionEithers $ fmap parseQueryParam params of
512534 ([], parsed) -> return parsed
513− (errs, _) -> delayedFailFatal err400
514− { errBody = cs $ "Error parsing query parameter(s) "
515− <> paramname <> " failed: "
516− <> T.intercalate ", " errs
517− }
535+ (errs, _) -> delayedFailFatal $ formatError rep req
536+ $ cs $ "Error parsing query parameter(s) "
537+ <> paramname <> " failed: "
538+ <> T.intercalate ", " errs
518539 where
519540 params :: [T.Text]
520541 params = mapMaybe snd
521542 . filter (looksLikeParam . fst)
522− . queryToQueryText
523− . queryString
543+ . queryToQueryText
544+ . queryString
524545 $ req
525546
526547 looksLikeParam name = name == paramname || name == (paramname <> "[]")
@@ -588,7 +609,7 @@ instance HasServer Raw context where
588609-- The @Content-Type@ header is inspected, and the list provided is used to
589610-- attempt deserialization. If the request does not have a @Content-Type@
590611-- header, it is treated as @application/octet-stream@ (as specified in
591−-- <http://tools.ietf.org/html/rfc7231#section-3.1.1.5 RFC7231>.
612+-- [RFC 7231 section 3.1.1.5](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)).
592613-- This lets servant worry about extracting it from the request and turning
593614-- it into a value of the type you specify.
594615--
@@ -604,6 +625,7 @@ instance HasServer Raw context where
604625-- > where postBook :: Book -> Handler Book
605626-- > postBook book = ...insert into your db...
606627instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
628+ , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
607629 ) => HasServer (ReqBody' mods list a :> api) context where
608630
609631 type ServerT (ReqBody' mods list a :> api) m =
@@ -615,6 +637,9 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
615637 = route (Proxy :: Proxy api) context $
616638 addBodyCheck subserver ctCheck bodyCheck
617639 where
640+ rep = typeRep (Proxy :: Proxy ReqBody')
641+ formatError = bodyParserErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
642+
618643 -- Content-Type check, we only lookup we can try to parse the request body
619644 ctCheck = withRequest $ \ request -> do
620645 -- See HTTP RFC 2616, section 7.2.1
@@ -633,7 +658,7 @@ instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods
633658 case sbool :: SBool (FoldLenient mods) of
634659 STrue -> return mrqbody
635660 SFalse -> case mrqbody of
636− Left e -> delayedFailFatal err400 { errBody = cs e }
661+ Left e -> delayedFailFatal $ formatError rep request e
637662 Right v -> return v
638663
639664instance
@@ -761,6 +786,9 @@ instance ( KnownSymbol realm
761786ct_wildcard :: B.ByteString
762787ct_wildcard = "*" <> "/" <> "*" -- Because CPP
763788
789+getAcceptHeader :: Request -> AcceptHeader
790+getAcceptHeader = AcceptHeader . fromMaybe ct_wildcard . lookup hAccept . requestHeaders
791+
764792-- * General Authentication
765793
766794