@@ -12,6 +12,7 @@
12{-# LANGUAGE TupleSections #-}
12{-# LANGUAGE TupleSections #-}
13{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE TypeFamilies #-}
14{-# LANGUAGE TypeOperators #-}
14{-# LANGUAGE TypeOperators #-}
15+{-# LANGUAGE UndecidableInstances #-}
16#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802
17#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802
17#define HAS_TYPE_ERROR
18#define HAS_TYPE_ERROR
@@ -33,7 +34,7 @@ module Servant.Server.Internal
33 , module Servant.Server.Internal.ServantErr
34 , module Servant.Server.Internal.ServantErr
36−import Control.Monad (when)
37+import Control.Monad (join, when)
37import Control.Monad.Trans (liftIO)
38import Control.Monad.Trans (liftIO)
38import Control.Monad.Trans.Resource (runResourceT)
39import Control.Monad.Trans.Resource (runResourceT)
39import qualified Data.ByteString as B
40import qualified Data.ByteString as B
@@ -43,7 +44,7 @@ import qualified Data.ByteString.Lazy as BL
43import Data.Maybe (fromMaybe, mapMaybe,
44import Data.Maybe (fromMaybe, mapMaybe,
44 isNothing, maybeToList)
45 isNothing, maybeToList)
45import Data.Either (partitionEithers)
46import Data.Either (partitionEithers)
46−import Data.String (fromString)
47+import Data.String (IsString (..))
47import Data.String.Conversions (cs, (<>))
48import Data.String.Conversions (cs, (<>))
48import Data.Tagged (Tagged(..), retag, untag)
49import Data.Tagged (Tagged(..), retag, untag)
49import qualified Data.Text as T
50import qualified Data.Text as T
@@ -69,15 +70,17 @@ import Web.HttpApiData (FromHttpApiData, parseHeader,
69import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
70import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
70 CaptureAll, Verb, EmptyAPI,
71 CaptureAll, Verb, EmptyAPI,
71 ReflectMethod(reflectMethod),
72 ReflectMethod(reflectMethod),
72− IsSecure(..), Header, QueryFlag,
73+ IsSecure(..), Header', QueryFlag,
73− QueryParam, QueryParams, Raw,
74+ QueryParam', QueryParams, Raw,
74− RemoteHost, ReqBody, Vault,
75+ RemoteHost, ReqBody', Vault,
75 WithNamedContext,
76 WithNamedContext,
76 Description, Summary,
77 Description, Summary,
77 Accept(..),
78 Accept(..),
78 FramingRender(..), Stream,
79 FramingRender(..), Stream,
79 StreamGenerator(..), ToStreamGenerator(..),
80 StreamGenerator(..), ToStreamGenerator(..),
80− BoundaryStrategy(..))
81+ BoundaryStrategy(..),
82+ If, SBool (..), SBoolI (..))
83+import Servant.API.Modifiers (unfoldRequestArgument, RequestArgument, FoldRequired, FoldLenient)
81import Servant.API.ContentTypes (AcceptHeader (..),
84import Servant.API.ContentTypes (AcceptHeader (..),
82 AllCTRender (..),
85 AllCTRender (..),
83 AllCTUnrender (..),
86 AllCTUnrender (..),
@@ -361,29 +364,39 @@ streamRouter splitHeaders method framingproxy ctypeproxy action = leafRouter $ \
361-- > server = viewReferer
364-- > server = viewReferer
362-- > where viewReferer :: Referer -> Handler referer
365-- > where viewReferer :: Referer -> Handler referer
363-- > viewReferer referer = return referer
366-- > viewReferer referer = return referer
364−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
368+ (KnownSymbol sym, FromHttpApiData a, HasServer api context
369+ , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
365− => HasServer (Header sym a :> api) context where
371+ => HasServer (Header' mods sym a :> api) context where
367− type ServerT (Header sym a :> api) m =
373+ type ServerT (Header' mods sym a :> api) m =
368− Maybe a -> ServerT api m
374+ RequestArgument mods a -> ServerT api m
370 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
376 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
372 route Proxy context subserver = route (Proxy :: Proxy api) context $
378 route Proxy context subserver = route (Proxy :: Proxy api) context $
373 subserver `addHeaderCheck` withRequest headerCheck
379 subserver `addHeaderCheck` withRequest headerCheck
381+ headerName :: IsString n => n
375− headerName = symbolVal (Proxy :: Proxy sym)
382+ headerName = fromString $ symbolVal (Proxy :: Proxy sym)
384+ headerCheck :: Request -> DelayedIO (RequestArgument mods a)
376 headerCheck req =
385 headerCheck req =
386+ unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
388+ mev :: Maybe (Either T.Text a)
377− case lookup (fromString headerName) (requestHeaders req) of
389+ mev = fmap parseHeader $ lookup headerName (requestHeaders req)
378− Nothing -> return Nothing
390+
379− Just txt ->
391+ errReq = delayedFailFatal err400
380− case parseHeader txt of
392+ { errBody = "Header " <> headerName <> " is required"
381− Left e -> delayedFailFatal err400
395+ errSt e = delayedFailFatal err400
382− { errBody = cs $ "Error parsing header "
396+ { errBody = cs $ "Error parsing header "
383− <> fromString headerName
397+ <> headerName
384− <> " failed: " <> e
398+ <> " failed: " <> e
386− Right hdr -> return $ Just hdr
388-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
401-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
389-- this automatically requires your server-side handler to be a function
402-- this automatically requires your server-side handler to be a function
@@ -406,33 +419,41 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
406-- > where getBooksBy :: Maybe Text -> Handler [Book]
419-- > where getBooksBy :: Maybe Text -> Handler [Book]
407-- > getBooksBy Nothing = ...return all books...
420-- > getBooksBy Nothing = ...return all books...
408-- > getBooksBy (Just author) = ...return books by the given author...
421-- > getBooksBy (Just author) = ...return books by the given author...
409−instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
423+ ( KnownSymbol sym, FromHttpApiData a, HasServer api context
424+ , SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
410− => HasServer (QueryParam sym a :> api) context where
426+ => HasServer (QueryParam' mods sym a :> api) context where
412− type ServerT (QueryParam sym a :> api) m =
428+ type ServerT (QueryParam' mods sym a :> api) m =
413− Maybe a -> ServerT api m
429+ RequestArgument mods a -> ServerT api m
415 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
431 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
417 route Proxy context subserver =
433 route Proxy context subserver =
418 let querytext req = parseQueryText $ rawQueryString req
434 let querytext req = parseQueryText $ rawQueryString req
435+ paramname = cs $ symbolVal (Proxy :: Proxy sym)
437+ parseParam :: Request -> DelayedIO (RequestArgument mods a)
419 parseParam req =
438 parseParam req =
439+ unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
441+ mev :: Maybe (Either T.Text a)
420− case lookup paramname (querytext req) of
442+ mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
421− Nothing -> return Nothing -- param absent from the query string
443+
422− Just Nothing -> return Nothing -- param present with no value -> Nothing
444+ errReq = delayedFailFatal err400
423− Just (Just v) ->
445+ { errBody = cs $ "Query parameter " <> paramname <> " is required"
424− case parseQueryParam v of
446+ }
425− Left e -> delayedFailFatal err400
448+ errSt e = delayedFailFatal err400
426− { errBody = cs $ "Error parsing query parameter "
449+ { errBody = cs $ "Error parsing query parameter "
427− <> paramname <> " failed: " <> e
450+ <> paramname <> " failed: " <> e
430− Right param -> return $ Just param
431 delayed = addParameterCheck subserver . withRequest $ \req ->
453 delayed = addParameterCheck subserver . withRequest $ \req ->
432 parseParam req
454 parseParam req
434 in route (Proxy :: Proxy api) context delayed
456 in route (Proxy :: Proxy api) context delayed
435− where paramname = cs $ symbolVal (Proxy :: Proxy sym)
437-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
458-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
438-- this automatically requires your server-side handler to be a function
459-- this automatically requires your server-side handler to be a function
@@ -561,11 +582,11 @@ instance HasServer Raw context where
561-- > server = postBook
582-- > server = postBook
562-- > where postBook :: Book -> Handler Book
583-- > where postBook :: Book -> Handler Book
563-- > postBook book = ...insert into your db...
584-- > postBook book = ...insert into your db...
564−instance ( AllCTUnrender list a, HasServer api context
585+instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
565− ) => HasServer (ReqBody list a :> api) context where
586+ ) => HasServer (ReqBody' mods list a :> api) context where
567− type ServerT (ReqBody list a :> api) m =
588+ type ServerT (ReqBody' mods list a :> api) m =
568− a -> ServerT api m
589+ If (FoldLenient mods) (Either String a) a -> ServerT api m
570 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
591 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
@@ -588,9 +609,11 @@ instance ( AllCTUnrender list a, HasServer api context
588 -- Body check, we get a body parsing functions as the first argument.
609 -- Body check, we get a body parsing functions as the first argument.
589 bodyCheck f = withRequest $ \ request -> do
610 bodyCheck f = withRequest $ \ request -> do
590 mrqbody <- f <$> liftIO (lazyRequestBody request)
611 mrqbody <- f <$> liftIO (lazyRequestBody request)
612+ case sbool :: SBool (FoldLenient mods) of
613+ STrue -> return mrqbody
591− case mrqbody of
614+ SFalse -> case mrqbody of
592− Left e -> delayedFailFatal err400 { errBody = cs e }
615+ Left e -> delayedFailFatal err400 { errBody = cs e }
593− Right v -> return v
616+ Right v -> return v
595-- | Make sure the incoming request starts with @"/path"@, strip it and
618-- | Make sure the incoming request starts with @"/path"@, strip it and
596-- pass the rest of the request path to @api@.
619-- pass the rest of the request path to @api@.