@@ -7,7 +7,6 @@
77{-# LANGUAGE ScopedTypeVariables #-}
88{-# LANGUAGE TypeFamilies #-}
99{-# LANGUAGE TypeOperators #-}
10−{-# LANGUAGE TypeSynonymInstances #-}
1110{-# OPTIONS_GHC -freduction-depth=100 #-}
1211
1312module Servant.ServerSpec where
@@ -49,14 +48,16 @@ import Network.Wai.Test
4948import Servant.API
5049 ((:<|>) (..), (:>), AuthProtect, BasicAuth,
5150 BasicAuthData (BasicAuthData), Capture, Capture', CaptureAll,
52− Delete, EmptyAPI, Get, Header, Headers, HttpVersion,
53− IsSecure (..), JSON, Lenient, NoContent (..), NoContentVerb,
54− NoFraming, OctetStream, Patch, PlainText, Post, Put,
55− QueryFlag, QueryParam, QueryParams, Raw, RemoteHost, ReqBody,
56− SourceIO, StdMethod (..), Stream, Strict, Verb, addHeader)
51+ Delete, EmptyAPI, Get, HasStatus(StatusOf), Header, Headers,
52+ HttpVersion, IsSecure (..), JSON, Lenient, NoContent (..),
53+ NoContentVerb, NoFraming, OctetStream, Patch, PlainText, Post,
54+ Put, QueryFlag, QueryParam, QueryParams, Raw, RemoteHost,
55+ ReqBody, SourceIO, StdMethod (..), Stream, Strict, Union,
56+ UVerb, Verb, addHeader)
5757import Servant.Server
5858 (Context ((:.), EmptyContext), Handler, Server, Tagged (..),
59− emptyServer, err401, err403, err404, serve, serveWithContext)
59+ emptyServer, err401, err403, err404, respond, serve,
60+ serveWithContext)
6061import Servant.Test.ComprehensiveAPI
6162import qualified Servant.Types.SourceT as S
6263import Test.Hspec
@@ -87,6 +88,7 @@ comprehensiveApiContext = NamedContext EmptyContext :. EmptyContext
8788spec :: Spec
8889spec = do
8990 verbSpec
91+ uverbSpec
9092 captureSpec
9193 captureAllSpec
9294 queryParamSpec
@@ -253,8 +255,8 @@ captureSpec = do
253255
254256 with (return (serve
255257 (Proxy :: Proxy (Capture "captured" String :> Raw))
256− (\ "captured" -> Tagged $ \request_ respond ->
257− respond $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do
258+ (\ "captured" -> Tagged $ \request_ sendResponse ->
259+ sendResponse $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do
258260 it "strips the captured path snippet from pathInfo" $ do
259261 get "/captured/foo" `shouldRespondWith` (fromString (show ["foo" :: String]))
260262
@@ -305,8 +307,8 @@ captureAllSpec = do
305307
306308 with (return (serve
307309 (Proxy :: Proxy (CaptureAll "segments" String :> Raw))
308− (\ _captured -> Tagged $ \request_ respond ->
309− respond $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do
310+ (\ _captured -> Tagged $ \request_ sendResponse ->
311+ sendResponse $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do
310312 it "consumes everything from pathInfo" $ do
311313 get "/captured/foo/bar/baz" `shouldRespondWith` (fromString (show ([] :: [Int])))
312314
@@ -544,8 +546,8 @@ rawApi :: Proxy RawApi
544546rawApi = Proxy
545547
546548rawApplication :: Show a => (Request -> a) -> Tagged m Application
547−rawApplication f = Tagged $ \request_ respond ->
548− respond $ responseLBS ok200 []
549+rawApplication f = Tagged $ \request_ sendResponse ->
550+ sendResponse $ responseLBS ok200 []
549551 (cs $ show $ f request_)
550552
551553rawSpec :: Spec
@@ -706,7 +708,7 @@ basicAuthApi = Proxy
706708basicAuthServer :: Server BasicAuthAPI
707709basicAuthServer =
708710 const (return jerry) :<|>
709− (Tagged $ \ _ respond -> respond $ responseLBS imATeapot418 [] "")
711+ (Tagged $ \ _ sendResponse -> sendResponse $ responseLBS imATeapot418 [] "")
710712
711713basicAuthContext :: Context '[ BasicAuthCheck () ]
712714basicAuthContext =
@@ -751,7 +753,7 @@ genAuthApi = Proxy
751753
752754genAuthServer :: Server GenAuthAPI
753755genAuthServer = const (return tweety)
754− :<|> (Tagged $ \ _ respond -> respond $ responseLBS imATeapot418 [] "")
756+ :<|> (Tagged $ \ _ sendResponse -> sendResponse $ responseLBS imATeapot418 [] "")
755757
756758type instance AuthServerData (AuthProtect "auth") = ()
757759
@@ -781,6 +783,73 @@ genAuthSpec = do
781783 it "plays nice with subsequent Raw endpoints" $ do
782784 get "/foo" `shouldRespondWith` 418
783785
786+-- }}}
787+------------------------------------------------------------------------------
788+-- * UVerb {{{
789+------------------------------------------------------------------------------
790+
791+newtype PersonResponse = PersonResponse Person
792+ deriving Generic
793+instance ToJSON PersonResponse
794+instance HasStatus PersonResponse where
795+ type StatusOf PersonResponse = 200
796+
797+newtype RedirectResponse = RedirectResponse String
798+ deriving Generic
799+instance ToJSON RedirectResponse
800+instance HasStatus RedirectResponse where
801+ type StatusOf RedirectResponse = 301
802+
803+newtype AnimalResponse = AnimalResponse Animal
804+ deriving Generic
805+instance ToJSON AnimalResponse
806+instance HasStatus AnimalResponse where
807+ type StatusOf AnimalResponse = 203
808+
809+
810+type UVerbApi
811+ = "person" :> Capture "shouldRedirect" Bool :> UVerb 'GET '[JSON] '[PersonResponse, RedirectResponse]
812+ :<|> "animal" :> UVerb 'GET '[JSON] '[AnimalResponse]
813+
814+uverbSpec :: Spec
815+uverbSpec = describe "Servant.API.UVerb " $ do
816+ let
817+ joe = Person "joe" 42
818+ mouse = Animal "Mouse" 7
819+
820+ personHandler
821+ :: Bool
822+ -> Handler (Union '[PersonResponse
823+ ,RedirectResponse])
824+ personHandler True = respond $ RedirectResponse "over there!"
825+ personHandler False = respond $ PersonResponse joe
826+
827+ animalHandler = respond $ AnimalResponse mouse
828+
829+ server :: Server UVerbApi
830+ server = personHandler :<|> animalHandler
831+
832+ with (pure $ serve (Proxy :: Proxy UVerbApi) server) $ do
833+ context "A route returning either 301/String or 200/Person" $ do
834+ context "when requesting the person" $ do
835+ let theRequest = THW.get "/person/false"
836+ it "returns status 200" $
837+ theRequest `shouldRespondWith` 200
838+ it "returns a person" $ do
839+ response <- theRequest
840+ liftIO $ decode' (simpleBody response) `shouldBe` Just joe
841+ context "requesting the redirect" $
842+ it "returns a message and status 301" $
843+ THW.get "/person/true"
844+ `shouldRespondWith` "\"over there!\"" {matchStatus = 301}
845+ context "a route with a single response type" $ do
846+ let theRequest = THW.get "/animal"
847+ it "should return the defined status code" $
848+ theRequest `shouldRespondWith` 203
849+ it "should return the expected response" $ do
850+ response <- theRequest
851+ liftIO $ decode' (simpleBody response) `shouldBe` Just mouse
852+
784853-- }}}
785854------------------------------------------------------------------------------
786855-- * Test data types {{{