Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modelling sum types #80

Open
commandodev opened this issue Jun 28, 2018 · 10 comments
Open

Modelling sum types #80

commandodev opened this issue Jun 28, 2018 · 10 comments

Comments

@commandodev
Copy link

How do you go about expressing a sum type like this:

data WithdrawlResult =
    WithdrawlError ClientError   -- ^ Error with http client error
  | WithdrawlSuccess Transaction -- ^ Success with transaction details
  deriving (Show, Typeable, Generic)

instance ToJSON WithdrawlResult where
    toJSON (WithdrawlSuccess txn) =
        object ["success" .= txn]
    toJSON (WithdrawlError err) =
        object ["error" .= show err]

wdDesc :: Text
wdDesc = "An object with either a success field containing the transaction or "
      <> "an error field containing the ClientError from the wallet as a string"

instance ToSchema WithdrawlResult where
    declareNamedSchema _ = do
        txnSchema <- declareSchemaRef (Proxy :: Proxy Transaction)
        errSchema <- declareSchemaRef (Proxy :: Proxy String)
        return $ NamedSchema (Just "WithdrawlResult") $ mempty
          & type_ .~ SwaggerObject
          & enum_ ?~ [ object ["success" .= toJSON txnSchema]
                               , object ["error"   .= toJSON errSchema]
                               ]
          & properties .~ (mempty
               & at "success" ?~ txnSchema
               & at "error" ?~ errSchema)
          & description .~ (Just $ wdDesc)

Ideally I'd want to express that I'm expecting a JSON object with either { success: {...}} or { error: "Some message" } I can't quite figure out how to do that...

@phadej
Copy link
Contributor

phadej commented Jun 28, 2018

Can Swagger2 represent those at all? AFAIK the best you can do is mark both fields optional.

ping @fizruk

@commandodev
Copy link
Author

Ah I see. Thanks @phadej - how do you mark the fields as optional? I also tried doing an explanation, but you can't see wdDesc anywhere in the UI...

@fizruk
Copy link
Member

fizruk commented Jun 28, 2018

So yeah, true sum types are not supported well by Swagger 2.0.

Note: sum types can be supported in OpenAPI 3.0, via oneOf.

Still, you can do as @phadej suggested — mark both fields optional. Additionally you can specify that there should be exactly 1 field (no more, no less) using minProperties and maxProperties ("properties" is what fields are called in a JSON object).

You can actually see that approach in an existing instance for Either a b:

>>> BSL8.putStrLn . encodePretty $ toSchema (Proxy :: Proxy (Either Integer String))
{
    "minProperties": 1,
    "maxProperties": 1,
    "type": "object",
    "properties": {
        "Left": {
            "type": "integer"
        },
        "Right": {
            "type": "string"
        }
    }
}

If you look at the definition, you'll find that it's actually a derived Generic-based implementation:

instance (ToSchema a, ToSchema b) => ToSchema (Either a b)

That means you can achieve the same for your type. Note that you can use genericDeclareNamedSchema explicitly to specify property names for constructors:

data WithdrawlResult =
    WithdrawlError ClientError   -- ^ Error with http client error
  | WithdrawlSuccess Transaction -- ^ Success with transaction details
  deriving (Show, Typeable, Generic)

wdDesc :: Text
wdDesc = "An object with either a success field containing the transaction or "
      <> "an error field containing the ClientError from the wallet as a string"

instance ToSchema WithdrawlResult where
  declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
    { constructorTagModifier = map toLower . drop (length "Withdrawl") }
    & mapped.mapped.schema.description ?~ wdDesc

You can check that it works in GHCi:

>>> BSL8.putStrLn . encodePretty $ toSchema (Proxy :: Proxy WithdrawlResult)
{
    "minProperties": 1,
    "maxProperties": 1,
    "type": "object",
    "description": "An object with either a success field containing the transaction or an error field containing the ClientError from the wallet as a string",
    "properties": {
        "error": {
            ...
        },
        "success": {
            ...
        }
    }
}

@commandodev
Copy link
Author

commandodev commented Jun 28, 2018 via email

commandodev pushed a commit to input-output-hk/cardano-sl that referenced this issue Jul 2, 2018
@neongreen
Copy link
Contributor

@fizruk

So yeah, true sum types are not supported well by Swagger 2.0 (and OpenAPI 3.0 AFAIK).

Hm, I thought OpenAPI 3.0 supported them (with oneOf) -- is there something there that makes you say that they're not supported "well"?

@fizruk
Copy link
Member

fizruk commented Sep 24, 2018

@neongreen yes, you are right, oneOf is what we need for sum types and it is in OpenAPI 3.0: https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/

I will edit my previous comment.

@AnthonySuper
Copy link

Is there a plan for supporting OpenAPI 3.0 in this module? I think that the oneOf feature alone provides a lot of value.

@saschat
Copy link

saschat commented May 25, 2019

@AnthonySuper see #99

@nsotelo
Copy link

nsotelo commented Sep 14, 2020

Now that #99 has been closed, can we also close this one?

@akhesaCaro
Copy link
Contributor

Hi,
Servant-swagger will be moved into the main Servant repo (see : haskell-servant/servant#1475)
If this issue is still relevant, would it be possible for you to summit it there? : https://github.com/haskell-servant/servant/issues

Thanks in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants