Closed ghillairet closed 5 years ago
@tedepstein Ready to review, see PR https://github.com/ModelSolv/RepreZen/pull/1876 for build
Example validator now normalizes schemas in Swagger/OpenAPI specs to resolve any references before running the validation.
Needs to be modified to deal with recursive (circular) references. These cannot be inlined, they must be localized. See the Normalizer documentation and codebase for a full explanation.
We also need the preference setting to be enabled by default.
Final changes to this PR are about adding extensions points to KZOE, so that clients (namely API Studio) can provide their own validation processors.
The 2 extensions points are:
ValidationProvider
PreferenceProvider
ValidationProviders
are called inside the Validation
class on each nodes. The clients that provide an implementation for it can then execute custom validation on each node in the document.
PreferenceProvider
can be used to provide custom preferences. Those preferences will be added in Swagger or OpenAPI preference pages. The extension point provides an attribute preferencePage
that will be used to identify which preference page should be extended.
Should be extended to validate example
property on a Schema Object.
The feature generally works, and we can merge the PR. But it has at least one unrecoverable error, meaning there are valid OpenAPI documents that cannot be used because they get a false-negative (error) result from the validator.
There are other issues where the validation yields false-positives in some cases (i.e. validation doesn't check what it should be checking) and false-negatives in other cases (raising errors where the schema is valid).
Pending further testing, we can merge the PR. (Further comments to follow here...)
Tests were created using examples within the following template, by adding examples to the request and response of the post
operation in the /pets
path item:
openapi: "3.0.0"
info:
version: 1.0.0
title: Expanded Petstore example.
paths:
/pets:
post:
requestBody:
content:
application/json:
example:
name: Frisbee
tag: dog
schema:
$ref: '#/components/schemas/NewPet'
responses:
200:
description: pet response
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
components:
schemas:
Pet:
allOf:
- $ref: '#/components/schemas/NewPet'
- type: object
required:
- id
properties:
id:
type: integer
format: int64
NewPet:
type: object
required:
- name
properties:
name:
type: string
tag:
type: string
Each test case will describe modifications that were made to this template to get the reported results.
Changing the example to break the rules yielded expected schema validation errors.
requestBody:
content:
application/json:
example:
name: 1234 # should be string, not number
tag:
- arrays are
- not allowed.
schema:
$ref: '#/components/schemas/NewPet'
Places a single error marker on the example line:
Multiple markers at this line
- instance type (array) does not match any allowed primitive type (allowed: ["string"])
- instance type (integer) does not match any allowed primitive type (allowed: ["string"])
- 5 added lines
Changing to multiple examples yields the same error message on the second example (array element), as expected:
requestBody:
content:
application/json:
examples:
validExample:
value:
name: Snoopy
tag: Peanuts
notValidExample:
value:
name: 1234 # should be string, not number
tag:
- arrays are
- not allowed.
schema:
$ref: '#/components/schemas/NewPet'
Works as expected with the schema moved inline:
requestBody:
content:
application/json:
example:
id: 465
name: 1234 #should be string, not number
tag:
- arrays are
- not allowed.
schema:
type: object
required:
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Note: In the above example, we added the id
property with an int64 format. The validation error now includes an additional warning:
Multiple markers at this line
- instance type (integer) does not match any allowed primitive type (allowed: ["string"])
- instance type (array) does not match any allowed primitive type (allowed: ["string"])
- format attribute "int64" not supported
If the errors are corrected (first two items), the error marker changes to a warning marker, and showing only the 'format attribute "int64" not supported' warning message. This is not a critical problem, but it would be best to filter those warnings out, when they occur on standard OpenAPI formats.
In the OpenAPI test template, the Pet
schema is composed using allOf
. Referencing this schema or copying it inline doesn't work. Schema validation appears not to take place at all.
requestBody:
content:
application/json:
example:
id: BadValue
name: 1324 #should be string, not number
tag: Dog
schema:
allOf:
- $ref: '#/components/schemas/NewPet'
- type: object
required:
- id
properties:
id:
type: integer
format: int64
The Eclipse error log shows entries like this:
com.github.fge.jsonschema.core.exceptions.ProcessingException: fatal: JSON Reference "#/components/schemas/NewPet" cannot be resolved
level: "fatal"
schema: {"loadingURI":"#","pointer":"/allOf/0"}
ref: "#/components/schemas/NewPet"
at com.github.fge.jsonschema.core.load.RefResolver.rawProcess(RefResolver.java:121)
at com.github.fge.jsonschema.core.load.RefResolver.rawProcess(RefResolver.java:51)
at com.github.fge.jsonschema.core.processing.RawProcessor.process(RawProcessor.java:77)
at com.github.fge.jsonschema.core.processing.RawProcessor.process(RawProcessor.java:41)
at com.github.fge.jsonschema.core.processing.ProcessorChain$ProcessorMerger.process(ProcessorChain.java:189)
at com.github.fge.jsonschema.core.processing.ProcessingResult.of(ProcessingResult.java:79)
at com.github.fge.jsonschema.core.processing.CachingProcessor$1.load(CachingProcessor.java:128)
at com.github.fge.jsonschema.core.processing.CachingProcessor$1.load(CachingProcessor.java:120)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3716)
at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2424)
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2298)
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2211)
at com.google.common.cache.LocalCache.get(LocalCache.java:4154)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:4158)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:5147)
at com.github.fge.jsonschema.core.processing.CachingProcessor.process(CachingProcessor.java:109)
at com.github.fge.jsonschema.processors.validation.ValidationChain.process(ValidationChain.java:107)
at com.github.fge.jsonschema.processors.validation.ValidationChain.process(ValidationChain.java:57)
at com.github.fge.jsonschema.core.processing.ProcessorMap$Mapper.process(ProcessorMap.java:166)
at com.github.fge.jsonschema.core.processing.ProcessingResult.of(ProcessingResult.java:79)
at com.github.fge.jsonschema.core.processing.CachingProcessor$1.load(CachingProcessor.java:128)
at com.github.fge.jsonschema.core.processing.CachingProcessor$1.load(CachingProcessor.java:120)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3716)
at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2424)
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2298)
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2211)
at com.google.common.cache.LocalCache.get(LocalCache.java:4154)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:4158)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:5147)
at com.github.fge.jsonschema.core.processing.CachingProcessor.process(CachingProcessor.java:109)
at com.github.fge.jsonschema.processors.validation.InstanceValidator.process(InstanceValidator.java:110)
at com.github.fge.jsonschema.processors.validation.InstanceValidator.process(InstanceValidator.java:59)
at com.github.fge.jsonschema.keyword.validator.draftv4.AllOfValidator.validate(AllOfValidator.java:68)
at com.github.fge.jsonschema.processors.validation.InstanceValidator.process(InstanceValidator.java:129)
at com.github.fge.jsonschema.processors.validation.ValidationProcessor.process(ValidationProcessor.java:56)
at com.github.fge.jsonschema.processors.validation.ValidationProcessor.process(ValidationProcessor.java:34)
at com.github.fge.jsonschema.core.processing.ProcessingResult.of(ProcessingResult.java:79)
at com.github.fge.jsonschema.main.JsonSchema.doValidate(JsonSchema.java:76)
at com.github.fge.jsonschema.main.JsonSchema.validate(JsonSchema.java:109)
at com.reprezen.swagedit.core.validation.JsonSchemaValidator.doValidate(JsonSchemaValidator.java:99)
at com.reprezen.swagedit.core.validation.JsonSchemaValidator.validate(JsonSchemaValidator.java:81)
at com.modelsolv.reprezen.ui.editors.validation.ExampleValidationProvider.doValidate(ExampleValidationProvider.java:147)
at com.modelsolv.reprezen.ui.editors.validation.ExampleValidationProvider.validate(ExampleValidationProvider.java:77)
at com.reprezen.swagedit.core.validation.Validator.lambda$0(Validator.java:133)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at com.reprezen.swagedit.core.validation.Validator.validateDocument(Validator.java:131)
at com.reprezen.swagedit.core.validation.Validator.validate(Validator.java:107)
at com.reprezen.swagedit.openapi3.validation.OpenApi3Validator.validate(OpenApi3Validator.java:99)
at com.reprezen.swagedit.core.validation.Validator.validate(Validator.java:89)
at com.reprezen.swagedit.core.editor.ValidationOperation.validateSwagger(ValidationOperation.java:109)
at com.reprezen.swagedit.core.editor.ValidationOperation.run(ValidationOperation.java:90)
at com.reprezen.swagedit.core.editor.JsonEditor$6.doRunInWorkspace(JsonEditor.java:429)
at com.reprezen.swagedit.core.editor.JsonEditor$SafeWorkspaceJob.runInWorkspace(JsonEditor.java:503)
at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:39)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:56)
Adding a reusable Example Object here:
components:
examples:
NewPetExample:
value:
id: foo # should be number
name: 1324 # should be string
tag:
- should
- be
- string
And referencing it here:
examples:
foo:
$ref: "#/components/examples/NewPetExample"
Puts an error marker on referencedExample
:
object has missing required properties (["name"])
It seems the validation code does not try to resolve the $ref
to the example object.
This raises validation errors as expected:
requestBody:
content:
application/json:
examples:
referencedExample:
value:
- id: 1234
name: zing
- name: fred
tag: 1234
schema:
type: array
items:
type: object
required:
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Referenced array schema also works, with this NewPets
reusable schema :
components:
schemas:
NewPets:
type: array
items:
$ref: "#/components/schemas/NewPet"
NewPet:
type: object
required:
- name
properties:
name:
type: string
tag:
type: string
... and this reference to NewPets
:
requestBody:
content:
application/json:
examples:
referencedExample:
value:
- id: 1234
name: zing
- name: fred
tag: 1234
schema:
$ref: "#/components/schemas/NewPets"
We see the expected validation errors.
This works:
requestBody:
content:
application/json:
examples:
example1:
value:
- The quick
- Brown
- Fox
schema:
type: array
items:
type: string
maxLength: 5
This works:
requestBody:
content:
application/json:
examples:
example1:
value: Fighters
schema:
type: string
maxLength: 5
example
property: FailThis was known from our walkthrough. Schemas can contain an example
keyword, but this doesn't trigger validation in the current implementation. it would be helpful to have this working.
NewPets:
type: array
items:
$ref: "#/components/schemas/NewPet"
example:
- id: 234
name: Fred
tag: 982734
This works, raising expected validation errors:
requestBody:
content:
application/json:
examples:
example1:
value:
orderID: ASDFASDF
orderDate: "2018-12-01"
pet:
foo: bar
name: Simone
tag: 67
schema:
type: object
title: Pet Order
properties:
orderID:
type: string
maxLength: 5
orderDate:
type: string
format: date
pet:
$ref: "#/components/schemas/NewPet"
required:
- orderID
It works the same if I extract the Pet Order schema to schema/components/PetOrder
and reference it there.
In this schema, the readOnly
keyword is part of the OpenAPI spec, but not part of JSON Schema proper.
content:
application/json:
examples:
example1:
value:
orderID: ASDF
orderDate: "2018-12-01"
pet:
foo: bar
name: Simone
tag: Cockapoo
schema:
type: object
title: Pet Order
properties:
orderID:
readOnly: true
type: string
maxLength: 5
orderDate:
type: string
format: date
pet:
$ref: "#/components/schemas/NewPet"
required:
- orderID
The readOnly
property is tolerated, but shows a warning.
Multiple markers at this line - format attribute "date" not supported - the following keywords are unknown and will be
ignored: [readOnly]
It would be good to filter out this warning on keywords that are defined in the OpenAPI spec.
This works as expected:
requestBody:
content:
application/json:
examples:
example1:
value: {
'orderID': 'ASDF',
'orderDate': "2018-12-01",
'pet': {
'foo': 'bar',
'name': 'Simone',
'tag': 'Cockapoo'
}
}
schema:
type: object
title: Pet Order
properties:
orderID:
readOnly: true
type: string
maxLength: 5
orderDate:
type: string
format: date
pet:
$ref: "#/components/schemas/NewPet"
required:
- orderID
This may be a problem:
requestBody:
content:
application/json:
examples:
example1:
value: |
{
'orderID': 'ASDF',
'orderDate': "2018-12-01",
'pet': {
'name': 'Simone',
'tag': 'Cockapoo'
}
}
schema:
type: object
title: Pet Order
properties:
orderID:
type: string
maxLength: 5
orderDate:
type: string
pet:
$ref: "#/components/schemas/NewPet"
required:
- orderID
The example1:
line is marked with the following error:
instance type (string) does not match any allowed primitive type (allowed: ["object"])
The error is the same regardless of the media type. Changing the media type to "application/xml", "text/csv", or other values doesn't change the result. If the string is valid XML, validation is bypassed. But any non-JSON, non-XML string causes the error.
There are a couple of questions here:
schema
is provided, but the example content needs to be a string, not embedded JSON or YAML? There are two scenarios where this is required:
I think we could accommodate these cases with more robust media type logic, and with some new preference settings.
We could add a checkbox like this:
Note that the current "Enable examples validation" checkbox has been renamed for clarity.
I think example validation should be dependent on media types, with three categories of media types:
Here's the logic I would propose:
schema
is missing, null, empty string, blank string (whitespace), empty object, or true
, don't attempt to validate.\/xml
(matches application/xml
or any 6 other media types containing "/xml")\+xml$
(matches 804 known media types ending in "+xml")\/json
(matches application/json
and others)\+json$
(matches any media type ending in +json
, like application/vnd.hal+json
)allOf
schemas don't seem to work at all. format
values that are not part of standard JSON Schema. Since they are only warnings, they are not a critical issue.readOnly
and other OpenAPI schema keywords that are not part of the JSON Schema standard. We should filter these out if possible.@tedepstein See changes in https://github.com/ModelSolv/RepreZen/pull/1876
See update https://github.com/RepreZen/KaiZen-OpenAPI-Editor/pull/486#issuecomment-479873166
Add preliminary support for example validation in OpenAPI3 and Swagger.