@@ -1,15 +1,19 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE MultiParamTypeClasses #-}
1−{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE TypeOperators #-}
4+{-# LANGUAGE TypeOperators #-}
3−{-# LANGUAGE DataKinds #-}
5+{-# OPTIONS_GHC -fno-warn-orphans #-}
4module Servant.Server.ErrorSpec (spec) where
6module Servant.Server.ErrorSpec (spec) where
6−import Test.Hspec
8+import Data.Aeson (encode)
9+import qualified Data.ByteString.Lazy.Char8 as BC
10+import Control.Monad.Trans.Either (left)
7−import Data.Proxy
11+import Data.Proxy
8−import Test.Hspec.Wai (request, with, shouldRespondWith)
9−import Network.HTTP.Types (methodGet, methodPost)
12+import Network.HTTP.Types (methodGet, methodPost)
10−import Data.Aeson (encode)
13+import Test.Hspec
12−import Servant
16+import Servant
15-- 1) Check whether one or more endpoints have the right path. Otherwise return 404.
19-- 1) Check whether one or more endpoints have the right path. Otherwise return 404.
@@ -26,9 +30,12 @@ import Servant
26-- 9) Call the handler. Whatever it returns, we return.
30-- 9) Call the handler. Whatever it returns, we return.
28spec :: Spec
32spec :: Spec
29−spec = do
33+spec = describe "HTTP Errors" $ do
30 errorOrder
34 errorOrder
37+------------------------------------------------------------------------------
33type ErrorOrderApi = "home"
40type ErrorOrderApi = "home"
34 :> ReqBody '[JSON] Int
41 :> ReqBody '[JSON] Int
@@ -39,40 +46,108 @@ errorOrderApi :: Proxy ErrorOrderApi
39errorOrderApi = Proxy
46errorOrderApi = Proxy
41errorOrderServer :: Server ErrorOrderApi
48errorOrderServer :: Server ErrorOrderApi
42−errorOrderServer = \_ _ -> return 10
49+errorOrderServer = \_ _ -> left err402
44errorOrder :: Spec
51errorOrder :: Spec
45errorOrder = describe "HTTP error order"
52errorOrder = describe "HTTP error order"
46 $ with (return $ serve errorOrderApi errorOrderServer) $ do
53 $ with (return $ serve errorOrderApi errorOrderServer) $ do
47− let badContentType = ("Content-Type", "text/plain")
54+ let badContentType = ("Content-Type", "text/plain")
48− badAccept = ("Accept", "text/plain")
55+ badAccept = ("Accept", "text/plain")
49− badMethod = methodGet
56+ badMethod = methodGet
50− badUrl = "home/nonexistent"
57+ badUrl = "home/nonexistent"
51− badBody = "nonsense"
58+ badBody = "nonsense"
52− goodContentType = ("Content-Type", "application/json")
59+ goodContentType = ("Content-Type", "application/json")
53− goodAccept = ("Accept", "application/json")
60+ goodAccept = ("Accept", "application/json")
54− goodMethod = methodPost
61+ goodMethod = methodPost
55− goodUrl = "home/5"
62+ goodUrl = "home/5"
56− goodBody = encode (5 :: Int)
63+ goodBody = encode (5 :: Int)
58− it "has 404 as its highest priority error" $ do
65+ it "has 404 as its highest priority error" $ do
59− request badMethod badUrl [badContentType, badAccept] badBody
66+ request badMethod badUrl [badContentType, badAccept] badBody
60− `shouldRespondWith` 404
67+ `shouldRespondWith` 404
62− it "has 405 as its second highest priority error" $ do
69+ it "has 405 as its second highest priority error" $ do
63− request badMethod goodUrl [badContentType, badAccept] badBody
70+ request badMethod goodUrl [badContentType, badAccept] badBody
64− `shouldRespondWith` 405
71+ `shouldRespondWith` 405
66− it "has 415 as its third highest priority error" $ do
73+ it "has 415 as its third highest priority error" $ do
67− request goodMethod goodUrl [badContentType, badAccept] badBody
74+ request goodMethod goodUrl [badContentType, badAccept] badBody
68− `shouldRespondWith` 415
75+ `shouldRespondWith` 415
70− it "has 400 as its fourth highest priority error" $ do
77+ it "has 400 as its fourth highest priority error" $ do
71− request goodMethod goodUrl [goodContentType, badAccept] badBody
78+ request goodMethod goodUrl [goodContentType, badAccept] badBody
72− `shouldRespondWith` 400
79+ `shouldRespondWith` 400
74− it "has 406 as its fifth highest priority error" $ do
81+ it "has 406 as its fifth highest priority error" $ do
75− request goodMethod goodUrl [goodContentType, badAccept] goodBody
82+ request goodMethod goodUrl [goodContentType, badAccept] goodBody
76− `shouldRespondWith` 406
83+ `shouldRespondWith` 406
78−
85+ it "returns handler errors as its lower priority errors" $ do
86+ request goodMethod goodUrl [goodContentType, goodAccept] goodBody
87+ `shouldRespondWith` 402
90+------------------------------------------------------------------------------
94+ = "a" :> ReqBody '[JSON] Int :> Post '[JSON] Int -- 0
95+ :<|> "a" :> ReqBody '[PlainText] Int :> Post '[JSON] Int -- 1
96+ :<|> "a" :> ReqBody '[JSON] Int :> Post '[PlainText] Int -- 2
97+ :<|> "a" :> ReqBody '[JSON] String :> Post '[JSON] Int -- 3
98+ :<|> "a" :> ReqBody '[JSON] Int :> Get '[PlainText] Int -- 4
99+ :<|> ReqBody '[JSON] Int :> Get '[JSON] Int -- 5
100+ :<|> ReqBody '[JSON] Int :> Get '[JSON] Int -- 6
102+errorRetryApi :: Proxy ErrorRetryApi
103+errorRetryApi = Proxy
105+errorRetryServer :: Server ErrorRetryApi
108+ :<|> (\_ -> return 1)
109+ :<|> (\_ -> return 2)
110+ :<|> (\_ -> return 3)
111+ :<|> (\_ -> return 4)
112+ :<|> (\_ -> return 5)
113+ :<|> (\_ -> return 6)
116+errorRetry = describe "Handler search"
117+ $ with (return $ serve errorRetryApi errorRetryServer) $ do
118+ let plainCT = ("Content-Type", "text/plain")
119+ plainAccept = ("Accept", "text/plain")
120+ jsonCT = ("Content-Type", "application/json")
121+ jsonAccept = ("Accept", "application/json")
122+ jsonBody = encode (1797 :: Int)
124+ it "should continue when URLs don't match" $ do
125+ request methodPost "" [jsonCT, jsonAccept] jsonBody
126+ `shouldRespondWith` 201 { matchBody = Just $ encode (5 :: Int) }
128+ it "should continue when methods don't match" $ do
129+ request methodGet "a" [jsonCT, jsonAccept] jsonBody
130+ `shouldRespondWith` 200 { matchBody = Just $ encode (4 :: Int) }
132+ it "should not continue when Content-Types don't match" $ do
133+ request methodPost "a" [plainCT, jsonAccept] jsonBody
134+ `shouldRespondWith` 415
136+ it "should not continue when body can't be deserialized" $ do
137+ request methodPost "a" [jsonCT, jsonAccept] (encode ("nonsense" :: String))
138+ `shouldRespondWith` 400
140+ it "should not continue when Accepts don't match" $ do
141+ request methodPost "a" [jsonCT, plainAccept] jsonBody
142+ `shouldRespondWith` 406
145+------------------------------------------------------------------------------
148+instance MimeUnrender PlainText Int where
149+ mimeUnrender _ = Right . read . BC.unpack
151+instance MimeRender PlainText Int where
152+ mimeRender _ = BC.pack . show