I have a Haskell type that is, roughly:
data WithMetadata a m = WithMetadata a m
instance (ToJSON a, ToJSON m) => ToJSON (WithMetadata a m) where
toJSON (WithMeta a m) = unionObjects (toJSON a) (toJSON m)
unionObjects :: Value -> Value -> Value
unionObjects = undefined
Basically it just says that { foo: bar } `WithMetadata` { baz: bat }
encodes like { foo: bar, baz: bat }
Writing the ToSchema
for this was surprisingly pleasant,
instance (ToSchema a, ToSchema m) => ToSchema (a `WithMetadata` m) where
declareNamedSchema _ = do
aSchema <- declareSchemaRef (Proxy @a)
mSchema <- declareSchemaRef (Proxy @m)
pure $
NamedSchema Nothing $
mempty
& type_ ?~ OpenApiObject
& allOf ?~ [aSchema, mSchema]
Schema and expanded references
{
"items": {
"allOf": [
{
"$ref": "#/components/schemas/StandardSet" // this is the a
},
{
"properties": { // and this is the m
"administrativeAreas": {
"items": {
"$ref": "#/components/schemas/CountryAdministrativeArea"
},
"type": "array"
}
},
"required": [
"administrativeAreas"
],
"type": "object"
}
],
"type": "object"
},
"type": "array"
}
{
"CountryAdministrativeArea": {
"properties": {
"administrativeArea": {
"type": "string"
},
"countryCode": {
"$ref": "#/components/schemas/CountryCode"
}
},
"required": [
"countryCode"
],
"type": "object"
},
"CountryCode": {
"type": "string"
},
"StandardSet": {
"properties": {
"description": {
"type": "string"
},
"domainLabel": {
"type": "string"
},
"id": {
"type": "string"
},
"isLive": {
"type": "boolean"
},
"name": {
"type": "string"
},
"standardLabel": {
"type": "string"
}
},
"required": [
"id",
"name",
"description",
"domainLabel",
"standardLabel",
"isLive"
],
"type": "object"
}
}
I don't claim to know the semantics of allOf
, but the documentation came out exactly as I wanted: it shows me an object with all of the properties of a
and m
:
[
{
"description": "string", // these fields are the ToJSON of a
"domainLabel": "string",
"id": "string",
"isLive": true,
"name": "string",
"standardLabel": "string",
"administrativeAreas": [ // and this field is the ToJSON of m
{
"administrativeArea": "string",
"countryCode": "string"
}
]
}
]
As well as a combined schema, as if I had written it by hand:
![](https://camo.githubusercontent.com/a0cc628cd6671fd137cf9eafc94e72ab67adace75101c2a7b71a88218ab62493/68747470733a2f2f66696c65732e706272697362696e2e636f6d2f73637265656e73686f74732f73637265656e73686f742e3539383536312e706e67)
But then I tried to use Data.OpenApi.validateJSON
with this schema and a valid example, and I get a failure on every single field being unexpected:
Errors:
- property "administrativeAreas" is found in JSON value, but it is not mentioned in Swagger schema
- property "description" is found in JSON value, but it is not mentioned in Swagger schema
- property "domainLabel" is found in JSON value, but it is not mentioned in Swagger schema
- property "id" is found in JSON value, but it is not mentioned in Swagger schema
- property "isLive" is found in JSON value, but it is not mentioned in Swagger schema
- property "name" is found in JSON value, but it is not mentioned in Swagger schema
- property "standardLabel" is found in JSON value, but it is not mentioned in Swagger schema
I think this comes from the implementation for allOf
:
(view allOf -> Just variants) -> do
-- Default semantics for Validation Monad will abort when at least one
-- variant does not match.
forM_ variants $ \var ->
validateWithSchemaRef var val
What is probably happening is it is validating each of the allOf
schemas individually, so any properties appear as extra when validating any one of the other schemas that don't specify them.
I think (again, just based on what the Example and Schema docs show), the intent of allOf
(at least with type: object
) would be to union them all into one schema, then validate with that.
The comment says "'Default' semantics" -- is there some way I can get the semantics I expected instead?