Implemented NoContentVerb and server instances for it

This commit is contained in:
Catherine Galkina 2019-09-07 18:13:46 +03:00 committed by Oleg Grenrus
parent 164f75711b
commit b440af900b
6 changed files with 46 additions and 13 deletions

View file

@ -75,10 +75,12 @@ import Servant.API
IsSecure (..), QueryFlag, QueryParam', QueryParams, Raw, IsSecure (..), QueryFlag, QueryParam', QueryParams, Raw,
ReflectMethod (reflectMethod), RemoteHost, ReqBody', ReflectMethod (reflectMethod), RemoteHost, ReqBody',
SBool (..), SBoolI (..), SourceIO, Stream, StreamBody', SBool (..), SBoolI (..), SourceIO, Stream, StreamBody',
Summary, ToSourceIO (..), Vault, Verb, WithNamedContext) Summary, ToSourceIO (..), Vault, Verb, NoContentVerb,
WithNamedContext)
import Servant.API.ContentTypes import Servant.API.ContentTypes
(AcceptHeader (..), AllCTRender (..), AllCTUnrender (..), (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
AllMime, MimeRender (..), MimeUnrender (..), canHandleAcceptH) AllMime, MimeRender (..), MimeUnrender (..), canHandleAcceptH,
NoContent (NoContent))
import Servant.API.Modifiers import Servant.API.Modifiers
(FoldLenient, FoldRequired, RequestArgument, (FoldLenient, FoldRequired, RequestArgument,
unfoldRequestArgument) unfoldRequestArgument)
@ -262,6 +264,17 @@ methodRouter splitHeaders method proxy status action = leafRouter route'
let bdy = if allowedMethodHead method request then "" else body let bdy = if allowedMethodHead method request then "" else body
in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy
noContentRouter :: Method
-> Status
-> Delayed env (Handler b)
-> Router env
noContentRouter method status action = leafRouter route'
where
route' env request respond =
runAction (action `addMethodCheck` methodCheck method request)
env request respond $ \ output ->
Route $ responseLBS status [] ""
instance {-# OVERLAPPABLE #-} instance {-# OVERLAPPABLE #-}
( AllCTRender ctypes a, ReflectMethod method, KnownNat status ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
) => HasServer (Verb method status ctypes a) context where ) => HasServer (Verb method status ctypes a) context where
@ -285,6 +298,15 @@ instance {-# OVERLAPPING #-}
where method = reflectMethod (Proxy :: Proxy method) where method = reflectMethod (Proxy :: Proxy method)
status = toEnum . fromInteger $ natVal (Proxy :: Proxy status) status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
instance {-# OVERLAPPABLE #-}
(ReflectMethod method
) => HasServer (NoContentVerb method) context where
type ServerT (NoContentVerb method) m = m NoContent
hoistServerWithContext _ _ nt s = nt s
route Proxy _ = noContentRouter method status204
where method = reflectMethod (Proxy :: Proxy method)
instance {-# OVERLAPPABLE #-} instance {-# OVERLAPPABLE #-}
( MimeRender ctype chunk, ReflectMethod method, KnownNat status, ( MimeRender ctype chunk, ReflectMethod method, KnownNat status,

View file

@ -51,7 +51,7 @@ import Servant.API
JSON, NoContent (..), NoFraming, OctetStream, Patch, JSON, NoContent (..), NoFraming, OctetStream, Patch,
PlainText, Post, Put, QueryFlag, QueryParam, QueryParams, Raw, PlainText, Post, Put, QueryFlag, QueryParam, QueryParams, Raw,
RemoteHost, ReqBody, SourceIO, StdMethod (..), Stream, Verb, RemoteHost, ReqBody, SourceIO, StdMethod (..), Stream, Verb,
addHeader) NoContentVerb, addHeader)
import Servant.Server import Servant.Server
(Context ((:.), EmptyContext), Handler, Server, Tagged (..), (Context ((:.), EmptyContext), Handler, Server, Tagged (..),
emptyServer, err400, err401, err403, err404, serve, serveWithContext) emptyServer, err400, err401, err403, err404, serve, serveWithContext)
@ -103,7 +103,7 @@ spec = do
type VerbApi method status type VerbApi method status
= Verb method status '[JSON] Person = Verb method status '[JSON] Person
:<|> "noContent" :> Verb method status '[JSON] NoContent :<|> "noContent" :> NoContentVerb method
:<|> "header" :> Verb method status '[JSON] (Headers '[Header "H" Int] Person) :<|> "header" :> Verb method status '[JSON] (Headers '[Header "H" Int] Person)
:<|> "headerNC" :> Verb method status '[JSON] (Headers '[Header "H" Int] NoContent) :<|> "headerNC" :> Verb method status '[JSON] (Headers '[Header "H" Int] NoContent)
:<|> "accept" :> ( Verb method status '[JSON] Person :<|> "accept" :> ( Verb method status '[JSON] Person
@ -140,7 +140,7 @@ verbSpec = describe "Servant.API.Verb" $ do
it "returns no content on NoContent" $ do it "returns no content on NoContent" $ do
response <- THW.request method "/noContent" [] "" response <- THW.request method "/noContent" [] ""
liftIO $ statusCode (simpleStatus response) `shouldBe` status liftIO $ statusCode (simpleStatus response) `shouldBe` 204
liftIO $ simpleBody response `shouldBe` "" liftIO $ simpleBody response `shouldBe` ""
-- HEAD should not return body -- HEAD should not return body

View file

@ -130,7 +130,8 @@ import Servant.API.Verbs
Post, PostAccepted, PostCreated, PostNoContent, Post, PostAccepted, PostCreated, PostNoContent,
PostNonAuthoritative, PostResetContent, Put, PutAccepted, PostNonAuthoritative, PostResetContent, Put, PutAccepted,
PutCreated, PutNoContent, PutNonAuthoritative, PutCreated, PutNoContent, PutNonAuthoritative,
ReflectMethod (reflectMethod), StdMethod (..), Verb) ReflectMethod (reflectMethod), StdMethod (..),
Verb, NoContentVerb)
import Servant.API.WithNamedContext import Servant.API.WithNamedContext
(WithNamedContext) (WithNamedContext)
import Servant.Links import Servant.Links

View file

@ -29,6 +29,12 @@ import Network.HTTP.Types.Method
data Verb (method :: k1) (statusCode :: Nat) (contentTypes :: [*]) (a :: *) data Verb (method :: k1) (statusCode :: Nat) (contentTypes :: [*]) (a :: *)
deriving (Typeable, Generic) deriving (Typeable, Generic)
-- | @NoContentVerb@ is a specific type to represent 'NoContent' responses.
-- It does not require either a list of content types (because there's
-- no content) or a status code (because it should always be 204).
data NoContentVerb (method :: k1)
deriving (Typeable, Generic)
-- * 200 responses -- * 200 responses
-- --
-- The 200 response is the workhorse of web servers, but also fairly generic. -- The 200 response is the workhorse of web servers, but also fairly generic.
@ -113,15 +119,15 @@ type PutNonAuthoritative = Verb 'PUT 203
-- If the document view should be reset, use @205 Reset Content@. -- If the document view should be reset, use @205 Reset Content@.
-- | 'GET' with 204 status code. -- | 'GET' with 204 status code.
type GetNoContent = Verb 'GET 204 type GetNoContent = NoContentVerb 'GET
-- | 'POST' with 204 status code. -- | 'POST' with 204 status code.
type PostNoContent = Verb 'POST 204 type PostNoContent = NoContentVerb 'POST
-- | 'DELETE' with 204 status code. -- | 'DELETE' with 204 status code.
type DeleteNoContent = Verb 'DELETE 204 type DeleteNoContent = NoContentVerb 'DELETE
-- | 'PATCH' with 204 status code. -- | 'PATCH' with 204 status code.
type PatchNoContent = Verb 'PATCH 204 type PatchNoContent = NoContentVerb 'PATCH
-- | 'PUT' with 204 status code. -- | 'PUT' with 204 status code.
type PutNoContent = Verb 'PUT 204 type PutNoContent = NoContentVerb 'PUT
-- ** 205 Reset Content -- ** 205 Reset Content

View file

@ -177,7 +177,7 @@ import Servant.API.TypeLevel
import Servant.API.Vault import Servant.API.Vault
(Vault) (Vault)
import Servant.API.Verbs import Servant.API.Verbs
(Verb) (Verb, NoContentVerb)
import Servant.API.WithNamedContext import Servant.API.WithNamedContext
(WithNamedContext) (WithNamedContext)
import Web.HttpApiData import Web.HttpApiData
@ -546,6 +546,10 @@ instance HasLink (Verb m s ct a) where
type MkLink (Verb m s ct a) r = r type MkLink (Verb m s ct a) r = r
toLink toA _ = toA toLink toA _ = toA
instance HasLink (NoContentVerb m) where
type MkLink (NoContentVerb m) r = r
toLink toA _ = toA
instance HasLink Raw where instance HasLink Raw where
type MkLink Raw a = a type MkLink Raw a = a
toLink toA _ = toA toLink toA _ = toA

View file

@ -64,7 +64,7 @@ type ComprehensiveAPIWithoutStreamingOrRaw' endpoint =
:<|> "res-headers" :> Get '[JSON] (Headers '[Header "foo" Int] NoContent) :<|> "res-headers" :> Get '[JSON] (Headers '[Header "foo" Int] NoContent)
:<|> "foo" :> GET :<|> "foo" :> GET
:<|> "vault" :> Vault :> GET :<|> "vault" :> Vault :> GET
:<|> "post-no-content" :> Verb 'POST 204 '[JSON] NoContent :<|> "post-no-content" :> PostNoContent
:<|> "post-int" :> Verb 'POST 204 '[JSON] Int :<|> "post-int" :> Verb 'POST 204 '[JSON] Int
:<|> "named-context" :> WithNamedContext "foo" '[] GET :<|> "named-context" :> WithNamedContext "foo" '[] GET
:<|> "capture-all" :> CaptureAll "foo" Int :> GET :<|> "capture-all" :> CaptureAll "foo" Int :> GET