@@ -1,46 +1,53 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE PolyKinds #-}
3−{-# LANGUAGE TypeFamilies #-}
4−{-# LANGUAGE TypeOperators #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE OverlappingInstances #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PolyKinds #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE TypeOperators #-}
810module Servant.Server.Internal where
911
10−import Control.Applicative ((<$>))
11−import Control.Monad.Trans.Either (EitherT, runEitherT)
12−import Data.Aeson (ToJSON)
13−import qualified Data.ByteString as B
14−import qualified Data.ByteString.Lazy as BL
15−import Data.IORef (newIORef, readIORef, writeIORef)
16−import Data.List (unfoldr)
17−import Data.Maybe (catMaybes, fromMaybe)
18−import Data.Monoid (Monoid, mempty, mappend)
19−import Data.String (fromString)
20−import Data.String.Conversions (cs, (<>))
21−import Data.Text.Encoding (decodeUtf8, encodeUtf8)
22−import Data.Text (Text)
23−import qualified Data.Text as T
24−import Data.Typeable
25−import GHC.TypeLits (KnownSymbol, symbolVal)
26−import Network.HTTP.Types hiding (Header)
27−import Network.Wai ( Response, Request, ResponseReceived, Application
28− , pathInfo, requestBody, strictRequestBody
29− , lazyRequestBody, requestHeaders, requestMethod,
30− rawQueryString, responseLBS)
31−import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
32− , MatrixParams, MatrixParam, MatrixFlag
33− , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..)
34− , Canonicalize)
35−import Servant.API.ContentTypes ( AllCTRender(..), AcceptHeader(..)
36− , AllCTUnrender(..),)
37−import Servant.Common.Text (FromText, fromText)
38−
12+import Control.Applicative ((<$>))
13+import Control.Monad.Trans.Either (EitherT, runEitherT)
14+import qualified Data.ByteString as B
15+import qualified Data.ByteString.Lazy as BL
16+import Data.IORef (newIORef, readIORef, writeIORef)
17+import Data.List (unfoldr)
18+import Data.Maybe (catMaybes, fromMaybe)
19+import Data.Monoid (Monoid, mappend, mempty)
20+import Data.String (fromString)
21+import Data.String.Conversions (cs, (<>))
22+import Data.Text (Text)
23+import qualified Data.Text as T
24+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
25+import Data.Typeable
26+import GHC.TypeLits (KnownSymbol, symbolVal)
27+import Network.HTTP.Types hiding (Header, ResponseHeaders)
28+import Network.Wai (Application, Request, Response,
29+ ResponseReceived, lazyRequestBody,
30+ pathInfo, rawQueryString,
31+ requestBody, requestHeaders,
32+ requestMethod, responseLBS,
33+ strictRequestBody)
34+import Servant.API ((:<|>) (..), (:>), Capture,
35+ Canonicalize, Delete, Get, Header,
36+ MatrixFlag, MatrixParam, MatrixParams,
37+ Patch, Post, Put, QueryFlag,
38+ QueryParam, QueryParams, Raw,
39+ ReqBody)
40+import Servant.API.ContentTypes (AcceptHeader (..),
41+ AllCTRender (..),
42+ AllCTUnrender (..))
43+import Servant.API.ResponseHeaders (Headers, getResponse, getHeaders)
44+import Servant.Common.Text (FromText, fromText)
3945
4046data ReqBodyState = Uncalled
4147 | Called !B.ByteString
4248 | Done !B.ByteString
4349
50+
4451toApplication :: RoutingApplication -> Application
4552toApplication ra request respond = do
4653 reqBodyRef <- newIORef Uncalled
@@ -281,19 +288,53 @@ instance ( AllCTRender ctypes a
281288 route Proxy action request respond
282289 | pathIsEmpty request && requestMethod request == methodGet = do
283290 e <- runEitherT action
284− respond . succeedWith $ case e of
291+ respond $ case e of
285292 Right output -> do
286293 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
287294 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
288− Nothing -> responseLBS (mkStatus 406 "Not Acceptable") [] ""
289− Just (contentT, body) -> responseLBS ok200 [ ("Content-Type"
290− , cs contentT)] body
295+ Nothing -> failWith UnsupportedMediaType
296+ Just (contentT, body) -> succeedWith $
297+ responseLBS ok200 [ ("Content-Type" , 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
303+
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 [] ""
291312 Left (status, message) ->
292313 responseLBS (mkStatus status (cs message)) [] (cs message)
293314 | pathIsEmpty request && requestMethod request /= methodGet =
294315 respond $ failWith WrongMethod
295316 | otherwise = respond $ failWith NotFound
296317
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
324+ respond $ case e of
325+ Right output -> do
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
337+
297338-- | If you use 'Header' in one of the endpoints for your API,
298339-- this automatically requires your server-side handler to be a function
299340-- that takes an argument of the type specified by 'Header'.
@@ -347,19 +388,52 @@ instance ( AllCTRender ctypes a
347388 route Proxy action request respond
348389 | pathIsEmpty request && requestMethod request == methodPost = do
349390 e <- runEitherT action
350− respond . succeedWith $ case e of
391+ respond $ case e of
351392 Right output -> do
352393 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
353394 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
354− Nothing -> responseLBS (mkStatus 406 "") [] ""
355− Just (contentT, body) -> responseLBS status201 [ ("Content-Type"
356− , cs contentT)] body
395+ Nothing -> failWith UnsupportedMediaType
396+ Just (contentT, body) -> succeedWith $
397+ responseLBS status201 [ ("Content-Type" , 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
403+
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 [] ""
357411 Left (status, message) ->
358412 responseLBS (mkStatus status (cs message)) [] (cs message)
359413 | pathIsEmpty request && requestMethod request /= methodPost =
360414 respond $ failWith WrongMethod
361415 | otherwise = respond $ failWith NotFound
362416
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
423+ respond $ case e of
424+ Right output -> do
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
436+
363437-- | When implementing the handler for a 'Put' endpoint,
364438-- just like for 'Servant.API.Delete.Delete', 'Servant.API.Get.Get'
365439-- and 'Servant.API.Post.Post', the handler code runs in the
@@ -370,7 +444,7 @@ instance ( AllCTRender ctypes a
370444--
371445-- If successfully returning a value, we use the type-level list, combined
372446-- 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
374448-- was @*/*@, we return encode using the first @Content-Type@ type on the
375449-- list.
376450instance ( AllCTRender ctypes a
@@ -381,18 +455,50 @@ instance ( AllCTRender ctypes a
381455 route Proxy action request respond
382456 | pathIsEmpty request && requestMethod request == methodPut = do
383457 e <- runEitherT action
384− respond . succeedWith $ case e of
458+ respond $ case e of
385459 Right output -> do
386460 let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
387461 case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
388− Nothing -> responseLBS (mkStatus 406 "") [] ""
389− Just (contentT, body) -> responseLBS status200 [ ("Content-Type"
390− , cs contentT)] body
462+ Nothing -> failWith UnsupportedMediaType
463+ Just (contentT, body) -> succeedWith $
464+ responseLBS status200 [ ("Content-Type" , 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
470+
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 [] ""
391478 Left (status, message) ->
392479 responseLBS (mkStatus status (cs message)) [] (cs message)
393480 | pathIsEmpty request && requestMethod request /= methodPut =
394481 respond $ failWith WrongMethod
482+ | otherwise = respond $ failWith NotFound
395483
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
490+ respond $ case e of
491+ Right output -> do
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
396502 | otherwise = respond $ failWith NotFound
397503
398504-- | When implementing the handler for a 'Patch' endpoint,
@@ -405,28 +511,57 @@ instance ( AllCTRender ctypes a
405511--
406512-- If successfully returning a value, we just require that its type has
407513-- 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.
409515instance ( AllCTRender ctypes a
410− , Typeable a
411− , ToJSON a) => HasServer (Patch ctypes a) where
412−
516+ ) => HasServer (Patch ctypes a) where
413517 type ServerT' (Patch ctypes a) m = m a
414518
415519 route Proxy action request respond
416− | pathIsEmpty request && requestMethod request == methodPost = do
520+ | pathIsEmpty request && requestMethod request == methodPatch = do
521+ e <- runEitherT action
522+ respond $ case e of
523+ Right output -> do
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
534+
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
417539 e <- runEitherT action
418540 respond . succeedWith $ case e of
419− Right out -> case cast out of
420− Nothing -> do
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 [] ""
541+ Right () -> responseLBS noContent204 [] ""
427542 Left (status, message) ->
428543 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
547+
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
554+ respond $ case e of
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 =
430565 respond $ failWith WrongMethod
431566 | otherwise = respond $ failWith NotFound
432567