Modelling sum types
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...
Can Swagger2 represent those at all? AFAIK the best you can do is mark both fields optional.
ping @fizruk
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...
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": {
...
}
}
}
Nice! Thanks a lot!
On Thu, 28 Jun 2018 at 14:52, Nickolay Kudasov [email protected] wrote:
So yeah, true sum types are not supported well by Swagger 2.0 (and OpenAPI 3.0 AFAIK).
Still, you can do as @phadej https://github.com/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": { ... } } }
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/haskell-servant/servant-swagger/issues/80#issuecomment-401042442, or mute the thread https://github.com/notifications/unsubscribe-auth/AAFW1Hw__gQ329y231XvKBozMfV9dn5Pks5uBN-sgaJpZM4U7Cod .
-- Regards, Ben Ford [email protected] +447540722690
@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"?
@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.
Is there a plan for supporting OpenAPI 3.0 in this module? I think that the oneOf feature alone provides a lot of value.
@AnthonySuper see #99
Now that #99 has been closed, can we also close this one?
Hi, Servant-swagger will be moved into the main Servant repo (see : https://github.com/haskell-servant/servant/pull/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!