2015-09-09 23:17:17 +02:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
2015-09-09 22:29:52 +02:00
|
|
|
module Servant.Server.ErrorSpec (spec) where
|
|
|
|
|
2015-09-09 23:17:17 +02:00
|
|
|
import Data.Aeson (encode)
|
|
|
|
import qualified Data.ByteString.Lazy.Char8 as BC
|
|
|
|
import Data.Proxy
|
2015-09-10 08:49:19 +02:00
|
|
|
import Network.HTTP.Types (hAccept, hContentType, methodGet,
|
|
|
|
methodPost)
|
2015-09-09 23:17:17 +02:00
|
|
|
import Test.Hspec
|
|
|
|
import Test.Hspec.Wai
|
2015-09-09 22:29:52 +02:00
|
|
|
|
2015-09-09 23:17:17 +02:00
|
|
|
import Servant
|
2015-09-09 22:29:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
-- 1) Check whether one or more endpoints have the right path. Otherwise return 404.
|
|
|
|
-- 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. *
|
|
|
|
-- 3) Check whether the Content-Type is known. Otherwise return 415.
|
|
|
|
-- 4) Check whether that one deserializes the body. Otherwise return 400. If there
|
|
|
|
-- was no Content-Type, try the first one of the API content-type list.
|
|
|
|
-- 5) Check whether the request is authorized. Otherwise return a 401.
|
|
|
|
-- 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
|
2015-09-09 23:17:17 +02:00
|
|
|
spec = describe "HTTP Errors" $ do
|
2015-09-10 08:49:19 +02:00
|
|
|
errorOrderSpec
|
|
|
|
errorRetrySpec
|
|
|
|
errorChoiceSpec
|
2015-09-09 22:29:52 +02:00
|
|
|
|
2015-09-09 23:17:17 +02:00
|
|
|
------------------------------------------------------------------------------
|
|
|
|
-- * Error Order {{{
|
2015-09-09 22:29:52 +02:00
|
|
|
|
|
|
|
type ErrorOrderApi = "home"
|
|
|
|
:> ReqBody '[JSON] Int
|
|
|
|
:> Capture "t" Int
|
|
|
|
:> Post '[JSON] Int
|
|
|
|
|
2015-09-10 08:49:19 +02:00
|
|
|
|
2015-09-09 22:29:52 +02:00
|
|
|
errorOrderApi :: Proxy ErrorOrderApi
|
|
|
|
errorOrderApi = Proxy
|
|
|
|
|
|
|
|
errorOrderServer :: Server ErrorOrderApi
|
2015-09-10 08:49:19 +02:00
|
|
|
errorOrderServer = \_ _ -> return 5
|
2015-09-09 22:29:52 +02:00
|
|
|
|
2015-09-10 08:49:19 +02:00
|
|
|
errorOrderSpec :: Spec
|
|
|
|
errorOrderSpec = describe "HTTP error order"
|
2015-09-09 22:29:52 +02:00
|
|
|
$ with (return $ serve errorOrderApi errorOrderServer) $ do
|
2015-09-10 08:49:19 +02:00
|
|
|
let badContentType = (hContentType, "text/plain")
|
|
|
|
badAccept = (hAccept, "text/plain")
|
2015-09-09 23:17:17 +02:00
|
|
|
badMethod = methodGet
|
|
|
|
badUrl = "home/nonexistent"
|
|
|
|
badBody = "nonsense"
|
2015-09-10 08:49:19 +02:00
|
|
|
goodContentType = (hContentType, "application/json")
|
2015-09-09 23:17:17 +02:00
|
|
|
goodMethod = methodPost
|
2015-09-10 08:49:19 +02:00
|
|
|
goodUrl = "home/2"
|
2015-09-09 23:17:17 +02:00
|
|
|
goodBody = encode (5 :: Int)
|
|
|
|
|
|
|
|
it "has 404 as its highest priority error" $ do
|
|
|
|
request badMethod badUrl [badContentType, badAccept] badBody
|
|
|
|
`shouldRespondWith` 404
|
|
|
|
|
|
|
|
it "has 405 as its second highest priority error" $ do
|
|
|
|
request badMethod goodUrl [badContentType, badAccept] badBody
|
|
|
|
`shouldRespondWith` 405
|
|
|
|
|
|
|
|
it "has 415 as its third highest priority error" $ do
|
|
|
|
request goodMethod goodUrl [badContentType, badAccept] badBody
|
|
|
|
`shouldRespondWith` 415
|
|
|
|
|
|
|
|
it "has 400 as its fourth highest priority error" $ do
|
|
|
|
request goodMethod goodUrl [goodContentType, badAccept] badBody
|
|
|
|
`shouldRespondWith` 400
|
|
|
|
|
|
|
|
it "has 406 as its fifth highest priority error" $ do
|
|
|
|
request goodMethod goodUrl [goodContentType, badAccept] goodBody
|
|
|
|
`shouldRespondWith` 406
|
|
|
|
|
|
|
|
-- }}}
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
-- * Error Retry {{{
|
|
|
|
|
|
|
|
type ErrorRetryApi
|
|
|
|
= "a" :> ReqBody '[JSON] Int :> Post '[JSON] Int -- 0
|
|
|
|
:<|> "a" :> ReqBody '[PlainText] Int :> Post '[JSON] Int -- 1
|
|
|
|
:<|> "a" :> ReqBody '[JSON] Int :> Post '[PlainText] Int -- 2
|
|
|
|
:<|> "a" :> ReqBody '[JSON] String :> Post '[JSON] Int -- 3
|
2015-09-10 08:49:19 +02:00
|
|
|
:<|> "a" :> ReqBody '[JSON] Int :> Get '[JSON] Int -- 4
|
|
|
|
:<|> "a" :> ReqBody '[JSON] Int :> Get '[PlainText] Int -- 5
|
2015-09-09 23:17:17 +02:00
|
|
|
:<|> ReqBody '[JSON] Int :> Get '[JSON] Int -- 6
|
2015-09-10 08:49:19 +02:00
|
|
|
:<|> ReqBody '[JSON] Int :> Post '[JSON] Int -- 7
|
2015-09-09 23:17:17 +02:00
|
|
|
|
|
|
|
errorRetryApi :: Proxy ErrorRetryApi
|
|
|
|
errorRetryApi = Proxy
|
|
|
|
|
|
|
|
errorRetryServer :: Server ErrorRetryApi
|
|
|
|
errorRetryServer
|
|
|
|
= (\_ -> return 0)
|
|
|
|
:<|> (\_ -> return 1)
|
|
|
|
:<|> (\_ -> return 2)
|
|
|
|
:<|> (\_ -> return 3)
|
|
|
|
:<|> (\_ -> return 4)
|
|
|
|
:<|> (\_ -> return 5)
|
|
|
|
:<|> (\_ -> return 6)
|
2015-09-10 08:49:19 +02:00
|
|
|
:<|> (\_ -> return 7)
|
2015-09-09 23:17:17 +02:00
|
|
|
|
2015-09-10 08:49:19 +02:00
|
|
|
errorRetrySpec :: Spec
|
|
|
|
errorRetrySpec = describe "Handler search"
|
2015-09-09 23:17:17 +02:00
|
|
|
$ with (return $ serve errorRetryApi errorRetryServer) $ do
|
2015-09-10 08:49:19 +02:00
|
|
|
|
|
|
|
let plainCT = (hContentType, "text/plain")
|
|
|
|
plainAccept = (hAccept, "text/plain")
|
|
|
|
jsonCT = (hContentType, "application/json")
|
|
|
|
jsonAccept = (hAccept, "application/json")
|
2015-09-09 23:17:17 +02:00
|
|
|
jsonBody = encode (1797 :: Int)
|
|
|
|
|
|
|
|
it "should continue when URLs don't match" $ do
|
|
|
|
request methodPost "" [jsonCT, jsonAccept] jsonBody
|
2015-09-10 08:49:19 +02:00
|
|
|
`shouldRespondWith` 201 { matchBody = Just $ encode (7 :: Int) }
|
2015-09-09 23:17:17 +02:00
|
|
|
|
|
|
|
it "should continue when methods don't match" $ do
|
|
|
|
request methodGet "a" [jsonCT, jsonAccept] jsonBody
|
|
|
|
`shouldRespondWith` 200 { matchBody = Just $ encode (4 :: Int) }
|
|
|
|
|
|
|
|
it "should not continue when Content-Types don't match" $ do
|
|
|
|
request methodPost "a" [plainCT, jsonAccept] jsonBody
|
|
|
|
`shouldRespondWith` 415
|
|
|
|
|
|
|
|
it "should not continue when body can't be deserialized" $ do
|
|
|
|
request methodPost "a" [jsonCT, jsonAccept] (encode ("nonsense" :: String))
|
|
|
|
`shouldRespondWith` 400
|
|
|
|
|
|
|
|
it "should not continue when Accepts don't match" $ do
|
|
|
|
request methodPost "a" [jsonCT, plainAccept] jsonBody
|
|
|
|
`shouldRespondWith` 406
|
|
|
|
|
2015-09-10 08:49:19 +02:00
|
|
|
-- }}}
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
-- * Error Choice {{{
|
|
|
|
|
|
|
|
type ErrorChoiceApi
|
|
|
|
= "path0" :> Get '[JSON] Int -- 0
|
|
|
|
:<|> "path1" :> Post '[JSON] Int -- 1
|
|
|
|
:<|> "path2" :> Post '[PlainText] Int -- 2
|
|
|
|
:<|> "path3" :> ReqBody '[JSON] Int :> Post '[PlainText] Int -- 3
|
|
|
|
:<|> "path4" :> (ReqBody '[PlainText] Int :> Post '[PlainText] Int -- 4
|
|
|
|
:<|> ReqBody '[PlainText] Int :> Post '[JSON] Int) -- 5
|
|
|
|
|
|
|
|
errorChoiceApi :: Proxy ErrorChoiceApi
|
|
|
|
errorChoiceApi = Proxy
|
|
|
|
|
|
|
|
errorChoiceServer :: Server ErrorChoiceApi
|
|
|
|
errorChoiceServer = return 0
|
|
|
|
:<|> return 1
|
|
|
|
:<|> return 2
|
|
|
|
:<|> (\_ -> return 3)
|
|
|
|
:<|> (\_ -> return 4)
|
|
|
|
:<|> (\_ -> return 5)
|
|
|
|
|
|
|
|
|
|
|
|
errorChoiceSpec :: Spec
|
|
|
|
errorChoiceSpec = describe "Multiple handlers return errors"
|
|
|
|
$ with (return $ serve errorChoiceApi errorChoiceServer) $ do
|
|
|
|
|
|
|
|
it "should respond with 404 if no path matches" $ do
|
|
|
|
request methodGet "" [] "" `shouldRespondWith` 404
|
|
|
|
|
|
|
|
it "should respond with 405 if a path but not method matches" $ do
|
|
|
|
request methodGet "path2" [] "" `shouldRespondWith` 405
|
|
|
|
|
|
|
|
it "should respond with the corresponding error if path and method match" $ do
|
|
|
|
request methodPost "path3" [(hContentType, "text/plain;charset=utf-8")] ""
|
|
|
|
`shouldRespondWith` 415
|
|
|
|
request methodPost "path3" [(hContentType, "application/json")] ""
|
|
|
|
`shouldRespondWith` 400
|
|
|
|
request methodPost "path4" [(hContentType, "text/plain;charset=utf-8"),
|
|
|
|
(hAccept, "application/json")] ""
|
|
|
|
`shouldRespondWith` 406
|
|
|
|
|
|
|
|
|
2015-09-09 23:17:17 +02:00
|
|
|
-- }}}
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
-- * Instances {{{
|
|
|
|
|
|
|
|
instance MimeUnrender PlainText Int where
|
|
|
|
mimeUnrender _ = Right . read . BC.unpack
|
|
|
|
|
|
|
|
instance MimeRender PlainText Int where
|
|
|
|
mimeRender _ = BC.pack . show
|
|
|
|
-- }}}
|