Merge pull request #38 from haskell-servant/jkarni/fix-406

Don't succeedWith when response content-type is unacceptable.
This commit is contained in:
Julian Arni 2015-04-07 16:40:19 +02:00
commit 1eaed73794
2 changed files with 55 additions and 29 deletions

View file

@ -273,14 +273,14 @@ instance ( AllCTRender ctypes a
route Proxy action request respond route Proxy action request respond
| pathIsEmpty request && requestMethod request == methodGet = do | pathIsEmpty request && requestMethod request == methodGet = do
e <- runEitherT action e <- runEitherT action
respond . succeedWith $ case e of respond $ case e of
Right output -> do Right output -> do
let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
Nothing -> responseLBS (mkStatus 406 "Not Acceptable") [] "" Nothing -> failWith UnsupportedMediaType
Just (contentT, body) -> responseLBS ok200 [ ("Content-Type" Just (contentT, body) -> succeedWith $
, cs contentT)] body responseLBS ok200 [ ("Content-Type" , cs contentT)] body
Left (status, message) -> Left (status, message) -> succeedWith $
responseLBS (mkStatus status (cs message)) [] (cs message) responseLBS (mkStatus status (cs message)) [] (cs message)
| pathIsEmpty request && requestMethod request /= methodGet = | pathIsEmpty request && requestMethod request /= methodGet =
respond $ failWith WrongMethod respond $ failWith WrongMethod
@ -351,14 +351,14 @@ instance ( AllCTRender ctypes a
route Proxy action request respond route Proxy action request respond
| pathIsEmpty request && requestMethod request == methodPost = do | pathIsEmpty request && requestMethod request == methodPost = do
e <- runEitherT action e <- runEitherT action
respond . succeedWith $ case e of respond $ case e of
Right output -> do Right output -> do
let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
Nothing -> responseLBS (mkStatus 406 "") [] "" Nothing -> failWith UnsupportedMediaType
Just (contentT, body) -> responseLBS status201 [ ("Content-Type" Just (contentT, body) -> succeedWith $
, cs contentT)] body responseLBS status201 [ ("Content-Type" , cs contentT)] body
Left (status, message) -> Left (status, message) -> succeedWith $
responseLBS (mkStatus status (cs message)) [] (cs message) responseLBS (mkStatus status (cs message)) [] (cs message)
| pathIsEmpty request && requestMethod request /= methodPost = | pathIsEmpty request && requestMethod request /= methodPost =
respond $ failWith WrongMethod respond $ failWith WrongMethod
@ -397,14 +397,14 @@ instance ( AllCTRender ctypes a
route Proxy action request respond route Proxy action request respond
| pathIsEmpty request && requestMethod request == methodPut = do | pathIsEmpty request && requestMethod request == methodPut = do
e <- runEitherT action e <- runEitherT action
respond . succeedWith $ case e of respond $ case e of
Right output -> do Right output -> do
let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
Nothing -> responseLBS (mkStatus 406 "") [] "" Nothing -> failWith UnsupportedMediaType
Just (contentT, body) -> responseLBS status200 [ ("Content-Type" Just (contentT, body) -> succeedWith $
, cs contentT)] body responseLBS status200 [ ("Content-Type" , cs contentT)] body
Left (status, message) -> Left (status, message) -> succeedWith $
responseLBS (mkStatus status (cs message)) [] (cs message) responseLBS (mkStatus status (cs message)) [] (cs message)
| pathIsEmpty request && requestMethod request /= methodPut = | pathIsEmpty request && requestMethod request /= methodPut =
respond $ failWith WrongMethod respond $ failWith WrongMethod
@ -441,14 +441,14 @@ instance ( AllCTRender ctypes a
route Proxy action request respond route Proxy action request respond
| pathIsEmpty request && requestMethod request == methodPatch = do | pathIsEmpty request && requestMethod request == methodPatch = do
e <- runEitherT action e <- runEitherT action
respond . succeedWith $ case e of respond $ case e of
Right output -> do Right output -> do
let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request let accH = fromMaybe "*/*" $ lookup hAccept $ requestHeaders request
case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of case handleAcceptH (Proxy :: Proxy ctypes) (AcceptHeader accH) output of
Nothing -> responseLBS (mkStatus 406 "") [] "" Nothing -> failWith UnsupportedMediaType
Just (contentT, body) -> responseLBS status200 [ ("Content-Type" Just (contentT, body) -> succeedWith $
, cs contentT)] body responseLBS status200 [ ("Content-Type" , cs contentT)] body
Left (status, message) -> Left (status, message) -> succeedWith $
responseLBS (mkStatus status (cs message)) [] (cs message) responseLBS (mkStatus status (cs message)) [] (cs message)
| pathIsEmpty request && requestMethod request /= methodPatch = | pathIsEmpty request && requestMethod request /= methodPatch =
respond $ failWith WrongMethod respond $ failWith WrongMethod

View file

@ -15,8 +15,10 @@ import Data.Monoid ((<>))
import Data.Proxy (Proxy (Proxy)) import Data.Proxy (Proxy (Proxy))
import Data.String (fromString) import Data.String (fromString)
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import qualified Data.Text as T
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Network.HTTP.Types (hContentType, methodDelete, import Network.HTTP.Types (hAccept, hContentType,
methodDelete, methodGet,
methodPatch, methodPost, methodPut, methodPatch, methodPost, methodPut,
ok200, parseQuery, status409) ok200, parseQuery, status409)
import Network.Wai (Application, Request, pathInfo, import Network.Wai (Application, Request, pathInfo,
@ -31,8 +33,9 @@ import Test.Hspec.Wai (get, liftIO, matchStatus, post,
import Servant.API ((:<|>) (..), (:>), Capture, Delete, import Servant.API ((:<|>) (..), (:>), Capture, Delete,
Get, Header, JSON, MatrixFlag, Get, Header, JSON, MatrixFlag,
MatrixParam, MatrixParams, Patch, MatrixParam, MatrixParams, Patch,
Post, Put, QueryFlag, QueryParam, PlainText, Post, Put, QueryFlag,
QueryParams, Raw, ReqBody) QueryParam, QueryParams, Raw,
ReqBody)
import Servant.Server (Server, serve) import Servant.Server (Server, serve)
import Servant.Server.Internal (RouteMismatch (..)) import Servant.Server.Internal (RouteMismatch (..))
@ -41,7 +44,7 @@ import Servant.Server.Internal (RouteMismatch (..))
data Person = Person { data Person = Person {
name :: String, name :: String,
age :: Integer age :: Integer
} }
deriving (Eq, Show, Generic) deriving (Eq, Show, Generic)
@ -52,7 +55,7 @@ alice :: Person
alice = Person "Alice" 42 alice = Person "Alice" 42
data Animal = Animal { data Animal = Animal {
species :: String, species :: String,
numberOfLegs :: Integer numberOfLegs :: Integer
} }
deriving (Eq, Show, Generic) deriving (Eq, Show, Generic)
@ -97,10 +100,13 @@ captureSpec :: Spec
captureSpec = do captureSpec = do
describe "Servant.API.Capture" $ do describe "Servant.API.Capture" $ do
with (return (serve captureApi captureServer)) $ do with (return (serve captureApi captureServer)) $ do
it "can capture parts of the 'pathInfo'" $ do it "can capture parts of the 'pathInfo'" $ do
response <- get "/2" response <- get "/2"
liftIO $ do liftIO $ decode' (simpleBody response) `shouldBe` Just tweety
decode' (simpleBody response) `shouldBe` Just tweety
it "returns 404 if the decoding fails" $ do
get "/notAnInt" `shouldRespondWith` 404
with (return (serve with (return (serve
(Proxy :: Proxy (Capture "captured" String :> Raw)) (Proxy :: Proxy (Capture "captured" String :> Raw))
@ -120,18 +126,23 @@ getSpec = do
describe "Servant.API.Get" $ do describe "Servant.API.Get" $ do
let server = return alice :<|> return () let server = return alice :<|> return ()
with (return $ serve getApi server) $ do with (return $ serve getApi server) $ do
it "allows to GET a Person" $ do it "allows to GET a Person" $ do
response <- get "/" response <- get "/"
return response `shouldRespondWith` 200 return response `shouldRespondWith` 200
liftIO $ do liftIO $ decode' (simpleBody response) `shouldBe` Just alice
decode' (simpleBody response) `shouldBe` Just alice
it "throws 405 (wrong method) on POSTs" $ do it "throws 405 (wrong method) on POSTs" $ do
post "/" "" `shouldRespondWith` 405 post "/" "" `shouldRespondWith` 405
post "/empty" "" `shouldRespondWith` 405
it "returns 204 if the type is '()'" $ do it "returns 204 if the type is '()'" $ do
get "empty" `shouldRespondWith` ""{ matchStatus = 204 } get "empty" `shouldRespondWith` ""{ matchStatus = 204 }
it "returns 415 if the Accept header is not supported" $ do
Test.Hspec.Wai.request methodGet "" [(hAccept, "crazy/mime")] ""
`shouldRespondWith` 415
type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person
@ -483,6 +494,10 @@ rawSpec = do
type AlternativeApi = type AlternativeApi =
"foo" :> Get '[JSON] Person "foo" :> Get '[JSON] Person
:<|> "bar" :> Get '[JSON] Animal :<|> "bar" :> Get '[JSON] Animal
:<|> "foo" :> Get '[PlainText] T.Text
:<|> "bar" :> Post '[JSON] Animal
:<|> "bar" :> Put '[JSON] Animal
:<|> "bar" :> Delete
unionApi :: Proxy AlternativeApi unionApi :: Proxy AlternativeApi
unionApi = Proxy unionApi = Proxy
@ -490,11 +505,16 @@ unionServer :: Server AlternativeApi
unionServer = unionServer =
return alice return alice
:<|> return jerry :<|> return jerry
:<|> return "a string"
:<|> return jerry
:<|> return jerry
:<|> return ()
unionSpec :: Spec unionSpec :: Spec
unionSpec = do unionSpec = do
describe "Servant.API.Alternative" $ do describe "Servant.API.Alternative" $ do
with (return $ serve unionApi unionServer) $ do with (return $ serve unionApi unionServer) $ do
it "unions endpoints" $ do it "unions endpoints" $ do
response <- get "/foo" response <- get "/foo"
liftIO $ do liftIO $ do
@ -505,6 +525,12 @@ unionSpec = do
decode' (simpleBody response_) `shouldBe` decode' (simpleBody response_) `shouldBe`
Just jerry Just jerry
it "checks all endpoints before returning 415" $ do
get "/foo" `shouldRespondWith` 200
it "returns 404 if the path does not exist" $ do
get "/nonexistent" `shouldRespondWith` 404
-- | Test server error functionality. -- | Test server error functionality.
errorsSpec :: Spec errorsSpec :: Spec
errorsSpec = do errorsSpec = do