@@ -31,6 +31,12 @@ module Servant.Server.Internal
3131 , module Servant.Server.Internal.ServerError
3232 ) where
3333
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)
39+
3440import Control.Monad
3541 (join, when)
3642import Control.Monad.Trans
@@ -41,7 +47,7 @@ import qualified Data.ByteString as B
4147import qualified Data.ByteString.Builder as BB
4248import qualified Data.ByteString.Char8 as BC8
4349import qualified Data.ByteString.Lazy as BL
44−import Data.Constraint (Constraint, Dict(..))
50+import Data.Constraint (Dict(..))
4551import Data.Either
4652 (partitionEithers)
4753import Data.Maybe
@@ -76,7 +82,9 @@ import Servant.API
7682 QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
7783 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
7884 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
79− WithNamedContext, NamedRoutes)
85+ WithNamedContext, NamedRoutes, UVerb, WithStatus(..))
86+
87+import Servant.API.UVerb (HasStatus, IsMember, Statuses, UVerb, Union, Unique, WithStatus (..), foldMapUnion, inject, statusOf)
8088import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
8189import Servant.API.ContentTypes
8290 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -88,7 +96,7 @@ import Servant.API.Modifiers
8896import Servant.API.ResponseHeaders
8997 (GetHeaders, Headers, getHeaders, getResponse)
9098import Servant.API.Status
91− (statusFromNat)
99+ (statusFromNat, KnownStatus)
92100import qualified Servant.Types.SourceT as S
93101import Servant.API.TypeErrors
94102import Web.HttpApiData
@@ -173,7 +181,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
173181-- > server = getBook
174182-- > where getBook :: Text -> Handler Book
175183-- > getBook isbn = ...
176−instance (KnownSymbol capture, FromHttpApiData a, Typeable a
184+instance (KnownSymbol capture, FromHttpApiData a
177185 , HasServer api context, SBoolI (FoldLenient mods)
178186 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
179187 )
@@ -185,7 +193,7 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
185193 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
186194
187195 route Proxy context d =
188− CaptureRouter [hint] $
196+ CaptureRouter $
189197 route (Proxy :: Proxy api)
190198 context
191199 (addCapture d $ \ txt -> withRequest $ \ request ->
@@ -197,7 +205,6 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
197205 where
198206 rep = typeRep (Proxy :: Proxy Capture')
199207 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
200− hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
201208
202209-- | If you use 'CaptureAll' in one of the endpoints for your API,
203210-- this automatically requires your server-side handler to be a
@@ -216,7 +223,7 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
216223-- > server = getSourceFile
217224-- > where getSourceFile :: [Text] -> Handler Book
218225-- > getSourceFile pathSegments = ...
219−instance (KnownSymbol capture, FromHttpApiData a, Typeable a
226+instance (KnownSymbol capture, FromHttpApiData a
220227 , HasServer api context
221228 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
222229 )
@@ -228,7 +235,7 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
228235 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
229236
230237 route Proxy context d =
231− CaptureAllRouter [hint] $
238+ CaptureAllRouter $
232239 route (Proxy :: Proxy api)
233240 context
234241 (addCapture d $ \ txts -> withRequest $ \ request ->
@@ -239,7 +246,6 @@ instance (KnownSymbol capture, FromHttpApiData a, Typeable a
239246 where
240247 rep = typeRep (Proxy :: Proxy CaptureAll)
241248 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
242− hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
243249
244250allowedMethodHead :: Method -> Request -> Bool
245251allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -294,28 +300,36 @@ noContentRouter method status action = leafRouter route'
294300 env request respond $ \ _output ->
295301 Route $ responseLBS status [] ""
296302
297−instance {-# OVERLAPPABLE #-}
298− ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
299− ) => HasServer (Verb method status ctypes a) context where
303+-- instance {-# OVERLAPPABLE #-}
304+-- ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
305+-- ) => HasServer (Verb method status ctypes a) context where
300306
301− type ServerT (Verb method status ctypes a) m = m a
302− hoistServerWithContext _ _ nt s = nt s
307+-- type ServerT (Verb method status ctypes a) m = m a
308+-- hoistServerWithContext _ _ nt s = nt s
303309
304− route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
305− where method = reflectMethod (Proxy :: Proxy method)
306− status = statusFromNat (Proxy :: Proxy status)
310+-- route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
311+-- where method = reflectMethod (Proxy :: Proxy method)
312+-- status = statusFromNat (Proxy :: Proxy status)
307313
308−instance {-# OVERLAPPING #-}
309− ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
310− , GetHeaders (Headers h a)
311− ) => HasServer (Verb method status ctypes (Headers h a)) context where
314+-- instance {-# OVERLAPPING #-}
315+-- ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
316+-- , GetHeaders (Headers h a)
317+-- ) => HasServer (Verb method status ctypes (Headers h a)) context where
312318
313− type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
314− hoistServerWithContext _ _ nt s = nt s
319+-- type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
320+-- hoistServerWithContext _ _ nt s = nt s
315321
316− route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
317− where method = reflectMethod (Proxy :: Proxy method)
318− status = statusFromNat (Proxy :: Proxy status)
322+-- route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
323+-- where method = reflectMethod (Proxy :: Proxy method)
324+-- status = statusFromNat (Proxy :: Proxy status)
325+
326+instance
327+ (KnownStatus statusCode, HasServer (UVerb method ctypes '[WithStatus statusCode a]) context) =>
328+ HasServer (Verb method statusCode ctypes a) context where
329+
330+ type ServerT (Verb method statusCode ctypes a) m = m a
331+ route = undefined
332+ hoistServerWithContext p1 p2 nat s = undefined
319333
320334instance (ReflectMethod method) =>
321335 HasServer (NoContentVerb method) context where
@@ -823,7 +837,7 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
823837-- Erroring instance for 'HasServer' when a combinator is not fully applied
824838instance TypeError (PartialApplication HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
825839 where
826− type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
840+ type ServerT (arr :> sub) _ = TypeError (PartialApplication HasServer arr)
827841 route = error "unreachable"
828842 hoistServerWithContext _ _ _ _ = error "unreachable"
829843
@@ -959,3 +973,84 @@ instance
959973 toServant server
960974 servantSrvN :: ServerT (ToServantApi api) n =
961975 hoistServerWithContext (Proxy @(ToServantApi api)) pctx nat servantSrvM
976+
977+
978+
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').
981+respond ::
982+ forall (x :: *) (xs :: [*]) (f :: * -> *).
983+ (Applicative f, HasStatus x, IsMember x xs) =>
984+ x ->
985+ f (Union xs)
986+respond = pure . inject . I
987+
988+class IsServerResource (cts :: [*]) a where
989+ resourceResponse :: Request -> Proxy cts -> a -> Maybe (LBS, LBS)
990+ resourceHeaders :: Proxy cts -> a -> [(HeaderName, B.ByteString)]
991+
992+instance {-# OVERLAPPABLE #-} AllCTRender cts a
993+ => IsServerResource cts a where
994+ resourceResponse request p res = handleAcceptH p (getAcceptHeader request) res
995+ resourceHeaders _ _ = []
996+
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)
1001+
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
1006+
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)
1013+
1014+type IsServerResourceWithStatus cts = IsServerResource cts `And` HasStatus
1015+
1016+instance
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
1024+ -- corner cases.
1025+ ) =>
1026+ HasServer (UVerb method contentTypes as) context
1027+ where
1028+ type ServerT (UVerb method contentTypes as) m = m (Union as)
1029+
1030+ hoistServerWithContext _ _ nt s = nt s
1031+
1032+ route ::
1033+ forall env.
1034+ Proxy (UVerb method contentTypes as) ->
1035+ Context context ->
1036+ Delayed env (Server (UVerb method contentTypes as)) ->
1037+ Router env
1038+ route _proxy _ctx action = leafRouter route'
1039+ where
1040+ method = reflectMethod (Proxy @method)
1041+ route' env request cont = do
1042+ let action' :: Delayed env (Handler (Union as))
1043+ action' =
1044+ action
1045+ `addMethodCheck` methodCheck method request
1046+ `addAcceptCheck` acceptCheck (Proxy @contentTypes) (getAcceptHeader request)
1047+
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