@@ -1,45 +1,53 @@
1−{-# LANGUAGE DataKinds #-}
1+{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE PolyKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE TypeFamilies #-}
4−{-# LANGUAGE TypeOperators #-}
5−{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE OverlappingInstances #-}
4{-# LANGUAGE OverlappingInstances #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PolyKinds #-}
8−{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE TypeOperators #-}
9module Servant.Server.Internal where
10module Servant.Server.Internal where
11−import Control.Applicative ((<$>))
12+import Control.Applicative ((<$>))
12−import Control.Monad.Trans.Either (EitherT, runEitherT)
13+import Control.Monad.Trans.Either (EitherT, runEitherT)
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+ Delete, Get, Header, MatrixFlag,
36+ MatrixParam, MatrixParams, Patch,
37+ Post, Put, QueryFlag, QueryParam,
38+ QueryParams, Raw, ReqBody)
34−import Servant.API.ContentTypes ( AllCTRender(..), AcceptHeader(..)
39+import Servant.API.ContentTypes (AcceptHeader (..),
35− , AllCTUnrender(..),)
41+ AllCTUnrender (..))
42+import Servant.API.ResponseHeaders (Headers, getResponse, getHeaders)
36−import Servant.Common.Text (FromText, fromText)
43+import Servant.Common.Text (FromText, fromText)
39data ReqBodyState = Uncalled
46data ReqBodyState = Uncalled
40 | Called !B.ByteString
47 | Called !B.ByteString
41 | Done !B.ByteString
48 | Done !B.ByteString
43toApplication :: RoutingApplication -> Application
51toApplication :: RoutingApplication -> Application
44toApplication ra request respond = do
52toApplication ra request respond = do
45 reqBodyRef <- newIORef Uncalled
53 reqBodyRef <- newIORef Uncalled
@@ -286,6 +294,7 @@ instance ( AllCTRender ctypes a
286 respond $ failWith WrongMethod
294 respond $ failWith WrongMethod
287 | otherwise = respond $ failWith NotFound
295 | otherwise = respond $ failWith NotFound
297+-- '()' ==> 204 No Content
289instance HasServer (Get ctypes ()) where
298instance HasServer (Get ctypes ()) where
290 type ServerT (Get ctypes ()) m = m ()
299 type ServerT (Get ctypes ()) m = m ()
291 route Proxy action request respond
300 route Proxy action request respond
@@ -299,6 +308,26 @@ instance HasServer (Get ctypes ()) where
299 respond $ failWith WrongMethod
308 respond $ failWith WrongMethod
300 | otherwise = respond $ failWith NotFound
309 | otherwise = respond $ failWith NotFound
311+-- Add response headers
312+instance ( AllCTRender ctypes v ) => HasServer (Get ctypes (Headers h v)) where
313+ type ServerT (Get ctypes (Headers h v)) m = m (Headers h v)
314+ route Proxy action request respond
315+ | pathIsEmpty request && requestMethod request == methodGet = do
316+ e <- runEitherT action
319+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
320+ headers = getHeaders output
321+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
322+ Nothing -> failWith UnsupportedMediaType
323+ Just (contentT, body) -> succeedWith $
324+ responseLBS ok200 ( ("Content-Type" , cs contentT) : headers) body
325+ Left (status, message) -> succeedWith $
326+ responseLBS (mkStatus status (cs message)) [] (cs message)
327+ | pathIsEmpty request && requestMethod request /= methodGet =
328+ respond $ failWith WrongMethod
329+ | otherwise = respond $ failWith NotFound
302-- | If you use 'Header' in one of the endpoints for your API,
331-- | If you use 'Header' in one of the endpoints for your API,
303-- this automatically requires your server-side handler to be a function
332-- this automatically requires your server-side handler to be a function
304-- that takes an argument of the type specified by 'Header'.
333-- that takes an argument of the type specified by 'Header'.
@@ -377,6 +406,26 @@ instance HasServer (Post ctypes ()) where
377 respond $ failWith WrongMethod
406 respond $ failWith WrongMethod
378 | otherwise = respond $ failWith NotFound
407 | otherwise = respond $ failWith NotFound
409+-- Add response headers
410+instance ( AllCTRender ctypes v ) => HasServer (Post ctypes (Headers h v)) where
411+ type ServerT (Post ctypes (Headers h v)) m = m (Headers h v)
412+ route Proxy action request respond
413+ | pathIsEmpty request && requestMethod request == methodPost = do
414+ e <- runEitherT action
417+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
418+ headers = getHeaders output
419+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
420+ Nothing -> failWith UnsupportedMediaType
421+ Just (contentT, body) -> succeedWith $
422+ responseLBS status201 ( ("Content-Type" , cs contentT) : headers) body
423+ Left (status, message) -> succeedWith $
424+ responseLBS (mkStatus status (cs message)) [] (cs message)
425+ | pathIsEmpty request && requestMethod request /= methodPost =
426+ respond $ failWith WrongMethod
427+ | otherwise = respond $ failWith NotFound
380-- | When implementing the handler for a 'Put' endpoint,
429-- | When implementing the handler for a 'Put' endpoint,
381-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
430-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
382-- and 'Servant.API.Post.Post', the handler code runs in the
431-- and 'Servant.API.Post.Post', the handler code runs in the
@@ -387,7 +436,7 @@ instance HasServer (Post ctypes ()) where
388-- If successfully returning a value, we use the type-level list, combined
437-- If successfully returning a value, we use the type-level list, combined
389-- with the request's @Accept@ header, to encode the value for you
438-- with the request's @Accept@ header, to encode the value for you
390−-- (returning a status code of 201). If there was no @Accept@ header or it
439+-- (returning a status code of 200). If there was no @Accept@ header or it
391-- was @*/*@, we return encode using the first @Content-Type@ type on the
440-- was @*/*@, we return encode using the first @Content-Type@ type on the
393instance ( AllCTRender ctypes a
442instance ( AllCTRender ctypes a
@@ -423,6 +472,26 @@ instance HasServer (Put ctypes ()) where
423 respond $ failWith WrongMethod
472 respond $ failWith WrongMethod
424 | otherwise = respond $ failWith NotFound
473 | otherwise = respond $ failWith NotFound
475+-- Add response headers
476+instance ( AllCTRender ctypes v ) => HasServer (Put ctypes (Headers h v)) where
477+ type ServerT (Put ctypes (Headers h v)) m = m (Headers h v)
478+ route Proxy action request respond
479+ | pathIsEmpty request && requestMethod request == methodPut = do
480+ e <- runEitherT action
483+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
484+ headers = getHeaders output
485+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse output) of
486+ Nothing -> failWith UnsupportedMediaType
487+ Just (contentT, body) -> succeedWith $
488+ responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
489+ Left (status, message) -> succeedWith $
490+ responseLBS (mkStatus status (cs message)) [] (cs message)
491+ | pathIsEmpty request && requestMethod request /= methodPut =
492+ respond $ failWith WrongMethod
493+ | otherwise = respond $ failWith NotFound
426-- | When implementing the handler for a 'Patch' endpoint,
495-- | When implementing the handler for a 'Patch' endpoint,
427-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
496-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
428-- and 'Servant.API.Put.Put', the handler code runs in the
497-- and 'Servant.API.Put.Put', the handler code runs in the
@@ -433,7 +502,7 @@ instance HasServer (Put ctypes ()) where
434-- If successfully returning a value, we just require that its type has
503-- If successfully returning a value, we just require that its type has
435-- a 'ToJSON' instance and servant takes care of encoding it for you,
504-- a 'ToJSON' instance and servant takes care of encoding it for you,
436−-- yielding status code 201 along the way.
505+-- yielding status code 200 along the way.
437instance ( AllCTRender ctypes a
506instance ( AllCTRender ctypes a
438 ) => HasServer (Patch ctypes a) where
507 ) => HasServer (Patch ctypes a) where
439 type ServerT (Patch ctypes a) m = m a
508 type ServerT (Patch ctypes a) m = m a
@@ -467,6 +536,26 @@ instance HasServer (Patch ctypes ()) where
467 respond $ failWith WrongMethod
536 respond $ failWith WrongMethod
468 | otherwise = respond $ failWith NotFound
537 | otherwise = respond $ failWith NotFound
539+-- Add response headers
540+instance ( AllCTRender ctypes v ) => HasServer (Patch ctypes (Headers h v)) where
541+ type ServerT (Patch ctypes (Headers h v)) m = m (Headers h v)
542+ route Proxy action request respond
543+ | pathIsEmpty request && requestMethod request == methodPatch = do
544+ e <- runEitherT action
546+ Right outpatch -> do
547+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
548+ headers = getHeaders outpatch
549+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) (getResponse outpatch) of
550+ Nothing -> failWith UnsupportedMediaType
551+ Just (contentT, body) -> succeedWith $
552+ responseLBS status200 ( ("Content-Type" , cs contentT) : headers) body
553+ Left (status, message) -> succeedWith $
554+ responseLBS (mkStatus status (cs message)) [] (cs message)
555+ | pathIsEmpty request && requestMethod request /= methodPatch =
556+ respond $ failWith WrongMethod
557+ | otherwise = respond $ failWith NotFound
470-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
559-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
471-- this automatically requires your server-side handler to be a function
560-- this automatically requires your server-side handler to be a function
472-- that takes an argument of type @'Maybe' 'Text'@.
561-- that takes an argument of type @'Maybe' 'Text'@.