@@ -9,7 +9,7 @@ module Servant.Server.Internal where
10import Control.Applicative ((<$>))
10import Control.Applicative ((<$>))
11import Control.Monad.Trans.Either (EitherT, runEitherT)
11import Control.Monad.Trans.Either (EitherT, runEitherT)
12−import Data.Aeson (ToJSON, FromJSON, encode, eitherDecode')
12+import Data.Aeson (ToJSON)
13import qualified Data.ByteString as B
13import qualified Data.ByteString as B
14import qualified Data.ByteString.Lazy as BL
14import qualified Data.ByteString.Lazy as BL
15import Data.IORef (newIORef, readIORef, writeIORef)
15import Data.IORef (newIORef, readIORef, writeIORef)
@@ -29,10 +29,10 @@ import Network.Wai ( Response, Request, ResponseReceived, Application
29 , lazyRequestBody, requestHeaders, requestMethod,
29 , lazyRequestBody, requestHeaders, requestMethod,
30 rawQueryString, responseLBS)
30 rawQueryString, responseLBS)
31import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
31import Servant.API ( QueryParams, QueryParam, QueryFlag, ReqBody, Header
32− , MatrixParams, MatrixParam, MatrixFlag,
32+ , MatrixParams, MatrixParam, MatrixFlag
33 , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..))
33 , Capture, Get, Delete, Put, Post, Patch, Raw, (:>), (:<|>)(..))
34−import Servant.Server.ContentTypes ( AllCTRender(..), AcceptHeader(..)
34+import Servant.API.ContentTypes ( AllCTRender(..), AcceptHeader(..)
35− , AllCTUnrender(..) )
35+ , AllCTUnrender(..),)
36import Servant.Common.Text (FromText, fromText)
36import Servant.Common.Text (FromText, fromText)
@@ -72,39 +72,33 @@ toApplication ra request respond = do
72 respond $ responseLBS methodNotAllowed405 [] "method not allowed"
72 respond $ responseLBS methodNotAllowed405 [] "method not allowed"
73 routingRespond (Left (InvalidBody err)) =
73 routingRespond (Left (InvalidBody err)) =
74 respond $ responseLBS badRequest400 [] $ fromString $ "Invalid JSON in request body: " ++ err
74 respond $ responseLBS badRequest400 [] $ fromString $ "Invalid JSON in request body: " ++ err
75+ routingRespond (Left UnsupportedMediaType) =
76+ respond $ responseLBS unsupportedMediaType415 [] "unsupported media type"
75 routingRespond (Left (HttpError status body)) =
77 routingRespond (Left (HttpError status body)) =
76 respond $ responseLBS status [] $ fromMaybe (BL.fromStrict $ statusMessage status) body
78 respond $ responseLBS status [] $ fromMaybe (BL.fromStrict $ statusMessage status) body
77 routingRespond (Right response) =
79 routingRespond (Right response) =
78 respond response
80 respond response
82+-- Note that the ordering of the constructors has great significance! It
83+-- determines the Ord instance and, consequently, the monoid instance.
80-- * Route mismatch
84-- * Route mismatch
81data RouteMismatch =
85data RouteMismatch =
82 NotFound -- ^ the usual "not found" error
86 NotFound -- ^ the usual "not found" error
83 | WrongMethod -- ^ a more informative "you just got the HTTP method wrong" error
87 | WrongMethod -- ^ a more informative "you just got the HTTP method wrong" error
88+ | UnsupportedMediaType -- ^ request body has unsupported media type
84 | InvalidBody String -- ^ an even more informative "your json request body wasn't valid" error
89 | InvalidBody String -- ^ an even more informative "your json request body wasn't valid" error
85 | HttpError Status (Maybe BL.ByteString) -- ^ an even even more informative arbitrary HTTP response code error.
90 | HttpError Status (Maybe BL.ByteString) -- ^ an even even more informative arbitrary HTTP response code error.
86− deriving (Eq, Show)
91+ deriving (Eq, Ord, Show)
90−-- > mempty = NotFound
92−-- > _ `mappend` HttpError s b = HttpError s b
93−-- > HttpError s b `mappend` _ = HttpError s b
94−-- > NotFound `mappend` x = x
95−-- > WrongMethod `mappend` InvalidBody s = InvalidBody s
96−-- > WrongMethod `mappend` _ = WrongMethod
97−-- > InvalidBody s `mappend` _ = InvalidBody s
99instance Monoid RouteMismatch where
93instance Monoid RouteMismatch where
100 mempty = NotFound
94 mempty = NotFound
95+ -- The following isn't great, since it picks @InvalidBody@ based on
96+ -- alphabetical ordering, but any choice would be arbitrary.
98+ -- "As one judge said to the other, 'Be just and if you can't be just, be
99+ -- arbitrary'" -- William Burroughs
102− _ `mappend` HttpError s b = HttpError s b
103− HttpError s b `mappend` _ = HttpError s b
104− NotFound `mappend` x = x
105− WrongMethod `mappend` InvalidBody s = InvalidBody s
106− WrongMethod `mappend` _ = WrongMethod
107− InvalidBody s `mappend` _ = InvalidBody s
109-- | A wrapper around @'Either' 'RouteMismatch' a@.
103-- | A wrapper around @'Either' 'RouteMismatch' a@.
110newtype RouteResult a =
104newtype RouteResult a =
@@ -401,15 +395,22 @@ instance ( AllCTRender ctypes a
401-- If successfully returning a value, we just require that its type has
395-- If successfully returning a value, we just require that its type has
402-- a 'ToJSON' instance and servant takes care of encoding it for you,
396-- a 'ToJSON' instance and servant takes care of encoding it for you,
403-- yielding status code 201 along the way.
397-- yielding status code 201 along the way.
398+instance ( AllCTRender ctypes a
404−instance (Typeable a, ToJSON a) => HasServer (Patch a) where
400+ , ToJSON a) => HasServer (Patch ctypes a) where
405− type Server (Patch a) = EitherT (Int, String) IO a
401+ type Server (Patch ctypes a) = EitherT (Int, String) IO a
407 route Proxy action request respond
403 route Proxy action request respond
408 | pathIsEmpty request && requestMethod request == methodPost = do
404 | pathIsEmpty request && requestMethod request == methodPost = do
409 e <- runEitherT action
405 e <- runEitherT action
410 respond . succeedWith $ case e of
406 respond . succeedWith $ case e of
411 Right out -> case cast out of
407 Right out -> case cast out of
409+ let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
410+ case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) out of
411+ Nothing -> responseLBS (mkStatus 406 "") [] ""
412− Nothing -> responseLBS status200 [("Content-Type", "application/json")] (encode out)
412+ Just (contentT, body) -> responseLBS status200 [ ("Content-Type"
413+ , cs contentT)] body
413 Just () -> responseLBS status204 [] ""
414 Just () -> responseLBS status204 [] ""
414 Left (status, message) ->
415 Left (status, message) ->
415 responseLBS (mkStatus status (cs message)) [] (cs message)
416 responseLBS (mkStatus status (cs message)) [] (cs message)
@@ -695,8 +696,9 @@ instance ( AllCTUnrender list a, HasServer sublayout
695 mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
696 mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
696 <$> lazyRequestBody request
697 <$> lazyRequestBody request
697 case mrqbody of
698 case mrqbody of
699+ Nothing -> respond . failWith $ UnsupportedMediaType
698− Left e -> respond . failWith $ InvalidBody e
700+ Just (Left e) -> respond . failWith $ InvalidBody e
699− Right v -> route (Proxy :: Proxy sublayout) (subserver v) request respond
701+ Just (Right v) -> route (Proxy :: Proxy sublayout) (subserver v) request respond
701-- | Make sure the incoming request starts with @"/path"@, strip it and
703-- | Make sure the incoming request starts with @"/path"@, strip it and
702-- pass the rest of the request path to @sublayout@.
704-- pass the rest of the request path to @sublayout@.