Cleanup errorspec description of routing, changelog.

Review fixes
This commit is contained in:
Julian K. Arni 2015-09-15 11:37:17 +02:00
parent a3b5652ab9
commit ccadba81ec
8 changed files with 154 additions and 145 deletions

View file

@ -32,12 +32,12 @@ instance HasServer rest => HasServer (AuthProtected :> rest) where
route Proxy a = WithRequest $ \ request -> route Proxy a = WithRequest $ \ request ->
route (Proxy :: Proxy rest) $ do route (Proxy :: Proxy rest) $ do
case lookup "Cookie" (requestHeaders request) of case lookup "Cookie" (requestHeaders request) of
Nothing -> return $! failFatallyWith err401 { errBody = "Missing auth header" } Nothing -> return $! FailFatal err401 { errBody = "Missing auth header" }
Just v -> do Just v -> do
authGranted <- isGoodCookie v authGranted <- isGoodCookie v
if authGranted if authGranted
then a then a
else return $! failFatallyWith err403 { errBody = "Invalid cookie" } else return $ FailFatal err403 { errBody = "Invalid cookie" }
type PrivateAPI = Get '[JSON] [PrivateData] type PrivateAPI = Get '[JSON] [PrivateData]

View file

@ -5,6 +5,11 @@ HEAD
* Drop `EitherT` in favor of `ExceptT` * Drop `EitherT` in favor of `ExceptT`
* Use `http-api-data` instead of `Servant.Common.Text` * Use `http-api-data` instead of `Servant.Common.Text`
* Remove matrix params. * Remove matrix params.
* Remove `RouteMismatch`.
* Redefined constructors of `RouteResult`.
* Add `failFatallyWith`.
* Make all (framework-generated) HTTP errors except 404 and 405 not try other
handlers.
0.4.1 0.4.1
----- -----

View file

@ -103,7 +103,7 @@ import Servant.Server.Internal.Enter
-- > main = Network.Wai.Handler.Warp.run 8080 app -- > main = Network.Wai.Handler.Warp.run 8080 app
-- --
serve :: HasServer layout => Proxy layout -> Server layout -> Application serve :: HasServer layout => Proxy layout -> Server layout -> Application
serve p server = toApplication (runRouter (route p (return (HandlerVal server)))) serve p server = toApplication (runRouter (route p (return (Route server))))
-- Documentation -- Documentation

View file

@ -1,15 +1,15 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-} {-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
#if !MIN_VERSION_base(4,8,0) #if !MIN_VERSION_base(4,8,0)
{-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE OverlappingInstances #-}
#endif #endif
module Servant.Server.Internal module Servant.Server.Internal
@ -26,9 +26,9 @@ import Control.Monad.Trans.Except (ExceptT)
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (mapMaybe, fromMaybe) import Data.Maybe (fromMaybe, mapMaybe)
import Data.String (fromString) import Data.String (fromString)
import Data.String.Conversions (cs, (<>), ConvertibleStrings) import Data.String.Conversions (ConvertibleStrings, cs, (<>))
import Data.Text (Text) import Data.Text (Text)
import Data.Typeable import Data.Typeable
import GHC.TypeLits (KnownSymbol, symbolVal) import GHC.TypeLits (KnownSymbol, symbolVal)
@ -47,8 +47,8 @@ import Servant.API ((:<|>) (..), (:>), Capture,
import Servant.API.ContentTypes (AcceptHeader (..), import Servant.API.ContentTypes (AcceptHeader (..),
AllCTRender (..), AllCTRender (..),
AllCTUnrender (..)) AllCTUnrender (..))
import Servant.API.ResponseHeaders (Headers, getResponse, GetHeaders, import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
getHeaders) getResponse)
import Servant.Server.Internal.Router import Servant.Server.Internal.Router
import Servant.Server.Internal.RoutingApplication import Servant.Server.Internal.RoutingApplication
@ -114,7 +114,7 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout)
route Proxy subserver = route Proxy subserver =
DynamicRouter $ \ first -> case captured captureProxy first of DynamicRouter $ \ first -> case captured captureProxy first of
Nothing -> LeafRouter (\_ r -> r $ failWith err404) Nothing -> LeafRouter (\_ r -> r $ Fail err404)
Just v -> route (Proxy :: Proxy sublayout) (feedTo subserver v) Just v -> route (Proxy :: Proxy sublayout) (feedTo subserver v)
where captureProxy = Proxy :: Proxy (Capture capture a) where captureProxy = Proxy :: Proxy (Capture capture a)
@ -130,8 +130,8 @@ processMethodRouter :: forall a. ConvertibleStrings a B.ByteString
-> Maybe [(HeaderName, B.ByteString)] -> Maybe [(HeaderName, B.ByteString)]
-> Request -> RouteResult Response -> Request -> RouteResult Response
processMethodRouter handleA status method headers request = case handleA of processMethodRouter handleA status method headers request = case handleA of
Nothing -> failFatallyWith err406 Nothing -> FailFatal err406
Just (contentT, body) -> succeedWith $ responseLBS status hdrs bdy Just (contentT, body) -> Route $! responseLBS status hdrs bdy
where where
bdy = if allowedMethodHead method request then "" else body bdy = if allowedMethodHead method request then "" else body
hdrs = (hContentType, cs contentT) : (fromMaybe [] headers) hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
@ -149,8 +149,8 @@ methodRouter method proxy status action = LeafRouter route'
handleA = handleAcceptH proxy (AcceptHeader accH) output handleA = handleAcceptH proxy (AcceptHeader accH) output
processMethodRouter handleA status method Nothing request processMethodRouter handleA status method Nothing request
| pathIsEmpty request && requestMethod request /= method = | pathIsEmpty request && requestMethod request /= method =
respond $ failWith err405 respond $ Fail err405
| otherwise = respond $ failWith err404 | otherwise = respond $ Fail err404
methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v) methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
=> Method -> Proxy ctypes -> Status => Method -> Proxy ctypes -> Status
@ -166,8 +166,8 @@ methodRouterHeaders method proxy status action = LeafRouter route'
handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output) handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
processMethodRouter handleA status method (Just headers) request processMethodRouter handleA status method (Just headers) request
| pathIsEmpty request && requestMethod request /= method = | pathIsEmpty request && requestMethod request /= method =
respond $ failWith err405 respond $ Fail err405
| otherwise = respond $ failWith err404 | otherwise = respond $ Fail err404
methodRouterEmpty :: Method methodRouterEmpty :: Method
-> IO (RouteResult (ExceptT ServantErr IO ())) -> IO (RouteResult (ExceptT ServantErr IO ()))
@ -177,10 +177,10 @@ methodRouterEmpty method action = LeafRouter route'
route' request respond route' request respond
| pathIsEmpty request && allowedMethod method request = do | pathIsEmpty request && allowedMethod method request = do
runAction action respond $ \ () -> runAction action respond $ \ () ->
succeedWith $ responseLBS noContent204 [] "" Route $! responseLBS noContent204 [] ""
| pathIsEmpty request && requestMethod request /= method = | pathIsEmpty request && requestMethod request /= method =
respond $ failWith err405 respond $ Fail err405
| otherwise = respond $ failWith err404 | otherwise = respond $ Fail err404
-- | If you have a 'Delete' endpoint in your API, -- | If you have a 'Delete' endpoint in your API,
-- the handler for this endpoint is meant to delete -- the handler for this endpoint is meant to delete
@ -557,9 +557,9 @@ instance HasServer Raw where
route Proxy rawApplication = LeafRouter $ \ request respond -> do route Proxy rawApplication = LeafRouter $ \ request respond -> do
r <- rawApplication r <- rawApplication
case r of case r of
HandlerVal app -> app request (respond . succeedWith) Route app -> app request (respond . Route)
Retriable e -> respond $ failWith e Fail a -> respond $ Fail a
NonRetriable e -> respond $! failFatallyWith e FailFatal e -> respond $ FailFatal e
-- | If you use 'ReqBody' in one of the endpoints for your API, -- | If you use 'ReqBody' in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function -- this automatically requires your server-side handler to be a function
@ -599,8 +599,8 @@ instance ( AllCTUnrender list a, HasServer sublayout
mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH) mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
<$> lazyRequestBody request <$> lazyRequestBody request
case mrqbody of case mrqbody of
Nothing -> return $! failFatallyWith err415 Nothing -> return $ FailFatal err415
Just (Left e) -> return $! failFatallyWith err400 { errBody = cs e } Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
Just (Right v) -> feedTo subserver v Just (Right v) -> feedTo subserver v
-- | Make sure the incoming request starts with @"/path"@, strip it and -- | Make sure the incoming request starts with @"/path"@, strip it and

View file

@ -1,10 +1,9 @@
{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE CPP #-}
module Servant.Server.Internal.Router where module Servant.Server.Internal.Router where
import Data.Map (Map) import Data.Map (Map)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Monoid ((<>))
import Data.Text (Text) import Data.Text (Text)
import Network.Wai (Request, Response, pathInfo) import Network.Wai (Request, Response, pathInfo)
import Servant.Server.Internal.ServantErr import Servant.Server.Internal.ServantErr
@ -65,24 +64,23 @@ runRouter (StaticRouter table) request respond =
| Just router <- M.lookup first table | Just router <- M.lookup first table
-> let request' = request { pathInfo = rest } -> let request' = request { pathInfo = rest }
in runRouter router request' respond in runRouter router request' respond
_ -> respond $ failWith err404 _ -> respond $ Fail err404
runRouter (DynamicRouter fun) request respond = runRouter (DynamicRouter fun) request respond =
case pathInfo request of case pathInfo request of
first : rest first : rest
-> let request' = request { pathInfo = rest } -> let request' = request { pathInfo = rest }
in runRouter (fun first) request' respond in runRouter (fun first) request' respond
_ -> respond $ failWith err404 _ -> respond $ Fail err404
runRouter (LeafRouter app) request respond = app request respond runRouter (LeafRouter app) request respond = app request respond
runRouter (Choice r1 r2) request respond = runRouter (Choice r1 r2) request respond =
runRouter r1 request $ \ mResponse1 -> runRouter r1 request $ \ mResponse1 -> case mResponse1 of
if isMismatch mResponse1 Fail _ -> runRouter r2 request $ \ mResponse2 ->
then runRouter r2 request $ \ mResponse2 -> respond (highestPri mResponse1 mResponse2)
respond (highestPri mResponse1 mResponse2) _ -> respond mResponse1
else respond mResponse1
where where
highestPri (Retriable r1) (Retriable r2) = highestPri (Fail e1) (Fail e2) =
if errHTTPCode r1 == 404 && errHTTPCode r2 /= 404 if errHTTPCode e1 == 404 && errHTTPCode e2 /= 404
then (Retriable r2) then Fail e2
else (Retriable r1) else Fail e1
highestPri (Retriable _) y = y highestPri (Fail _) y = y
highestPri x _ = x highestPri x _ = x

View file

@ -1,6 +1,5 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
module Servant.Server.Internal.RoutingApplication where module Servant.Server.Internal.RoutingApplication where
@ -28,10 +27,10 @@ type RoutingApplication =
-- | A wrapper around @'Either' 'RouteMismatch' a@. -- | A wrapper around @'Either' 'RouteMismatch' a@.
data RouteResult a = data RouteResult a =
Retriable ServantErr -- ^ Keep trying other paths. The @ServantErr@ Fail ServantErr -- ^ Keep trying other paths. The @ServantErr@
-- should only be 404 or 405. -- should only be 404 or 405.
| NonRetriable ServantErr -- ^ Stop trying. | FailFatal ServantErr -- ^ Don't other paths.
| HandlerVal a | Route a
deriving (Eq, Show, Read, Functor) deriving (Eq, Show, Read, Functor)
data ReqBodyState = Uncalled data ReqBodyState = Uncalled
@ -64,9 +63,9 @@ toApplication ra request respond = do
ra request{ requestBody = memoReqBody } routingRespond ra request{ requestBody = memoReqBody } routingRespond
where where
routingRespond :: RouteResult Response -> IO ResponseReceived routingRespond :: RouteResult Response -> IO ResponseReceived
routingRespond (Retriable err) = respond $! responseServantErr err routingRespond (Fail err) = respond $! responseServantErr err
routingRespond (NonRetriable err) = respond $! responseServantErr err routingRespond (FailFatal err) = respond $! responseServantErr err
routingRespond (HandlerVal v) = respond v routingRespond (Route v) = respond v
runAction :: IO (RouteResult (ExceptT ServantErr IO a)) runAction :: IO (RouteResult (ExceptT ServantErr IO a))
-> (RouteResult Response -> IO r) -> (RouteResult Response -> IO r)
@ -74,39 +73,23 @@ runAction :: IO (RouteResult (ExceptT ServantErr IO a))
-> IO r -> IO r
runAction action respond k = action >>= go >>= respond runAction action respond k = action >>= go >>= respond
where where
go (Retriable e) = return $! Retriable e go (Fail e) = return $ Fail e
go (NonRetriable e) = return . succeedWith $! responseServantErr e go (FailFatal e) = return $ FailFatal e
go (HandlerVal a) = do go (Route a) = do
e <- runExceptT a e <- runExceptT a
case e of case e of
Left err -> return . succeedWith $! responseServantErr err Left err -> return . Route $ responseServantErr err
Right x -> return $! k x Right x -> return $! k x
feedTo :: IO (RouteResult (a -> b)) -> a -> IO (RouteResult b) feedTo :: IO (RouteResult (a -> b)) -> a -> IO (RouteResult b)
feedTo f x = (($ x) <$>) <$> f feedTo f x = (($ x) <$>) <$> f
extractL :: RouteResult (a :<|> b) -> RouteResult a extractL :: RouteResult (a :<|> b) -> RouteResult a
extractL (HandlerVal (a :<|> _)) = HandlerVal a extractL (Route (a :<|> _)) = Route a
extractL (Retriable x) = Retriable x extractL (Fail x) = Fail x
extractL (NonRetriable x) = NonRetriable x extractL (FailFatal x) = FailFatal x
extractR :: RouteResult (a :<|> b) -> RouteResult b extractR :: RouteResult (a :<|> b) -> RouteResult b
extractR (HandlerVal (_ :<|> b)) = HandlerVal b extractR (Route (_ :<|> b)) = Route b
extractR (Retriable x) = Retriable x extractR (Fail x) = Fail x
extractR (NonRetriable x) = NonRetriable x extractR (FailFatal x) = FailFatal x
-- | Fail with a @ServantErr@, but keep trying other paths and.
failWith :: ServantErr -> RouteResult a
failWith = Retriable
-- | Fail with immediately @ServantErr@.
failFatallyWith :: ServantErr -> RouteResult a
failFatallyWith = NonRetriable
-- | Return a value, and don't try other paths.
succeedWith :: a -> RouteResult a
succeedWith = HandlerVal
isMismatch :: RouteResult a -> Bool
isMismatch (Retriable _) = True
isMismatch _ = False

View file

@ -6,32 +6,38 @@
module Servant.Server.ErrorSpec (spec) where module Servant.Server.ErrorSpec (spec) where
import Data.Aeson (encode) import Data.Aeson (encode)
import qualified Data.ByteString.Lazy.Char8 as BC import qualified Data.ByteString.Lazy.Char8 as BCL
import qualified Data.ByteString.Char8 as BC
import Data.Proxy import Data.Proxy
import Network.HTTP.Types (hAccept, hContentType, methodGet, import Network.HTTP.Types (hAccept, hContentType, methodGet,
methodPost) methodPost, methodPut)
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Servant import Servant
-- 1) Check whether one or more endpoints have the right path. Otherwise return 404. -- The semantics of routing and handling requests should be as follows:
-- 2) Check whether the one of those have the right method. Otherwise return --
-- 405. If so, pick the first. We've now commited to calling at most one handler. * -- 1) Check whether one or more endpoints have the right path. Otherwise
-- 3) Check whether the Content-Type is known. Otherwise return 415. -- return 404.
-- 4) Check whether that one deserializes the body. Otherwise return 400. If there -- 2) Check whether the one of those have the right method. Otherwise return
-- was no Content-Type, try the first one of the API content-type list. -- 405. If so, pick the first. We've now committed to calling at most one
-- 5) Check whether the request is authorized. Otherwise return a 401. -- handler.
-- 6) Check whether the request is forbidden. If so return 403. -- 3) Check whether the Content-Type is known. Otherwise return 415.
-- 7) Check whether the request has a known Accept. Otherwise return 406. -- 4) Check whether that one deserializes the body. Otherwise return 400. If
-- 8) Check whether Accept-Language, Accept-Charset and Accept-Encoding exist and -- there was no Content-Type, try the first one of the API content-type list.
-- match. We can follow the webmachine order here. -- 5) Check whether the request is authorized. Otherwise return a 401.
-- 9) Call the handler. Whatever it returns, we return. -- 6) Check whether the request is forbidden. If so return 403.
-- 7) Check whether the request has a known Accept. Otherwise return 406.
-- 8) Check whether Accept-Language, Accept-Charset and Accept-Encoding
-- exist and match. We can follow the webmachine order here.
-- 9) Call the handler. Whatever it returns, we return.
spec :: Spec spec :: Spec
spec = describe "HTTP Errors" $ do spec = describe "HTTP Errors" $ do
errorOrderSpec errorOrderSpec
prioErrorsSpec
errorRetrySpec errorRetrySpec
errorChoiceSpec errorChoiceSpec
@ -83,6 +89,52 @@ errorOrderSpec = describe "HTTP error order"
request goodMethod goodUrl [goodContentType, badAccept] goodBody request goodMethod goodUrl [goodContentType, badAccept] goodBody
`shouldRespondWith` 406 `shouldRespondWith` 406
type PrioErrorsApi = ReqBody '[JSON] Integer :> "foo" :> Get '[JSON] Integer
prioErrorsApi :: Proxy PrioErrorsApi
prioErrorsApi = Proxy
-- Check whether matching continues even if a 'ReqBody' or similar construct
-- is encountered early in a path. We don't want to see a complaint about the
-- request body unless the path actually matches.
prioErrorsSpec :: Spec
prioErrorsSpec = describe "PrioErrors" $ do
let server = return
with (return $ serve prioErrorsApi server) $ do
let check (mdescr, method) path (cdescr, ctype, body) resp =
it fulldescr $
Test.Hspec.Wai.request method path [(hContentType, ctype)] body
`shouldRespondWith` resp
where
fulldescr = "returns " ++ show (matchStatus resp) ++ " on " ++ mdescr
++ " " ++ (BC.unpack path) ++ " (" ++ cdescr ++ ")"
get' = ("GET", methodGet)
put' = ("PUT", methodPut)
txt = ("text" , "text/plain;charset=utf8" , "42" )
ijson = ("invalid json", "application/json;charset=utf8", "invalid" )
vjson = ("valid json" , "application/json;charset=utf8", encode (5 :: Int))
check get' "/" txt 404
check get' "/bar" txt 404
check get' "/foo" txt 415
check put' "/" txt 404
check put' "/bar" txt 404
check put' "/foo" txt 405
check get' "/" ijson 404
check get' "/bar" ijson 404
check get' "/foo" ijson 400
check put' "/" ijson 404
check put' "/bar" ijson 404
check put' "/foo" ijson 405
check get' "/" vjson 404
check get' "/bar" vjson 404
check get' "/foo" vjson 200
check put' "/" vjson 404
check put' "/bar" vjson 404
check put' "/foo" vjson 405
-- }}} -- }}}
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- * Error Retry {{{ -- * Error Retry {{{
@ -190,8 +242,10 @@ errorChoiceSpec = describe "Multiple handlers return errors"
-- * Instances {{{ -- * Instances {{{
instance MimeUnrender PlainText Int where instance MimeUnrender PlainText Int where
mimeUnrender _ = Right . read . BC.unpack mimeUnrender _ = Right . read . BCL.unpack
instance MimeRender PlainText Int where instance MimeRender PlainText Int where
mimeRender _ = BC.pack . show mimeRender _ = BCL.pack . show
-- }}} -- }}}
--

View file

@ -1,11 +1,20 @@
<<<<<<< HEAD
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
=======
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
>>>>>>> Review fixes
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE TypeSynonymInstances #-}
<<<<<<< HEAD
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
=======
>>>>>>> Review fixes
module Servant.ServerSpec where module Servant.ServerSpec where
@ -32,6 +41,16 @@ import Network.Wai (Application, Request, pathInfo,
import Network.Wai.Internal (Response(ResponseBuilder)) import Network.Wai.Internal (Response(ResponseBuilder))
import Network.Wai.Test (defaultRequest, request, import Network.Wai.Test (defaultRequest, request,
runSession, simpleBody) runSession, simpleBody)
import Servant.API ((:<|>) (..), (:>), Capture, Delete,
Get, Header (..), Headers,
HttpVersion, IsSecure (..), JSON,
MatrixFlag, MatrixParam,
MatrixParams, Patch, PlainText,
Post, Put, QueryFlag, QueryParam,
QueryParams, Raw, RemoteHost,
ReqBody, addHeader)
import Servant.Server (ServantErr (..), Server, err404,
serve)
import Test.Hspec (Spec, describe, it, shouldBe) import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Hspec.Wai (get, liftIO, matchHeaders, import Test.Hspec.Wai (get, liftIO, matchHeaders,
matchStatus, post, request, matchStatus, post, request,
@ -96,7 +115,6 @@ spec = do
headerSpec headerSpec
rawSpec rawSpec
unionSpec unionSpec
prioErrorsSpec
routerSpec routerSpec
responseHeadersSpec responseHeadersSpec
miscReqCombinatorsSpec miscReqCombinatorsSpec
@ -526,55 +544,6 @@ responseHeadersSpec = describe "ResponseHeaders" $ do
Test.Hspec.Wai.request method "" [(hAccept, "crazy/mime")] "" Test.Hspec.Wai.request method "" [(hAccept, "crazy/mime")] ""
`shouldRespondWith` 406 `shouldRespondWith` 406
type PrioErrorsApi = ReqBody '[JSON] Person :> "foo" :> Get '[JSON] Integer
prioErrorsApi :: Proxy PrioErrorsApi
prioErrorsApi = Proxy
-- | Test the relative priority of error responses from the server.
--
-- In particular, we check whether matching continues even if a 'ReqBody'
-- or similar construct is encountered early in a path. We don't want to
-- see a complaint about the request body unless the path actually matches.
--
prioErrorsSpec :: Spec
prioErrorsSpec = describe "PrioErrors" $ do
let server = return . age
with (return $ serve prioErrorsApi server) $ do
let check (mdescr, method) path (cdescr, ctype, body) resp =
it fulldescr $
Test.Hspec.Wai.request method path [(hContentType, ctype)] body
`shouldRespondWith` resp
where
fulldescr = "returns " ++ show (matchStatus resp) ++ " on " ++ mdescr
++ " " ++ cs path ++ " (" ++ cdescr ++ ")"
get' = ("GET", methodGet)
put' = ("PUT", methodPut)
txt = ("text" , "text/plain;charset=utf8" , "42" )
ijson = ("invalid json", "application/json;charset=utf8", "invalid" )
vjson = ("valid json" , "application/json;charset=utf8", encode alice)
check get' "/" txt 404
check get' "/bar" txt 404
check get' "/foo" txt 415
check put' "/" txt 404
check put' "/bar" txt 404
check put' "/foo" txt 405
check get' "/" ijson 404
check get' "/bar" ijson 404
check get' "/foo" ijson 400
check put' "/" ijson 404
check put' "/bar" ijson 404
check put' "/foo" ijson 405
check get' "/" vjson 404
check get' "/bar" vjson 404
check get' "/foo" vjson 200
check put' "/" vjson 404
check put' "/bar" vjson 404
check put' "/foo" vjson 405
routerSpec :: Spec routerSpec :: Spec
routerSpec = do routerSpec = do