@@ -22,15 +22,17 @@ module Servant.Server.UVerb
2222 )
2323where
2424
25+import qualified Data.ByteString as B
2526import Data.Proxy (Proxy (Proxy))
2627import Data.SOP (I (I))
2728import Data.SOP.Constraint (All, And)
2829import Data.String.Conversions (LBS, cs)
29−import Network.HTTP.Types (Status, hContentType)
30−import Network.Wai (responseLBS)
30+import Network.HTTP.Types (Status, HeaderName, hContentType)
31+import Network.Wai (responseLBS, Request)
3132import Servant.API (ReflectMethod, reflectMethod)
3233import Servant.API.ContentTypes (AllCTRender (handleAcceptH), AllMime)
33−import Servant.API.UVerb (HasStatus, IsMember, Statuses, UVerb, Union, Unique, foldMapUnion, inject, statusOf)
34+import Servant.API.ResponseHeaders (GetHeaders (..), Headers (..))
35+import Servant.API.UVerb (HasStatus, IsMember, Statuses, UVerb, Union, Unique, WithStatus (..), foldMapUnion, inject, statusOf)
3436import Servant.Server.Internal (Context, Delayed, Handler, HasServer (..), RouteResult (FailFatal, Route), Router, Server, ServerT, acceptCheck, addAcceptCheck, addMethodCheck, allowedMethodHead, err406, getAcceptHeader, leafRouter, methodCheck, runAction)
3537
3638
@@ -43,13 +45,38 @@ respond ::
4345 f (Union xs)
4446respond = pure . inject . I
4547
46−-- | Helper constraint used in @instance 'HasServer' 'UVerb'@.
47−type IsServerResource contentTypes = AllCTRender contentTypes `And` HasStatus
48+class IsServerResource (cts :: [*]) a where
49+ resourceResponse :: Request -> Proxy cts -> a -> Maybe (LBS, LBS)
50+ resourceHeaders :: Proxy cts -> a -> [(HeaderName, B.ByteString)]
51+
52+instance {-# OVERLAPPABLE #-} AllCTRender cts a
53+ => IsServerResource cts a where
54+ resourceResponse request p res = handleAcceptH p (getAcceptHeader request) res
55+ resourceHeaders _ _ = []
56+
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)
61+
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
66+
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)
73+
74+type IsServerResourceWithStatus cts = IsServerResource cts `And` HasStatus
4875
4976instance
5077 ( ReflectMethod method,
5178 AllMime contentTypes,
52− All (IsServerResource contentTypes) as,
79+ All (IsServerResourceWithStatus contentTypes) as,
5380 Unique (Statuses as) -- for consistency with servant-swagger (server would work fine
5481 -- without; client is a bit of a corner case, because it dispatches
5582 -- the parser based on the status code. with this uniqueness
@@ -77,20 +104,13 @@ instance
77104 action
78105 `addMethodCheck` methodCheck method request
79106 `addAcceptCheck` acceptCheck (Proxy @contentTypes) (getAcceptHeader request)
80− mkProxy :: a -> Proxy a
81− mkProxy _ = Proxy
82107
83108 runAction action' env request cont $ \(output :: Union as) -> do
84− let encodeResource :: (AllCTRender contentTypes a, HasStatus a) => a -> (Status, Maybe (LBS, LBS))
85− encodeResource res =
86− ( statusOf $ mkProxy res,
87− handleAcceptH (Proxy @contentTypes) (getAcceptHeader request) res
88− )
89− pickResource :: Union as -> (Status, Maybe (LBS, LBS))
90− pickResource = foldMapUnion (Proxy @(IsServerResource contentTypes)) encodeResource
91−
109+ let cts = Proxy @contentTypes
110+ pickResource :: Union as -> (Status, Maybe (LBS, LBS), [(HeaderName, B.ByteString)])
111+ pickResource = foldMapUnion (Proxy @(IsServerResourceWithStatus contentTypes)) (encodeResource request cts)
92112 case pickResource output of
93− (_, Nothing) -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
94− (status, Just (contentT, body)) ->
113+ (_, Nothing, _) -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
114+ (status, Just (contentT, body), headers) ->
95115 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