NoContent limitations
As far as I understand, it is currently impossible to use NoContent:
- to return an empty body with a status code different from 204,
- to return empty bodies from a
UVerbendpoint.
We have worked around these limitations in https://github.com/wireapp/wire-server by introducing a type:
data EmptyResult (s :: Nat) = EmptyResult
and adding some special HasServer (and HasClient) instances for EmptyResult with an empty list of content types for both Verb and UVerb.
[There is still the limitation that is not possible to return an empty body for some of the possible results of a UVerb, but not all, because then the list of content types is not empty. This stems from a limitation with UVerb itself, namely that the content types are fixed, and cannot depend on the result of the handler. I'm not sure if anything can be done about that, short of completely redesigning UVerb. Maybe this belongs to a separate issue.]
Am I missing some other potential solutions? Would it make sense to submit a PR with the workaround above? If so, should NoContent be removed, as it would be superseded by EmptyResult 204?
The non-UVerb and even the UVerb approaches indeed don't feel quite optimal, not entirely allowing us to say what we want to say. Also bring in a bunch of chances for things to go south with the overlapping instances mess. I'd be very interested in seeing a more unified, flexible solution. But it has yet to be found.
In the meantime, I would probably make sense to indeed have your workaround, and then perhaps just have the typical NoContent just be a synonym for EmptyResutl 204?
(Sorry for the delay, it's been a busy summer for me.)
I believe we've opted to just use
PostCreated '[PlainText] NoContent
And add whatever instance it complains about so that it returns a 201 Created 👍 with an empty body
Another limitation that I encountered this afternoon is the inability to MimeUnrender NoContent from a variety of common things, for example JSON. Consider the following example, which doesn't compile with the error given below. As a server this compiles fine because we have Accept ctyp => AllMimeRender '[ctyp] NoContent. However we don't have a corresponding AllMimeUnrender instance thus we fail to be able to instantiate the client, because we don't have a FromJSON instance for NoContent.
It might be possible to solve this problem by adding a AllMimeUnrender instance for NoContent that always succeeds returning NoContent, although I can see that this isn't completely satisfactory because it would successfully parse responses containing bodies.
#!/usr/bin/env cabal
{- cabal:
build-depends:
, base
, servant
, servant-client
, servant-client-core
-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -ddump-cs-trace #-}
module Main where
import Data.Proxy
import Servant.API
import Servant.Client
import Servant.Client.Core
type Api = "test" :> UVerb 'POST '[JSON] '[WithStatus 400 Int, NoContent]
client :: Client ClientM Api
client = clientIn (Proxy :: Proxy Api) (Proxy :: Proxy ClientM)
main :: IO ()
main = error "can't run this; it only exists to be compiled"
Main.hs:22:10: error:
• No instance for (aeson-2.0.3.0:Data.Aeson.Types.FromJSON.FromJSON
NoContent)
arising from a use of ‘clientIn’
• In the expression:
clientIn (Proxy :: Proxy Api) (Proxy :: Proxy ClientM)
In an equation for ‘Main.client’:
Main.client
= clientIn (Proxy :: Proxy Api) (Proxy :: Proxy ClientM)
|
22 | client = clientIn (Proxy :: Proxy Api) (Proxy :: Proxy ClientM)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Making a MimeUnrender ctyp NoContent should be easy by enforcing the body should be empty, right?
mimeUnrender _ "" = Right NoContent
mimeUnrender _ _ = Left "Unexpected content"
Though I guess this would clash with the MimeUnrender JSON a instance. 🤔
I suppose servant already uses overlapping instance though so maybe that's fine?