Hey @llfbandit,
your library is great and helps a lot to validate data against a rather complex schema, but I encountered some problems, which I like to share with an example.
My example OpenAPI spec defines a Zoo
which contains:
- root references like:
$ref: "#/components/schemas/???"
. Which leads to a schemaParentNode
/parentSchema
problem.
allOf
/anyOf
quantor for grouped objects, see: BigFive
. Which one is to pick, if I want to allow any Animal
within that group?
anyOf
/oneOf
quantor for array items
, see: SafariPark.animals
. Which one is to pick, if I want to allow any of the given Animals
by its group?
nullable
referenced objects, as follows (see also Reference objects don't combine well with โnullableโ #1368 , Clarify Semantics of nullable in OpenAPI 3.0):
"Sibling values alongside $refs are ignored. To add properties to a $ref, wrap the $ref into allOf, or move the extra properties into the referenced definition (if applicable)." (swagger-editor)
properties:
maybeSafari:
$ref: "#/components/schemas/SafariPark"
-->
SafariPark:
type: object
nullable: true
nullable
enum
s do not work as expected:
subspecies:
type: string
nullable: true
enum:
- PantheraLeoLeo
- PantheraLeoMelanochaita
- null
I have four problems:
Problem 1:
I guess with the upcoming changes in 0.9
I won't be able to instantiate the SchemaValidator with the schemaParentNode
and parentSchema
, as this constructor is not public anymore.
I wrote a validation method in Scala like so:
def validate(schemaName: String, data: JsonNode, isValid: Boolean, schema: JsonNode): ValidationResults = {
val apiContext: OAI3Context = new OAI3Context(new URI("/"), schema)
val validationContext: ValidationContext[OAI3] = new ValidationContext(apiContext)
validationContext.setFastFail(true)
val parentValidator = new SchemaValidator("", schema)
// 0.8
val objectValidator: SchemaValidator = new SchemaValidator(
validationContext, // context
schemaName, // propertyName
Try(schema.get("components").get("schemas").get(schemaName)).getOrElse(schema), // schemaNode
schema, // schemaParentNode
parentValidator // parentSchema
)
val validation = new ValidationResults();
// 0.9-SNAPSHOT
// val objectValidator: SchemaValidator = new SchemaValidator(
// validationContext,
// schemaName,
// schema.get("components").get("schemas").get(schemaName)
// ???
// )
// val validation: ValidationData[Unit] = new ValidationData();
objectValidator.validate(data, validation)
if (validation.isValid === false) println(s"'$schemaName' $validation")
// assert(validation.isValid === isValid)
validation
}
This validates:
"A `Zoo` with a `SafariPark`" should "only contain `BigFive` `Animal`s" in {
val schema: JsonNode = TreeUtil.json.readTree(new File("zoo.json"))
val data: JsonNode = TreeUtil.json.readTree(
"""
|{
| "name": "Zoologischer Garten Berlin AG",
| "maybeNickname": "Zoo Berlin",
| "parks": {
| "maybeSafari": {
| "name": "Great Safari Park",
| "animals": [
| {
| "species": "LoxodontaAfricana",
| "subspecies": null
| },
| {
| "species": "PantheraLeo",
| "subspecies": "PantheraLeoLeo"
| }
| ]
| }
| }
|}
|""".stripMargin)
val result = validate("Zoo", data, true, schema)
assert(result.isValid === true)
}
Problem 2:
If I try at purpose to break the schema, by changing species
to a forbidden value, then I get a rather uninformative validation report like this:
'Zoo' Validation error(s) :
Zoo.parks.maybeSafari./#/components/schemas/SafariPark.animals.items.anyOf : No valid schema. (code: 1001)
Problem 3:
nullable
referenced objects are not validated as nullables:
"A `Zoo` without a `SafariPark`" should "be valid, too" in {
val schema: JsonNode = TreeUtil.json.readTree(new File("zoo.json"))
val data: JsonNode = TreeUtil.json.readTree(
"""
|{
| "name": "Zoologischer Garten Berlin AG",
| "maybeNickname": "Zoo Berlin",
| "parks": {
| "maybeSafari": null
| }
|}
|""".stripMargin)
val result = validate("Zoo", data, true, schema)
assert(result.isValid === true)
}
Which give me the following validation error:
'Zoo' Validation error(s) :
Zoo.parks.maybeSafari.nullable : Null value is not allowed. (code: 1021)
Problem 4:
This validates fine:
"A valid `Animal` with a nullable enum" should "be valid" in {
val schema: JsonNode = TreeUtil.json.readTree(new File("zoo.json"))
val data: JsonNode = TreeUtil.json.readTree(
"""
|{
| "species": "PantheraPardus",
| "subspecies": null
|}
|""".stripMargin)
val result = validate("Animal", data, true, schema)
assert(result.isValid === true)
}
But this does not, as the validator seems to accept any value, but I expect the enum
to forbid such values:
"An invalid `Animal` with a nullable enum" should "not be valid" in {
val schema: JsonNode = TreeUtil.json.readTree(new File("zoo.json"))
val data: JsonNode = TreeUtil.json.readTree(
"""
|{
| "species": "PantheraPardus",
| "subspecies": "not allowed"
|}
|""".stripMargin)
val result = validate("Animal", data, false, schema)
assert(result.isValid === false)
}
This it the full zoo.yaml
:
openapi: 3.0.0
info:
title: Zootopia
description: "The zoo is a place where animals are held. Hopefully under good circumstances."
version: 1.0.0
paths:
/api/v1/zoo/{name}:
get:
parameters:
- in: path
name: name
required: true
schema:
type: string
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/Zoo"
components:
schemas:
Zoo:
type: object
required:
- name
properties:
name:
type: string
maybeNickname:
type: string
nullable: true
parks:
properties:
maybeSafari:
$ref: "#/components/schemas/SafariPark"
# nullable: true # "Sibling values alongside $refs are ignored. To add properties to a $ref, wrap the $ref into allOf, or move the extra properties into the referenced definition (if applicable)." (swagger-editor)
# apes:
# ...
# birds:
# ...
# aquarium:
# ...
SafariPark:
type: object
nullable: true
required:
- name
- animals
properties:
name:
type: string
animals:
type: array
items:
anyOf:
- $ref: "#/components/schemas/BigFive"
Animal:
type: object
required:
- species
properties:
species:
type: string
subspecies:
type: string
nullable: true
discriminator:
propertyName: species
mapping:
LoxodontaAfricana: '#/components/schemas/Elephant'
LoxodontaCyclotis: '#/components/schemas/Elephant'
DicerosBicornis: '#/components/schemas/Rhinoceros'
SyncerusCaffer: '#/components/schemas/CapeBuffalo'
PantheraLeo: "#/components/schemas/Lion"
PantheraPardus: "#/components/schemas/Leopard"
BigFive:
anyOf:
- $ref: "#/components/schemas/Elephant"
- $ref: "#/components/schemas/Rhinoceros"
- $ref: "#/components/schemas/CapeBuffalo"
- $ref: "#/components/schemas/Lion"
- $ref: "#/components/schemas/Leopard"
Elephant:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
species:
enum:
- LoxodontaAfricana
- LoxodontaCyclotis
Rhinoceros:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
species:
enum:
- DicerosBicornis
CapeBuffalo:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
species:
enum:
- SyncerusCaffer
subspecies:
enum:
- SyncerusCafferCaffer
- SyncerusCafferNanus
- SyncerusCafferBrachyceros
- SyncerusCafferAequinoctialis
- SyncerusCafferMathewsi
- null
Lion:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
species:
enum:
- PantheraLeo
subspecies:
enum:
- PantheraLeoLeo
- PantheraLeoMelanochaita
- null
Leopard:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
species:
enum:
- PantheraPardus
subspecies:
enum:
- PantheraPardusPardus
- null
The zoo.yaml as json (converted with the swagger-editor):
{
"openapi": "3.0.0",
"info": {
"title": "Zootopia",
"description": "The zoo is a place where animals are held. Hopefully under good circumstances.",
"version": "1.0.0"
},
"paths": {
"/api/v1/zoo/{name}": {
"get": {
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Zoo"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Zoo": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"maybeNickname": {
"type": "string",
"nullable": true
},
"parks": {
"properties": {
"maybeSafari": {
"$ref": "#/components/schemas/SafariPark"
}
}
}
}
},
"SafariPark": {
"type": "object",
"nullable": true,
"required": [
"name",
"animals"
],
"properties": {
"name": {
"type": "string"
},
"animals": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/components/schemas/BigFive"
}
]
}
}
}
},
"Animal": {
"type": "object",
"required": [
"species"
],
"properties": {
"species": {
"type": "string"
},
"subspecies": {
"type": "string",
"nullable": true
}
},
"discriminator": {
"propertyName": "species",
"mapping": {
"LoxodontaAfricana": "#/components/schemas/Elephant",
"LoxodontaCyclotis": "#/components/schemas/Elephant",
"DicerosBicornis": "#/components/schemas/Rhinoceros",
"SyncerusCaffer": "#/components/schemas/CapeBuffalo",
"PantheraLeo": "#/components/schemas/Lion",
"PantheraPardus": "#/components/schemas/Leopard"
}
}
},
"BigFive": {
"anyOf": [
{
"$ref": "#/components/schemas/Elephant"
},
{
"$ref": "#/components/schemas/Rhinoceros"
},
{
"$ref": "#/components/schemas/CapeBuffalo"
},
{
"$ref": "#/components/schemas/Lion"
},
{
"$ref": "#/components/schemas/Leopard"
}
]
},
"Elephant": {
"allOf": [
{
"$ref": "#/components/schemas/Animal"
},
{
"type": "object"
}
],
"properties": {
"species": {
"enum": [
"LoxodontaAfricana",
"LoxodontaCyclotis"
]
}
}
},
"Rhinoceros": {
"allOf": [
{
"$ref": "#/components/schemas/Animal"
},
{
"type": "object"
}
],
"properties": {
"species": {
"enum": [
"DicerosBicornis"
]
}
}
},
"CapeBuffalo": {
"allOf": [
{
"$ref": "#/components/schemas/Animal"
},
{
"type": "object"
}
],
"properties": {
"species": {
"enum": [
"SyncerusCaffer"
]
},
"subspecies": {
"enum": [
"SyncerusCafferCaffer",
"SyncerusCafferNanus",
"SyncerusCafferBrachyceros",
"SyncerusCafferAequinoctialis",
"SyncerusCafferMathewsi",
null
]
}
}
},
"Lion": {
"allOf": [
{
"$ref": "#/components/schemas/Animal"
},
{
"type": "object"
}
],
"properties": {
"species": {
"enum": [
"PantheraLeo"
]
},
"subspecies": {
"enum": [
"PantheraLeoLeo",
"PantheraLeoMelanochaita",
null
]
}
}
},
"Leopard": {
"allOf": [
{
"$ref": "#/components/schemas/Animal"
},
{
"type": "object"
}
],
"properties": {
"species": {
"enum": [
"PantheraPardus"
]
},
"subspecies": {
"enum": [
"PantheraPardusPardus",
null
]
}
}
}
}
}
}