@@ -1,46 +1,53 @@
1−{-# LANGUAGE DataKinds #-}
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE OverlappingInstances #-}
5+{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE PolyKinds #-}
6+{-# LANGUAGE PolyKinds #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
3−{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE TypeFamilies #-}
4−{-# LANGUAGE TypeOperators #-}
9+{-# LANGUAGE TypeOperators #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8module Servant.Server.Internal where
10module Servant.Server.Internal where
10−import Control.Applicative ((<$>))
12+import Control.Applicative ((<$>))
11−import Control.Monad.Trans.Either (EitherT, runEitherT)
13+import Control.Monad.Trans.Either (EitherT, runEitherT)
12−import Data.Aeson (ToJSON)
13−import qualified Data.ByteString as B
14+import qualified Data.ByteString as B
14−import qualified Data.ByteString.Lazy as BL
15+import qualified Data.ByteString.Lazy as BL
15−import Data.IORef (newIORef, readIORef, writeIORef)
16+import Data.IORef (newIORef, readIORef, writeIORef)
16−import Data.List (unfoldr)
17+import Data.List (unfoldr)
17−import Data.Maybe (catMaybes, fromMaybe)
18+import Data.Maybe (catMaybes, fromMaybe)
18−import Data.Monoid (Monoid, mempty, mappend)
19+import Data.Monoid (Monoid, mappend, mempty)
19−import Data.String (fromString)
20+import Data.String (fromString)
20−import Data.String.Conversions (cs, (<>))
21+import Data.String.Conversions (cs, (<>))
22+import Data.Text (Text)
23+import qualified Data.Text as T
21−import Data.Text.Encoding (decodeUtf8, encodeUtf8)
24+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
22−import Data.Text (Text)
23−import qualified Data.Text as T
24−import Data.Typeable
25+import Data.Typeable
25−import GHC.TypeLits (KnownSymbol, symbolVal)
26+import GHC.TypeLits (KnownSymbol, symbolVal)
26−import Network.HTTP.Types hiding (Header)
27+import Network.HTTP.Types hiding (Header, ResponseHeaders)
27−import Network.Wai ( Response, Request, ResponseReceived, Application
28+import Network.Wai (Application, Request, Response,
28− , pathInfo, requestBody, strictRequestBody
29+ ResponseReceived, lazyRequestBody,
30+ pathInfo, rawQueryString,
29− , lazyRequestBody, requestHeaders, requestMethod,
31+ requestBody, requestHeaders,
30− rawQueryString, responseLBS)
32+ requestMethod, responseLBS,
31−import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
33+ strictRequestBody)
32− , MatrixParams, MatrixParam, MatrixFlag
34+import Servant.API ((:<|>) (..), (:>), Capture,
33− , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..)
35+ Canonicalize, Delete, Get, Header,
34− , Canonicalize)
36+ MatrixFlag, MatrixParam, MatrixParams,
37+ Patch, Post, Put, QueryFlag,
38+ QueryParam, QueryParams, Raw,
35−import Servant.API.ContentTypes ( AllCTRender(..), AcceptHeader(..)
40+import Servant.API.ContentTypes (AcceptHeader (..),
36− , AllCTUnrender(..),)
42+ AllCTUnrender (..))
43+import Servant.API.ResponseHeaders (Headers, getResponse, getHeaders)
37−import Servant.Common.Text (FromText, fromText)
44+import Servant.Common.Text (FromText, fromText)
40data ReqBodyState = Uncalled
46data ReqBodyState = Uncalled
41 | Called !B.ByteString
47 | Called !B.ByteString
42 | Done !B.ByteString
48 | Done !B.ByteString
44toApplication :: RoutingApplication -> Application
51toApplication :: RoutingApplication -> Application
45toApplication ra request respond = do
52toApplication ra request respond = do
46 reqBodyRef <- newIORef Uncalled
53 reqBodyRef <- newIORef Uncalled
@@ -281,19 +288,53 @@ instance ( AllCTRender ctypes a
281 route Proxy action request respond
288 route Proxy action request respond
282 | pathIsEmpty request && requestMethod request == methodGet = do
289 | pathIsEmpty request && requestMethod request == methodGet = do
283 e <- runEitherT action
290 e <- runEitherT action
284− respond . succeedWith $ case e of
291+ respond $ case e of
285 Right output -> do
292 Right output -> do
286 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
293 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
287 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
294 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
288− Nothing -> responseLBS (mkStatus 406 "Not Acceptable") [] ""
295+ Nothing -> failWith UnsupportedMediaType
296+ Just (contentT, body) -> succeedWith $
289− Just (contentT, body) -> responseLBS ok200 [ ("Content-Type"
297+ responseLBS ok200 [ ("Content-Type" , cs contentT)] body
290− , cs contentT)] body
298+ Left (status, message) -> succeedWith $
299+ responseLBS (mkStatus status (cs message)) [] (cs message)
300+ | pathIsEmpty request && requestMethod request /= methodGet =
301+ respond $ failWith WrongMethod
302+ | otherwise = respond $ failWith NotFound
304+-- '()' ==> 204 No Content
305+instance HasServer (Get ctypes ()) where
306+ type ServerT (Get ctypes ()) m = m ()
307+ route Proxy action request respond
308+ | pathIsEmpty request && requestMethod request == methodGet = do
309+ e <- runEitherT action
310+ respond . succeedWith $ case e of
311+ Right () -> responseLBS noContent204 [] ""
291 Left (status, message) ->
312 Left (status, message) ->
292 responseLBS (mkStatus status (cs message)) [] (cs message)
313 responseLBS (mkStatus status (cs message)) [] (cs message)
293 | pathIsEmpty request && requestMethod request /= methodGet =
314 | pathIsEmpty request && requestMethod request /= methodGet =
294 respond $ failWith WrongMethod
315 respond $ failWith WrongMethod
295 | otherwise = respond $ failWith NotFound
316 | otherwise = respond $ failWith NotFound
318+-- Add response headers
319+instance ( AllCTRender ctypes v ) => HasServer (Get ctypes (Headers h v)) where
320+ type ServerT (Get ctypes (Headers h v)) m = m (Headers h v)
321+ route Proxy action request respond
322+ | pathIsEmpty request && requestMethod request == methodGet = do
323+ e <- runEitherT action
326+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
327+ headers = getHeaders output
328+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
329+ Nothing -> failWith UnsupportedMediaType
330+ Just (contentT, body) -> succeedWith $
331+ responseLBS ok200 ( ("Content-Type" , cs contentT) : headers) body
332+ Left (status, message) -> succeedWith $
333+ responseLBS (mkStatus status (cs message)) [] (cs message)
334+ | pathIsEmpty request && requestMethod request /= methodGet =
335+ respond $ failWith WrongMethod
336+ | otherwise = respond $ failWith NotFound
297-- | If you use 'Header' in one of the endpoints for your API,
338-- | If you use 'Header' in one of the endpoints for your API,
298-- this automatically requires your server-side handler to be a function
339-- this automatically requires your server-side handler to be a function
299-- that takes an argument of the type specified by 'Header'.
340-- that takes an argument of the type specified by 'Header'.
@@ -347,19 +388,52 @@ instance ( AllCTRender ctypes a
347 route Proxy action request respond
388 route Proxy action request respond
348 | pathIsEmpty request && requestMethod request == methodPost = do
389 | pathIsEmpty request && requestMethod request == methodPost = do
349 e <- runEitherT action
390 e <- runEitherT action
350− respond . succeedWith $ case e of
391+ respond $ case e of
351 Right output -> do
392 Right output -> do
352 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
393 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
353 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
394 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
354− Nothing -> responseLBS (mkStatus 406 "") [] ""
395+ Nothing -> failWith UnsupportedMediaType
396+ Just (contentT, body) -> succeedWith $
355− Just (contentT, body) -> responseLBS status201 [ ("Content-Type"
397+ responseLBS status201 [ ("Content-Type" , cs contentT)] body
356− , cs contentT)] body
398+ Left (status, message) -> succeedWith $
399+ responseLBS (mkStatus status (cs message)) [] (cs message)
400+ | pathIsEmpty request && requestMethod request /= methodPost =
401+ respond $ failWith WrongMethod
402+ | otherwise = respond $ failWith NotFound
404+instance HasServer (Post ctypes ()) where
405+ type ServerT (Post ctypes ()) m = m ()
406+ route Proxy action request respond
407+ | pathIsEmpty request && requestMethod request == methodPost = do
408+ e <- runEitherT action
409+ respond . succeedWith $ case e of
410+ Right () -> responseLBS noContent204 [] ""
357 Left (status, message) ->
411 Left (status, message) ->
358 responseLBS (mkStatus status (cs message)) [] (cs message)
412 responseLBS (mkStatus status (cs message)) [] (cs message)
359 | pathIsEmpty request && requestMethod request /= methodPost =
413 | pathIsEmpty request && requestMethod request /= methodPost =
360 respond $ failWith WrongMethod
414 respond $ failWith WrongMethod
361 | otherwise = respond $ failWith NotFound
415 | otherwise = respond $ failWith NotFound
417+-- Add response headers
418+instance ( AllCTRender ctypes v ) => HasServer (Post ctypes (Headers h v)) where
419+ type ServerT (Post ctypes (Headers h v)) m = m (Headers h v)
420+ route Proxy action request respond
421+ | pathIsEmpty request && requestMethod request == methodPost = do
422+ e <- runEitherT action
425+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
426+ headers = getHeaders output
427+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
428+ Nothing -> failWith UnsupportedMediaType
429+ Just (contentT, body) -> succeedWith $
430+ responseLBS status201 ( ("Content-Type" , cs contentT) : headers) body
431+ Left (status, message) -> succeedWith $
432+ responseLBS (mkStatus status (cs message)) [] (cs message)
433+ | pathIsEmpty request && requestMethod request /= methodPost =
434+ respond $ failWith WrongMethod
435+ | otherwise = respond $ failWith NotFound
363-- | When implementing the handler for a 'Put' endpoint,
437-- | When implementing the handler for a 'Put' endpoint,
364-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
438-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
365-- and 'Servant.API.Post.Post', the handler code runs in the
439-- and 'Servant.API.Post.Post', the handler code runs in the
@@ -370,7 +444,7 @@ instance ( AllCTRender ctypes a
371-- If successfully returning a value, we use the type-level list, combined
445-- If successfully returning a value, we use the type-level list, combined
372-- with the request's @Accept@ header, to encode the value for you
446-- with the request's @Accept@ header, to encode the value for you
373−-- (returning a status code of 201). If there was no @Accept@ header or it
447+-- (returning a status code of 200). If there was no @Accept@ header or it
374-- was @*/*@, we return encode using the first @Content-Type@ type on the
448-- was @*/*@, we return encode using the first @Content-Type@ type on the
376instance ( AllCTRender ctypes a
450instance ( AllCTRender ctypes a
@@ -381,18 +455,50 @@ instance ( AllCTRender ctypes a
381 route Proxy action request respond
455 route Proxy action request respond
382 | pathIsEmpty request && requestMethod request == methodPut = do
456 | pathIsEmpty request && requestMethod request == methodPut = do
383 e <- runEitherT action
457 e <- runEitherT action
384− respond . succeedWith $ case e of
458+ respond $ case e of
385 Right output -> do
459 Right output -> do
386 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
460 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
387 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
461 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
388− Nothing -> responseLBS (mkStatus 406 "") [] ""
462+ Nothing -> failWith UnsupportedMediaType
463+ Just (contentT, body) -> succeedWith $
389− Just (contentT, body) -> responseLBS status200 [ ("Content-Type"
464+ responseLBS status200 [ ("Content-Type" , cs contentT)] body
390− , cs contentT)] body
465+ Left (status, message) -> succeedWith $
466+ responseLBS (mkStatus status (cs message)) [] (cs message)
467+ | pathIsEmpty request && requestMethod request /= methodPut =
468+ respond $ failWith WrongMethod
469+ | otherwise = respond $ failWith NotFound
471+instance HasServer (Put ctypes ()) where
472+ type ServerT (Put ctypes ()) m = m ()
473+ route Proxy action request respond
474+ | pathIsEmpty request && requestMethod request == methodPut = do
475+ e <- runEitherT action
476+ respond . succeedWith $ case e of
477+ Right () -> responseLBS noContent204 [] ""
391 Left (status, message) ->
478 Left (status, message) ->
392 responseLBS (mkStatus status (cs message)) [] (cs message)
479 responseLBS (mkStatus status (cs message)) [] (cs message)
393 | pathIsEmpty request && requestMethod request /= methodPut =
480 | pathIsEmpty request && requestMethod request /= methodPut =
394 respond $ failWith WrongMethod
481 respond $ failWith WrongMethod
482+ | otherwise = respond $ failWith NotFound
484+-- Add response headers
485+instance ( AllCTRender ctypes v ) => HasServer (Put ctypes (Headers h v)) where
486+ type ServerT (Put ctypes (Headers h v)) m = m (Headers h v)
487+ route Proxy action request respond
488+ | pathIsEmpty request && requestMethod request == methodPut = do
489+ e <- runEitherT action
492+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
493+ headers = getHeaders output
494+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
495+ Nothing -> failWith UnsupportedMediaType
496+ Just (contentT, body) -> succeedWith $
497+ responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
498+ Left (status, message) -> succeedWith $
499+ responseLBS (mkStatus status (cs message)) [] (cs message)
500+ | pathIsEmpty request && requestMethod request /= methodPut =
501+ respond $ failWith WrongMethod
396 | otherwise = respond $ failWith NotFound
502 | otherwise = respond $ failWith NotFound
398-- | When implementing the handler for a 'Patch' endpoint,
504-- | When implementing the handler for a 'Patch' endpoint,
@@ -405,28 +511,57 @@ instance ( AllCTRender ctypes a
406-- If successfully returning a value, we just require that its type has
512-- If successfully returning a value, we just require that its type has
407-- a 'ToJSON' instance and servant takes care of encoding it for you,
513-- a 'ToJSON' instance and servant takes care of encoding it for you,
408−-- yielding status code 201 along the way.
514+-- yielding status code 200 along the way.
409instance ( AllCTRender ctypes a
515instance ( AllCTRender ctypes a
411− , ToJSON a) => HasServer (Patch ctypes a) where
516+ ) => HasServer (Patch ctypes a) where
413 type ServerT' (Patch ctypes a) m = m a
517 type ServerT' (Patch ctypes a) m = m a
415 route Proxy action request respond
519 route Proxy action request respond
416− | pathIsEmpty request && requestMethod request == methodPost = do
520+ | pathIsEmpty request && requestMethod request == methodPatch = do
521+ e <- runEitherT action
524+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
525+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
526+ Nothing -> failWith UnsupportedMediaType
527+ Just (contentT, body) -> succeedWith $
528+ responseLBS status200 [ ("Content-Type" , cs contentT)] body
529+ Left (status, message) -> succeedWith $
530+ responseLBS (mkStatus status (cs message)) [] (cs message)
531+ | pathIsEmpty request && requestMethod request /= methodPatch =
532+ respond $ failWith WrongMethod
533+ | otherwise = respond $ failWith NotFound
535+instance HasServer (Patch ctypes ()) where
536+ type ServerT (Patch ctypes ()) m = m ()
537+ route Proxy action request respond
538+ | pathIsEmpty request && requestMethod request == methodPatch = do
417 e <- runEitherT action
539 e <- runEitherT action
418 respond . succeedWith $ case e of
540 respond . succeedWith $ case e of
419− Right out -> case cast out of
541+ Right () -> responseLBS noContent204 [] ""
421− let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
422− case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) out of
423− Nothing -> responseLBS (mkStatus 406 "") [] ""
424− Just (contentT, body) -> responseLBS status200 [ ("Content-Type"
425− , cs contentT)] body
426− Just () -> responseLBS status204 [] ""
427 Left (status, message) ->
542 Left (status, message) ->
428 responseLBS (mkStatus status (cs message)) [] (cs message)
543 responseLBS (mkStatus status (cs message)) [] (cs message)
429− | pathIsEmpty request && requestMethod request /= methodPost =
544+ | pathIsEmpty request && requestMethod request /= methodPatch =
545+ respond $ failWith WrongMethod
546+ | otherwise = respond $ failWith NotFound
548+-- Add response headers
549+instance ( AllCTRender ctypes v ) => HasServer (Patch ctypes (Headers h v)) where
550+ type ServerT (Patch ctypes (Headers h v)) m = m (Headers h v)
551+ route Proxy action request respond
552+ | pathIsEmpty request && requestMethod request == methodPatch = do
553+ e <- runEitherT action
555+ Right outpatch -> do
556+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
557+ headers = getHeaders outpatch
558+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse outpatch) of
559+ Nothing -> failWith UnsupportedMediaType
560+ Just (contentT, body) -> succeedWith $
561+ responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
562+ Left (status, message) -> succeedWith $
563+ responseLBS (mkStatus status (cs message)) [] (cs message)
564+ | pathIsEmpty request && requestMethod request /= methodPatch =
430 respond $ failWith WrongMethod
565 respond $ failWith WrongMethod
431 | otherwise = respond $ failWith NotFound
566 | otherwise = respond $ failWith NotFound