@@ -31,6 +31,12 @@ module Servant.Server.Internal
31 , module Servant.Server.Internal.ServerError
31 , module Servant.Server.Internal.ServerError
34+import Data.Proxy (Proxy (Proxy))
35+import Data.SOP (I (I))
36+import Data.SOP.Constraint (All, And)
37+import Data.String.Conversions (LBS, cs)
38+import Network.HTTP.Types (Status, HeaderName, hContentType)
34import Control.Monad
40import Control.Monad
35 (join, when)
41 (join, when)
36import Control.Monad.Trans
42import Control.Monad.Trans
@@ -76,7 +82,9 @@ import Servant.API
76 QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
82 QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
77 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
83 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
78 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
84 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
79− WithNamedContext, NamedRoutes)
85+ WithNamedContext, NamedRoutes, UVerb, WithStatus(..))
87+import Servant.API.UVerb (HasStatus, IsMember, Statuses, UVerb, Union, Unique, WithStatus (..), foldMapUnion, inject, statusOf)
80import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
88import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
81import Servant.API.ContentTypes
89import Servant.API.ContentTypes
82 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
90 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -88,7 +96,7 @@ import Servant.API.Modifiers
88import Servant.API.ResponseHeaders
96import Servant.API.ResponseHeaders
89 (GetHeaders, Headers, getHeaders, getResponse)
97 (GetHeaders, Headers, getHeaders, getResponse)
90import Servant.API.Status
98import Servant.API.Status
91− (statusFromNat)
99+ (statusFromNat, KnownStatus)
92import qualified Servant.Types.SourceT as S
100import qualified Servant.Types.SourceT as S
93import Servant.API.TypeErrors
101import Servant.API.TypeErrors
94import Web.HttpApiData
102import Web.HttpApiData
@@ -292,28 +300,36 @@ noContentRouter method status action = leafRouter route'
292 env request respond $ \ _output ->
300 env request respond $ \ _output ->
293 Route $ responseLBS status [] ""
301 Route $ responseLBS status [] ""
295−instance {-# OVERLAPPABLE #-}
303+-- instance {-# OVERLAPPABLE #-}
296− ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
304+-- ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
297− ) => HasServer (Verb method status ctypes a) context where
305+-- ) => HasServer (Verb method status ctypes a) context where
299− type ServerT (Verb method status ctypes a) m = m a
307+-- type ServerT (Verb method status ctypes a) m = m a
300− hoistServerWithContext _ _ nt s = nt s
308+-- hoistServerWithContext _ _ nt s = nt s
302− route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
310+-- route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
303− where method = reflectMethod (Proxy :: Proxy method)
311+-- where method = reflectMethod (Proxy :: Proxy method)
304− status = statusFromNat (Proxy :: Proxy status)
312+-- status = statusFromNat (Proxy :: Proxy status)
306−instance {-# OVERLAPPING #-}
314+-- instance {-# OVERLAPPING #-}
307− ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
315+-- ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
308− , GetHeaders (Headers h a)
316+-- , GetHeaders (Headers h a)
309− ) => HasServer (Verb method status ctypes (Headers h a)) context where
317+-- ) => HasServer (Verb method status ctypes (Headers h a)) context where
311− type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
319+-- type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
312− hoistServerWithContext _ _ nt s = nt s
320+-- hoistServerWithContext _ _ nt s = nt s
314− route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
322+-- route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
315− where method = reflectMethod (Proxy :: Proxy method)
323+-- where method = reflectMethod (Proxy :: Proxy method)
316− status = statusFromNat (Proxy :: Proxy status)
324+-- status = statusFromNat (Proxy :: Proxy status)
327+ (KnownStatus statusCode, HasServer (UVerb method ctypes '[WithStatus statusCode a]) context) =>
328+ HasServer (Verb method statusCode ctypes a) context where
330+ type ServerT (Verb method statusCode ctypes a) m = m a
332+ hoistServerWithContext p1 p2 nat s = undefined
318instance (ReflectMethod method) =>
334instance (ReflectMethod method) =>
319 HasServer (NoContentVerb method) context where
335 HasServer (NoContentVerb method) context where
@@ -957,3 +973,84 @@ instance
957 toServant server
973 toServant server
958 servantSrvN :: ServerT (ToServantApi api) n =
974 servantSrvN :: ServerT (ToServantApi api) n =
959 hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM
975 hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM
979+-- | 'return' for 'UVerb' handlers. Takes a value of any of the members of the open union,
980+-- and will construct a union value in an 'Applicative' (eg. 'Server').
982+ forall (x :: *) (xs :: [*]) (f :: * -> *).
983+ (Applicative f, HasStatus x, IsMember x xs) =>
986+respond = pure . inject . I
988+class IsServerResource (cts :: [*]) a where
989+ resourceResponse :: Request -> Proxy cts -> a -> Maybe (LBS, LBS)
990+ resourceHeaders :: Proxy cts -> a -> [(HeaderName, B.ByteString)]
992+instance {-# OVERLAPPABLE #-} AllCTRender cts a
993+ => IsServerResource cts a where
994+ resourceResponse request p res = handleAcceptH p (getAcceptHeader request) res
995+ resourceHeaders _ _ = []
997+instance {-# OVERLAPPING #-} (IsServerResource cts a, GetHeaders (Headers h a))
998+ => IsServerResource cts (Headers h a) where
999+ resourceResponse request p res = resourceResponse request p (getResponse res)
1000+ resourceHeaders cts res = getHeaders res ++ resourceHeaders cts (getResponse res)
1002+instance {-# OVERLAPPING #-} IsServerResource cts a
1003+ => IsServerResource cts (WithStatus n a) where
1004+ resourceResponse request p (WithStatus x) = resourceResponse request p x
1005+ resourceHeaders cts (WithStatus x) = resourceHeaders cts x
1007+encodeResource :: forall a cts . (IsServerResource cts a, HasStatus a)
1008+ => Request -> Proxy cts -> a
1009+ -> (Status, Maybe (LBS, LBS), [(HeaderName, B.ByteString)])
1010+encodeResource request cts res = (statusOf (Proxy @a),
1011+ resourceResponse request cts res,
1012+ resourceHeaders cts res)
1014+type IsServerResourceWithStatus cts = IsServerResource cts `And` HasStatus
1017+ ( ReflectMethod method,
1018+ AllMime contentTypes,
1019+ All (IsServerResourceWithStatus contentTypes) as,
1020+ Unique (Statuses as) -- for consistency with servant-swagger (server would work fine
1021+ -- without; client is a bit of a corner case, because it dispatches
1022+ -- the parser based on the status code. with this uniqueness
1023+ -- constraint it won't have to run more than one parser in weird
1026+ HasServer (UVerb method contentTypes as) context
1028+ type ServerT (UVerb method contentTypes as) m = m (Union as)
1030+ hoistServerWithContext _ _ nt s = nt s
1034+ Proxy (UVerb method contentTypes as) ->
1036+ Delayed env (Server (UVerb method contentTypes as)) ->
1038+ route _proxy _ctx action = leafRouter route'
1040+ method = reflectMethod (Proxy @method)
1041+ route' env request cont = do
1042+ let action' :: Delayed env (Handler (Union as))
1045+ `addMethodCheck` methodCheck method request
1046+ `addAcceptCheck` acceptCheck (Proxy @contentTypes) (getAcceptHeader request)
1048+ runAction action' env request cont $ \(output :: Union as) -> do
1049+ let cts = Proxy @contentTypes
1050+ pickResource :: Union as -> (Status, Maybe (LBS, LBS), [(HeaderName, B.ByteString)])
1051+ pickResource = foldMapUnion (Proxy @(IsServerResourceWithStatus contentTypes)) (encodeResource request cts)
1052+ case pickResource output of
1053+ (_, Nothing, _) -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
1054+ (status, Just (contentT, body), headers) ->
1055+ let bdy = if allowedMethodHead method request then "" else body
1056+ in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy