@@ -21,26 +21,33 @@ module Servant.Server.Internal
2121#if !MIN_VERSION_base(4,8,0)
2222import Control.Applicative ((<$>))
2323#endif
24−import Control.Monad.Trans.Except (ExceptT)
25−import qualified Data.ByteString as B
26−import qualified Data.ByteString.Lazy as BL
27−import qualified Data.Map as M
28−import Data.Maybe (fromMaybe, mapMaybe)
29−import Data.String (fromString)
30−import Data.String.Conversions (ConvertibleStrings, cs, (<>))
31−import Data.Text (Text)
24+import Control.Monad.Trans.Except (ExceptT)
25+import qualified Data.ByteString as B
26+import qualified Data.ByteString.Lazy as BL
27+import qualified Data.Map as M
28+import Data.Maybe (fromMaybe, mapMaybe)
29+import Data.String (fromString)
30+import Data.String.Conversions (cs, (<>))
31+import Data.Text (Text)
3232import Data.Typeable
33−import GHC.TypeLits (KnownSymbol, symbolVal)
34−import Network.HTTP.Types hiding (Header, ResponseHeaders)
35−import Network.Socket (SockAddr)
36−import Network.Wai (Application, lazyRequestBody,
37− rawQueryString, requestHeaders,
38− requestMethod, responseLBS, remoteHost,
39− isSecure, vault, httpVersion, Response,
40− Request, pathInfo)
33+import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
34+ symbolVal)
35+import Network.HTTP.Types hiding (Header, ResponseHeaders)
36+import Network.Socket (SockAddr)
37+import Network.Wai (Application, Request, Response,
38+ httpVersion, isSecure,
39+ lazyRequestBody, pathInfo,
40+ rawQueryString, remoteHost,
41+ requestHeaders, requestMethod,
42+ responseLBS, vault)
43+import Web.HttpApiData (FromHttpApiData)
44+import Web.HttpApiData.Internal (parseHeaderMaybe,
45+ parseQueryParamMaybe,
46+ parseUrlPieceMaybe)
47+
4148import Servant.API ((:<|>) (..), (:>), Capture,
42− Delete, Get, Header,
43− IsSecure(..), Patch, Post, Put,
49+ Verb, ReflectMethod(reflectMethod),
50+ IsSecure(..), Header,
4451 QueryFlag, QueryParam, QueryParams,
4552 Raw, RemoteHost, ReqBody, Vault)
4653import Servant.API.ContentTypes (AcceptHeader (..),
@@ -55,8 +62,6 @@ import Servant.Server.Internal.Router
5562import Servant.Server.Internal.RoutingApplication
5663import Servant.Server.Internal.ServantErr
5764
58−import Web.HttpApiData (FromHttpApiData)
59−import Web.HttpApiData.Internal (parseUrlPieceMaybe, parseHeaderMaybe, parseQueryParamMaybe)
6065
6166class HasServer layout where
6267 type ServerT layout (m :: * -> *) :: *
@@ -129,12 +134,12 @@ allowedMethodHead method request = method == methodGet && requestMethod request
129134allowedMethod :: Method -> Request -> Bool
130135allowedMethod method request = allowedMethodHead method request || requestMethod request == method
131136
132−processMethodRouter :: forall a. ConvertibleStrings a B.ByteString
133− => Maybe (a, BL.ByteString) -> Status -> Method
137+processMethodRouter :: Maybe (BL.ByteString, BL.ByteString) -> Status -> Method
134138 -> Maybe [(HeaderName, B.ByteString)]
135139 -> Request -> RouteResult Response
136140processMethodRouter handleA status method headers request = case handleA of
137141 Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
142+ Just (_, "") -> Route $ responseLBS status204 (fromMaybe [] headers) ""
138143 Just (contentT, body) -> Route $ responseLBS status hdrs bdy
139144 where
140145 bdy = if allowedMethodHead method request then "" else body
@@ -160,7 +165,7 @@ methodRouter method proxy status action = LeafRouter route'
160165 | pathIsEmpty request =
161166 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
162167 in runAction (action `addMethodCheck` methodCheck method request
163− `addAcceptCheck` acceptCheck proxy accH
168+ `addAcceptCheck` acceptCheck proxy accH
164169 ) respond $ \ output -> do
165170 let handleA = handleAcceptH proxy (AcceptHeader accH) output
166171 processMethodRouter handleA status method Nothing request
@@ -176,95 +181,34 @@ methodRouterHeaders method proxy status action = LeafRouter route'
176181 | pathIsEmpty request =
177182 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
178183 in runAction (action `addMethodCheck` methodCheck method request
179− `addAcceptCheck` acceptCheck proxy accH
184+ `addAcceptCheck` acceptCheck proxy accH
180185 ) respond $ \ output -> do
181186 let headers = getHeaders output
182187 handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
183188 processMethodRouter handleA status method (Just headers) request
184189 | otherwise = respond $ Fail err404
185190
186−methodRouterEmpty :: Method
187− -> Delayed (ExceptT ServantErr IO ())
188− -> Router
189−methodRouterEmpty method action = LeafRouter route'
190− where
191− route' request respond
192− | pathIsEmpty request = do
193− runAction (addMethodCheck action (methodCheck method request)) respond $ \ () ->
194− Route $! responseLBS noContent204 [] ""
195− | otherwise = respond $ Fail err404
196−
197−-- | If you have a 'Delete' endpoint in your API,
198−-- the handler for this endpoint is meant to delete
199−-- a resource.
200−--
201−-- The code of the handler will, just like
202−-- for 'Servant.API.Get.Get', 'Servant.API.Post.Post' and
203−-- 'Servant.API.Put.Put', run in @ExceptT ServantErr IO ()@.
204−-- The 'Int' represents the status code and the 'String' a message
205−-- to be returned. You can use 'Control.Monad.Trans.Except.throwE' to
206−-- painlessly error out if the conditions for a successful deletion
207−-- are not met.
208191instance OVERLAPPABLE_
209− ( AllCTRender ctypes a
210− ) => HasServer (Delete ctypes a) where
211−
212− type ServerT (Delete ctypes a) m = m a
213−
214− route Proxy = methodRouter methodDelete (Proxy :: Proxy ctypes) ok200
192+ ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
193+ ) => HasServer (Verb method status ctypes a) where
215194
216−instance OVERLAPPING_
217− HasServer (Delete ctypes ()) where
218−
219− type ServerT (Delete ctypes ()) m = m ()
195+ type ServerT (Verb method status ctypes a) m = m a
220196
221− route Proxy = methodRouterEmpty methodDelete
197+ route Proxy = methodRouter method (Proxy :: Proxy ctypes) status
198+ where method = reflectMethod (Proxy :: Proxy method)
199+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
222200
223−-- Add response headers
224201instance OVERLAPPING_
225− ( GetHeaders (Headers h v), AllCTRender ctypes v
226− ) => HasServer (Delete ctypes (Headers h v)) where
227−
228− type ServerT (Delete ctypes (Headers h v)) m = m (Headers h v)
229−
230− route Proxy = methodRouterHeaders methodDelete (Proxy :: Proxy ctypes) ok200
202+instance
203+ ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
204+ , GetHeaders (Headers h a)
205+ ) => HasServer (Verb method status ctypes (Headers h a)) where
231206
232−-- | When implementing the handler for a 'Get' endpoint,
233−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Post.Post'
234−-- and 'Servant.API.Put.Put', the handler code runs in the
235−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
236−-- the status code and the 'String' a message, returned in case of
237−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
238−-- to quickly fail if some conditions are not met.
239−--
240−-- If successfully returning a value, we use the type-level list, combined
241−-- with the request's @Accept@ header, to encode the value for you
242−-- (returning a status code of 200). If there was no @Accept@ header or it
243−-- was @*\/\*@, we return encode using the first @Content-Type@ type on the
244−-- list.
245−instance OVERLAPPABLE_
246− ( AllCTRender ctypes a ) => HasServer (Get ctypes a) where
207+ type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
247208
248− type ServerT (Get ctypes a) m = m a
249−
250− route Proxy = methodRouter methodGet (Proxy :: Proxy ctypes) ok200
251−
252−-- '()' ==> 204 No Content
253−instance OVERLAPPING_
254− HasServer (Get ctypes ()) where
255−
256− type ServerT (Get ctypes ()) m = m ()
257−
258− route Proxy = methodRouterEmpty methodGet
259−
260−-- Add response headers
261−instance OVERLAPPING_
262− ( GetHeaders (Headers h v), AllCTRender ctypes v
263− ) => HasServer (Get ctypes (Headers h v)) where
264−
265− type ServerT (Get ctypes (Headers h v)) m = m (Headers h v)
266−
267− route Proxy = methodRouterHeaders methodGet (Proxy :: Proxy ctypes) ok200
209+ route Proxy = methodRouterHeaders method (Proxy :: Proxy ctypes) status
210+ where method = reflectMethod (Proxy :: Proxy method)
211+ status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
268212
269213-- | If you use 'Header' in one of the endpoints for your API,
270214-- this automatically requires your server-side handler to be a function
@@ -297,113 +241,6 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
297241 in route (Proxy :: Proxy sublayout) (passToServer subserver mheader)
298242 where str = fromString $ symbolVal (Proxy :: Proxy sym)
299243
300−-- | When implementing the handler for a 'Post' endpoint,
301−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
302−-- and 'Servant.API.Put.Put', the handler code runs in the
303−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
304−-- the status code and the 'String' a message, returned in case of
305−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
306−-- to quickly fail if some conditions are not met.
307−--
308−-- If successfully returning a value, we use the type-level list, combined
309−-- with the request's @Accept@ header, to encode the value for you
310−-- (returning a status code of 201). If there was no @Accept@ header or it
311−-- was @*\/\*@, we return encode using the first @Content-Type@ type on the
312−-- list.
313−instance OVERLAPPABLE_
314− ( AllCTRender ctypes a
315− ) => HasServer (Post ctypes a) where
316−
317− type ServerT (Post ctypes a) m = m a
318−
319− route Proxy = methodRouter methodPost (Proxy :: Proxy ctypes) created201
320−
321−instance OVERLAPPING_
322− HasServer (Post ctypes ()) where
323−
324− type ServerT (Post ctypes ()) m = m ()
325−
326− route Proxy = methodRouterEmpty methodPost
327−
328−-- Add response headers
329−instance OVERLAPPING_
330− ( GetHeaders (Headers h v), AllCTRender ctypes v
331− ) => HasServer (Post ctypes (Headers h v)) where
332−
333− type ServerT (Post ctypes (Headers h v)) m = m (Headers h v)
334−
335− route Proxy = methodRouterHeaders methodPost (Proxy :: Proxy ctypes) created201
336−
337−-- | When implementing the handler for a 'Put' endpoint,
338−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
339−-- and 'Servant.API.Post.Post', the handler code runs in the
340−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
341−-- the status code and the 'String' a message, returned in case of
342−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
343−-- to quickly fail if some conditions are not met.
344−--
345−-- If successfully returning a value, we use the type-level list, combined
346−-- with the request's @Accept@ header, to encode the value for you
347−-- (returning a status code of 200). If there was no @Accept@ header or it
348−-- was @*\/\*@, we return encode using the first @Content-Type@ type on the
349−-- list.
350−instance OVERLAPPABLE_
351− ( AllCTRender ctypes a) => HasServer (Put ctypes a) where
352−
353− type ServerT (Put ctypes a) m = m a
354−
355− route Proxy = methodRouter methodPut (Proxy :: Proxy ctypes) ok200
356−
357−instance OVERLAPPING_
358− HasServer (Put ctypes ()) where
359−
360− type ServerT (Put ctypes ()) m = m ()
361−
362− route Proxy = methodRouterEmpty methodPut
363−
364−-- Add response headers
365−instance OVERLAPPING_
366− ( GetHeaders (Headers h v), AllCTRender ctypes v
367− ) => HasServer (Put ctypes (Headers h v)) where
368−
369− type ServerT (Put ctypes (Headers h v)) m = m (Headers h v)
370−
371− route Proxy = methodRouterHeaders methodPut (Proxy :: Proxy ctypes) ok200
372−
373−-- | When implementing the handler for a 'Patch' endpoint,
374−-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
375−-- and 'Servant.API.Put.Put', the handler code runs in the
376−-- @ExceptT ServantErr IO@ monad, where the 'Int' represents
377−-- the status code and the 'String' a message, returned in case of
378−-- failure. You can quite handily use 'Control.Monad.Trans.Except.throwE'
379−-- to quickly fail if some conditions are not met.
380−--
381−-- If successfully returning a value, we just require that its type has
382−-- a 'ToJSON' instance and servant takes care of encoding it for you,
383−-- yielding status code 200 along the way.
384−instance OVERLAPPABLE_
385− ( AllCTRender ctypes a) => HasServer (Patch ctypes a) where
386−
387− type ServerT (Patch ctypes a) m = m a
388−
389− route Proxy = methodRouter methodPatch (Proxy :: Proxy ctypes) ok200
390−
391−instance OVERLAPPING_
392− HasServer (Patch ctypes ()) where
393−
394− type ServerT (Patch ctypes ()) m = m ()
395−
396− route Proxy = methodRouterEmpty methodPatch
397−
398−-- Add response headers
399−instance OVERLAPPING_
400− ( GetHeaders (Headers h v), AllCTRender ctypes v
401− ) => HasServer (Patch ctypes (Headers h v)) where
402−
403− type ServerT (Patch ctypes (Headers h v)) m = m (Headers h v)
404−
405− route Proxy = methodRouterHeaders methodPatch (Proxy :: Proxy ctypes) ok200
406−
407244-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
408245-- this automatically requires your server-side handler to be a function
409246-- that takes an argument of type @'Maybe' 'Text'@.