@@ -22,15 +22,17 @@ module Servant.Server.UVerb
25+import qualified Data.ByteString as B
25import Data.Proxy (Proxy (Proxy))
26import Data.Proxy (Proxy (Proxy))
26import Data.SOP (I (I))
27import Data.SOP (I (I))
27import Data.SOP.Constraint (All, And)
28import Data.SOP.Constraint (All, And)
28import Data.String.Conversions (LBS, cs)
29import Data.String.Conversions (LBS, cs)
29−import Network.HTTP.Types (Status, hContentType)
30+import Network.HTTP.Types (Status, HeaderName, hContentType)
30−import Network.Wai (responseLBS)
31+import Network.Wai (responseLBS, Request)
31import Servant.API (ReflectMethod, reflectMethod)
32import Servant.API (ReflectMethod, reflectMethod)
32import Servant.API.ContentTypes (AllCTRender (handleAcceptH), AllMime)
33import Servant.API.ContentTypes (AllCTRender (handleAcceptH), AllMime)
34+import Servant.API.ResponseHeaders (GetHeaders (..), Headers (..))
33−import Servant.API.UVerb (HasStatus, IsMember, Statuses, UVerb, Union, Unique, foldMapUnion, inject, statusOf)
35+import Servant.API.UVerb (HasStatus, IsMember, Statuses, UVerb, Union, Unique, WithStatus (..), foldMapUnion, inject, statusOf)
34import Servant.Server.Internal (Context, Delayed, Handler, HasServer (..), RouteResult (FailFatal, Route), Router, Server, ServerT, acceptCheck, addAcceptCheck, addMethodCheck, allowedMethodHead, err406, getAcceptHeader, leafRouter, methodCheck, runAction)
36import Servant.Server.Internal (Context, Delayed, Handler, HasServer (..), RouteResult (FailFatal, Route), Router, Server, ServerT, acceptCheck, addAcceptCheck, addMethodCheck, allowedMethodHead, err406, getAcceptHeader, leafRouter, methodCheck, runAction)
@@ -43,13 +45,38 @@ respond ::
43 f (Union xs)
45 f (Union xs)
44respond = pure . inject . I
46respond = pure . inject . I
46−-- | Helper constraint used in @instance 'HasServer' 'UVerb'@.
48+class IsServerResource (cts :: [*]) a where
47−type IsServerResource contentTypes = AllCTRender contentTypes `And` HasStatus
49+ resourceResponse :: Request -> Proxy cts -> a -> Maybe (LBS, LBS)
50+ resourceHeaders :: Proxy cts -> a -> [(HeaderName, B.ByteString)]
52+instance {-# OVERLAPPABLE #-} AllCTRender cts a
53+ => IsServerResource cts a where
54+ resourceResponse request p res = handleAcceptH p (getAcceptHeader request) res
55+ resourceHeaders _ _ = []
57+instance {-# OVERLAPPING #-} (IsServerResource cts a, GetHeaders (Headers h a))
58+ => IsServerResource cts (Headers h a) where
59+ resourceResponse request p res = resourceResponse request p (getResponse res)
60+ resourceHeaders cts res = getHeaders res ++ resourceHeaders cts (getResponse res)
62+instance {-# OVERLAPPING #-} IsServerResource cts a
63+ => IsServerResource cts (WithStatus n a) where
64+ resourceResponse request p (WithStatus x) = resourceResponse request p x
65+ resourceHeaders cts (WithStatus x) = resourceHeaders cts x
67+encodeResource :: forall a cts . (IsServerResource cts a, HasStatus a)
68+ => Request -> Proxy cts -> a
69+ -> (Status, Maybe (LBS, LBS), [(HeaderName, B.ByteString)])
70+encodeResource request cts res = (statusOf (Proxy @a),
71+ resourceResponse request cts res,
72+ resourceHeaders cts res)
74+type IsServerResourceWithStatus cts = IsServerResource cts `And` HasStatus
50 ( ReflectMethod method,
77 ( ReflectMethod method,
51 AllMime contentTypes,
78 AllMime contentTypes,
52− All (IsServerResource contentTypes) as,
79+ All (IsServerResourceWithStatus contentTypes) as,
53 Unique (Statuses as) -- for consistency with servant-swagger (server would work fine
80 Unique (Statuses as) -- for consistency with servant-swagger (server would work fine
54 -- without; client is a bit of a corner case, because it dispatches
81 -- without; client is a bit of a corner case, because it dispatches
55 -- the parser based on the status code. with this uniqueness
82 -- the parser based on the status code. with this uniqueness
@@ -77,20 +104,13 @@ instance
78 `addMethodCheck` methodCheck method request
105 `addMethodCheck` methodCheck method request
79 `addAcceptCheck` acceptCheck (Proxy @contentTypes) (getAcceptHeader request)
106 `addAcceptCheck` acceptCheck (Proxy @contentTypes) (getAcceptHeader request)
80− mkProxy :: a -> Proxy a
83 runAction action' env request cont $ \(output :: Union as) -> do
108 runAction action' env request cont $ \(output :: Union as) -> do
84− let encodeResource :: (AllCTRender contentTypes a, HasStatus a) => a -> (Status, Maybe (LBS, LBS))
86− ( statusOf $ mkProxy res,
87− handleAcceptH (Proxy @contentTypes) (getAcceptHeader request) res
109+ let cts = Proxy @contentTypes
89− pickResource :: Union as -> (Status, Maybe (LBS, LBS))
110+ pickResource :: Union as -> (Status, Maybe (LBS, LBS), [(HeaderName, B.ByteString)])
90− pickResource = foldMapUnion (Proxy @(IsServerResource contentTypes)) encodeResource
111+ pickResource = foldMapUnion (Proxy @(IsServerResourceWithStatus contentTypes)) (encodeResource request cts)
92 case pickResource output of
112 case pickResource output of
93− (_, Nothing) -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
113+ (_, Nothing, _) -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
94− (status, Just (contentT, body)) ->
114+ (status, Just (contentT, body), headers) ->
95 let bdy = if allowedMethodHead method request then "" else body
115 let bdy = if allowedMethodHead method request then "" else body
96− in Route $ responseLBS status ((hContentType, cs contentT) : []) bdy
116+ in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy