openapi4j / openapi4j Goto Github PK
View Code? Open in Web Editor NEWOpenAPI 3 parser, JSON schema and request validator.
Home Page: https://www.openapi4j.org/
License: Apache License 2.0
OpenAPI 3 parser, JSON schema and request validator.
Home Page: https://www.openapi4j.org/
License: Apache License 2.0
Describe the bug
AbsOpenApiSchema.copyMap()
does not honor the source map type.
The properties of an object
schema originally are stored in a LinkedHashMap
to preserve the order of the properties. This is lost by copyMap()
.
It happens when I try to resolve a ref schema like this:
def resolved = schema.copy (api.context, true)
Some convertions are not checked leading to NPE or IAE.
The validation still fails but not properly.
Describe the bug
When using a JSON ref, it is not possible to get back the additional properties JSON in the schema
To Reproduce
import com.fasterxml.jackson.databind.JsonNode;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Schema;
import java.nio.file.Files;
import java.nio.file.Path;
public class AdditionalPropertiesBug {
public static void main(String[] args) throws Exception {
String openApi =
"components:\n" +
" schemas:\n" +
" Messages:\n" +
" type: object\n" +
" additionalProperties:\n" +
" $ref: '#/components/schemas/Message'\n" +
" Message:\n" +
" type: object\n" +
" properties:\n" +
" code:\n" +
" type: integer\n" +
" text:\n" +
" type: string";
Path enumBug = Files.createTempFile("", "");
Files.write(enumBug, openApi.getBytes());
OpenApi3 api = new OpenApi3Parser().parse(enumBug.toFile(), false);
Schema schema = api.getComponents().getSchema("Messages");
JsonNode jsonNode = schema.toNode(api.getContext(), true);
System.out.println(jsonNode.toPrettyString());
}
}
The output:
{
"type" : "object",
"additionalProperties" : false
}
Expected behavior
The additionalProperties
field should be the same as the source document
Additional context
I am attempting to use openapi4j to validate JSON examples, from the OpenAPI document and at runtime using https://github.com/networknt/json-schema-validator, any enumerated integer value currently fails
The toolset can be easily compliant with AOT compilation.
Todo:
Motivations:
This would allow to get a net result of the data with the current schema path in ValidationResults
object.
This includes a minor breaking change in ValidationItem
:
crumbs()
method is renamed to schemaCrumbs()
dataCrumbs()
method is added to get the path of data.Add test to not apply additional validators (i.e. additionalProperties & nullable) when schema is JSON-Reference.
Describe the bug
Validating a Map<String,Object> implicitly converts the map to a JsonNode using a schema. However this causes values to be coerced to the correct type. This can mean numbers get converted to strings and validate as strings. It can also cause strings to be parsed as numbers, fail parsing, and be left as 'null'. This causes the validation errors to be incorrect or missing.
The use of a schema for conversion is also problematic as it does not cater for discriminators which can cause values to be converted to different types depending on context.
To Reproduce
spec:
{
"properties": {
"name": {
"type": "string"
}
}
}
request body:
{
name: 1234
}
Validate the body using a Map<String,Object> representation of the request.
Expected behavior
Expect that Java String objects get converted to JsonNode strings, irrespective of the schema. Likewise for Integer etc objects.
This can be worked around by manually converting to JsonNode:
ObjectMapper OBJECT_MAPPER = new ObjectMapper();
JsonNode jsonNodeMap = OBJECT_MAPPER.convertValue(objMap, JsonNode.class);
builder.body(Body.from(jsonNodeMap));
If this is too difficult to solve it may be best to deprecate the Map<String,Object> interface, as it seems quite incorrect.
Is your feature request related to a problem? Please describe.
I am preprocessing my OpenAPI document, by the time I come to parse it with OpenApi3Parser
I already have a JsonNode
. The current parser requires a file, which means I need to serilaise and write it to disk in order to get a OpenApi3
Describe the solution you'd like
Either add a method to OpenApi3Parser
that takes a JsonNode
or provide some other utility to do it
Like Vert.x vertx-web-api-contract router factory, add router factory util.
In case of path parameter setup on server URL, searching for path parameter will fail.
A relative path to an individual endpoint. The field name MUST begin with a slash. The path is appended (no relative URL resolution) to the expanded URL from the Server Object's url field in order to construct the full URL.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#paths-object
This should be fixed before feature #59
Describe the bug
A validation error is issued by SchemaValidator
when a nullable number property with a format (e.g. double
) is null.
To Reproduce
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.openapi4j.core.model.v3.OAI3;
import org.openapi4j.core.validation.ValidationResults;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Schema;
import java.nio.file.Files;
import java.nio.file.Path;
import org.openapi4j.schema.validator.ValidationContext;
import org.openapi4j.schema.validator.v3.SchemaValidator;
public class NullableFormatBug {
public static void main(String[] args) throws Exception {
String openApi =
"paths:\n"
+ " /example:\n"
+ " post:\n"
+ " requestBody:\n"
+ " content:\n"
+ " 'application/json':\n"
+ " schema:\n"
+ " $ref: '#/components/schemas/NullableFormat'\n"
+ " responses:\n"
+ " '200':\n"
+ " description: Updated\n"
+ "components:\n"
+ " schemas:\n"
+ " NullableFormat:\n"
+ " type: array\n"
+ " items:\n"
+ " type: object\n"
+ " additionalProperties: false\n"
+ " properties:\n"
+ " foo:\n"
+ " type: number\n"
+ " format: double\n"
+ " nullable: true\n";
Path nullableFormatBug = Files.createTempFile("NullableFormatBug", "");
Files.write(nullableFormatBug, openApi.getBytes());
OpenApi3 api = new OpenApi3Parser().parse(nullableFormatBug.toFile(), false);
Schema schema = api.getPath("/example").getOperation("post")
.getRequestBody().getContentMediaType("application/json").getSchema();
JsonNode jsonNode = TreeUtil.toJsonNode(schema);
ValidationContext<OAI3> validationContext = new ValidationContext<>(api.getContext());
SchemaValidator schemaValidator = new SchemaValidator(validationContext, "", jsonNode);
String testData1 =
"["
+ "{\n"
+ " \"foo\": null\n"
+ "}"
+ "]";
ObjectMapper mapper = new ObjectMapper();
ValidationResults results = new ValidationResults();
schemaValidator.validate(mapper.readTree(testData1), results);
if (!results.isValid()) {
System.out.println(results);
}
}
}
The following error is displayed when the above code is run:
Validation error(s) :
.#/components/schemas/NullableFormat.items.foo : Value 'null' does not match format 'double'.
Expected behavior
Expected no validation errors from the above code.
Is your feature request related to a problem? Please describe.
This is a nice-to-have idea that I have not seen any other validators do, yet I imagine would be quite useful.
The OpenApi schema is useful for describing an API, but less useful for describing a given instance of data due to the use of 'discriminators'. Discriminators make it impossible to traverse the schema and map it directly to the data.
It would be great to have a way to calculate a resolved schema for a given data object, stripped of all discriminators. Thus you could iterate through the resolved schema to navigate your data without having to re-do and re-invent the discrimination code.
I believe this would be a very useful set of functionality. The particular use I have for it is to make it easier to iterate the data to convert 'strings marked as dates' into actual Java Dates. I also need to identify integers that need to be converted to booleans etc for marshalling from another system.
Describe the solution you'd like
Evaluating 'validate' would also return the resolved schema, with 'anyOf', 'oneOf' etc discriminators stripped from the schema and replaced with the calculated properties. The 'resolved schema' may not be a true OpenApi schema, but something similar.
The net result should allow you to iterate the new 'resolved schema' against the data without having to perform any validation/discrimination yourself.
Describe alternatives you've considered
I am currently using a 'format' custom validator to store 'INFO' validation results. I then iterate these validation results and use the 'crumbs' of the validation messages to determine where in the data the date/date-times exist.
This is perhaps a long-term feature that you may not be interested in supporting immediately. However I do think it is a useful bit of functionality that is above what other projects are offering.
Describe the bug
Relative URLs are not allowed in the authorizationUrl
field of the OAuth Flow Object.
Expected behavior
The specification indicates that
Relative References in URLs
Unless specified otherwise, all properties that are URLs MAY be relative references as defined by RFC3986. Relative references are resolved using the URLs defined in the Server Object as a Base URI.
The authorization URL to be used for this flow. This MUST be in the form of a URL.
As there is nothing special about this URL, I guess this field (and others) should be allowed to use relative paths.
What do you think about it ?
For now, ShemaValidator lists all the errors from the given content.
We could add a fast exit when first error has been encountered.
I am trying to validate an input to a given schema but I would like the validation to fail if there are properties in the input that are not in the schema. Is this possible and if not are you planning to implement it?
'multipart/*' content type should be reviewed for better accuracy on input conversion.
Enable validation context as input for schema validator overrides and options
When using the allOf
pattern with discriminators and referencing only the "superclass" (so to speak) from the operation (see example below,) it would be useful to validate not only against the superclass schema, but also the subclass schema.
I.e. if validating an instance against a schema that has a discriminator
field, look for a schema in #/components/schemas
that is defined with an allOf
that contains a $ref
to the original schema, and whose name matches the discriminator value in the instance, and validate against that schema instead.
I'm not sure if this is on the edge of the validator's responsibility or the OpenAPI specification, but it seems reasonable enough. The last paragraph of the description at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#discriminatorObject is a little vague though.
Thoughts?
paths:
/discriminator:
post:
operationId: discriminator
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
components:
schemas:
Pet:
type: object
required:
- pet_type
properties:
pet_type:
type: string
discriminator:
propertyName: pet_type
Dog: # "Dog" is a value for the pet_type property (the discriminator value)
allOf: # Combines the main `Pet` schema with `Dog`-specific properties
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Dog`
properties:
bark:
type: boolean
breed:
type: string
enum: [Dingo, Husky, Retriever, Shepherd]
Cat: # "Cat" is a value for the pet_type property (the discriminator value)
allOf: # Combines the main `Pet` schema with `Cat`-specific properties
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Cat`
properties:
hunts:
type: boolean
age:
type: integer
Describe the bug
Question (not a bug): Is it possible to chain custom validators to the original core validator?
In particular I have written a new "format" validator to trap 'date-time' and 'date' formats and record them (for later data conversion). I would like to then delegate the validation to the original 'FormatValidator' behaviour for validation against other well-known formats.
To Reproduce
Steps to reproduce the behavior:
For now, parameters are only checked from schema with associated style/explode.
Content type should be considered before 1.0.
According to the spec, both PathItem and SecurityScheme should allow references.
PathItem: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathItemObject
SecurityScheme: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject
This will ease eventual lookup for further processing on validation results as for now, only severity and message are filled.
Is your feature request related to a problem? Please describe.
I would like to validate a request/response by path rather than a known operation id. To validate requests as they arrive to an end-point the only information we have is the path, headers and method. We don't know what the "operation" is. We can do this with code similar to:
Path path = api.getPath(request.getPath());
Operation op = path.getOperation(request.getMethod());
OperationValidator operationValidator = new OperationValidator(ctx, api, path, op);
ValidationResults vr = new ValidationResults();
operationValidator.validateBody(request, vr);
However this only works for paths with no parameters. I would like to find paths such as "/pet/{petId}".
Describe the solution you'd like
I believe the "api.getPath" could take an overload which converts all paths with "{xxx}" to regexes similar to: "/pet/.*/" and find a match using this instead.
It would be nice if it also took the 'servers:url' component into account as well. Perhaps an api.findPath(url) that correctly finds the path by skipping over the server part and regexing through the parameters parts.
Describe alternatives you've considered
I can write my own iteration over the map of paths but it would be nice to have this part of the API. I suspect many people would like to validate using the path+method/response code rather than a known operation id.
Describe the bug
Hi,
url field in Server Object should allow a relative url. But it seems like not supported yet.
from the spec:
A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in {brackets}.
Describe the bug
Given two query parameters a
and b
, both not marked as required
.
If a request has no query
string, it will correctly pass the request validation.
However, a request that has a value for a
but not b
will fail validation with the message
b.nullable : Null value is not allowed.
To Reproduce
Steps to reproduce the behavior:
null
- it will passa=1
(where a
is one of the parameters defined in step 1) - it won't passExpected behavior
Given two non-required query parameters, I'd expect a request to pass without specifying a value for all parameters.
Additional context
The issue seems to be caused by ParameterValidator#checkRequired
. When the query is null
, the paramValues
will be an empty map.
However, when the query is ""
, the paramValues
is populated with null
-values. Similarly, when the query is a=1
, the map will contain the values {a: 1, b: null}
This causes the function to return true
, thus marking the null-ish parameter for further validation.
Later on, in the NullishValidator
, it's correctly recognized that the query parameter is set to null
One solution could be to treat null
values in paramValues
as unset.
Reproducer
required-params.zip
Related 2b26e83
For now multipart only supports primitive values in parts.
We should handle multipart/mixed also.
Related to 50a5d05
Describe the bug
When using a root-level security
block, the specification.yaml
file can't be parsed correctly and fails with a
Exception in thread "main" org.openapi4j.core.exception.ResolutionException: Failed to load spec at 'file:/.../minimal.yaml'
at org.openapi4j.parser.OpenApi3Parser.parse(OpenApi3Parser.java:38)
at org.openapi4j.parser.OpenApi3Parser.parse(OpenApi3Parser.java:19)
at org.openapi4j.parser.OpenApiParser.parse(OpenApiParser.java:53)
at org.openapi4j.parser.OpenApiParser.parse(OpenApiParser.java:37)
at Reproducer.main(Reproducer.java:10)
Caused by: org.openapi4j.core.exception.DecodeException: Failed to decode : Cannot deserialize instance of `org.openapi4j.parser.model.v3.SecurityRequirement` out of START_ARRAY token
at [Source: (StringReader); line: 15, column: 3] (through reference chain: org.openapi4j.parser.model.v3.OpenApi3["security"])
at org.openapi4j.core.util.TreeUtil.load(TreeUtil.java:123)
at org.openapi4j.parser.OpenApi3Parser.parse(OpenApi3Parser.java:35)
... 4 more
I believe the issue can be resolved by converting the security
field in OpenApi3.java
to a List<SecurityRequirement>
, the same way it's defined in Operation.java
.
To Reproduce
Steps to reproduce the behavior:
security
block (see reproducer) and at least one security schemeExpected behavior
I expect the root-level security block to behave the same as the operation-level ones and be parsed without problems.
Additional context
Version used: 0.5
Relevant OpenAPI doc: https://swagger.io/docs/specification/authentication/#security
Reproducer
reproducer.zip
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:
$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
]
}
}
}
}
}
}
Operation validator loses somes external $ref when building Schema Object validators. (in BodyValidator & ParameterValidator).
Is your feature request related to a problem? Please describe.
When parsing the API we do the following to get a full document with all references inlined:
new OpenApi3Parser().parse(tempFile.toFile, false)
api.copy(api.getContext, true)
The API definition contains multiple schemas with discriminator properties like the one below:
components:
responses:
sampleObjectResponse:
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Object1'
- $ref: '#/components/schemas/Object2'
- $ref: 'sysObject.json#/sysObject'
discriminator:
propertyName: objectType
mapping:
obj1: '#/components/schemas/Object1'
obj2: '#/components/schemas/Object2'
system: 'sysObject.json#/sysObject'
…
schemas:
Object1:
type: object
required:
- objectType
properties:
objectType:
type: string
…
Object2:
type: object
required:
- objectType
properties:
objectType:
type: string
…
The problem with flattening the above is that it no longer complies with the OpenAPI specification because discriminator properties must be used in conjunction with a Schema ref and cannot be used with an inline schema.
Validating the above schema with a valid payload fails with a NullPointerException
because the DiscriminatorValidator
is looking for a schema ref.
Describe the solution you'd like
Do not inline refs if they are used as discriminator properties
Describe alternatives you've considered
At the moment we are working around it by not following refs at the top level and instead selectively following refs on the operation level using Operation::copy
.
Describe the bug
Composing schemas using allOf
with one of the schemas containing the discriminator property causes validation errors.
To Reproduce
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.file.Files;
import java.nio.file.Path;
import org.openapi4j.core.model.v3.OAI3;
import org.openapi4j.core.validation.ValidationResults;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Schema;
import org.openapi4j.parser.validation.v3.OpenApi3Validator;
import org.openapi4j.schema.validator.ValidationContext;
import org.openapi4j.schema.validator.v3.SchemaValidator;
public class SchemaCompositionBug {
public static void main(String[] args) throws Exception {
String openApi =
"paths:\n"
+ " /pets:\n"
+ " patch:\n"
+ " requestBody:\n"
+ " content:\n"
+ " application/json:\n"
+ " schema:\n"
+ " oneOf:\n"
+ " - $ref: '#/components/schemas/Cat'\n"
+ " - $ref: '#/components/schemas/Dog'\n"
+ " discriminator:\n"
+ " propertyName: pet_type\n"
+ " responses:\n"
+ " '200':\n"
+ " description: Updated\n"
+ "components:\n"
+ " schemas:\n"
+ " Pet:\n"
+ " type: object\n"
+ " required:\n"
+ " - pet_type\n"
+ " properties:\n"
+ " pet_type:\n"
+ " type: string\n"
+ " discriminator:\n"
+ " propertyName: pet_type\n"
+ " Dog: # \"Dog\" is a value for the pet_type property (the discriminator value)\n"
+ " allOf: # Combines the main `Pet` schema with `Dog`-specific properties \n"
+ " - $ref: '#/components/schemas/Pet'\n"
+ " - type: object\n"
+ " # all other properties specific to a `Dog`\n"
+ " properties:\n"
+ " bark:\n"
+ " type: boolean\n"
+ " breed:\n"
+ " type: string\n"
+ " enum: [Dingo, Husky, Retriever, Shepherd]\n"
+ " Cat: # \"Cat\" is a value for the pet_type property (the discriminator value)\n"
+ " allOf: # Combines the main `Pet` schema with `Cat`-specific properties \n"
+ " - $ref: '#/components/schemas/Pet'\n"
+ " - type: object\n"
+ " # all other properties specific to a `Cat`\n"
+ " properties:\n"
+ " hunts:\n"
+ " type: boolean\n"
+ " age:\n"
+ " type: integer";
Path bug = Files.createTempFile("SchemaCompositionBug", "");
Files.write(bug, openApi.getBytes());
OpenApi3 api = new OpenApi3Parser().parse(bug.toFile(), false);
OpenApi3Validator.instance().validate(api);
}
}
Expected behavior
Expected the above to validate with no errors.
Additional context
The Open API definition above is taken from a Swagger example: https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/#allof
We should handle correctly media types with the following sample patterns:
text/plain; charset=whatever
image/*
, video/*
, audio/*
I have multiple openapi files in my resources folder that reference each other. Attempting to parse them works just fine when running the code with my IDE, but as soon as I jar the project and run the jar, openapi4j is unable to process any refs. I am loading the swagger file like this:
URL url = Thread.currentThread().getContextClassLoader().getResource(filePath);
openAPI = new OpenApi3Parser().parse(url, false);
and my swagger file looks like this:
{
"openapi" : "3.0.0",
"info" : {
"title" : "My Service",
"version" : "1.0.0"
},
"paths" : {
"/endpoint" : { "$ref" : "swagger2.json#/paths/get_endpoint" },
}
}
To Reproduce
Expected behavior
The refs should evaluate, even though they are in a jar
Describe the bug
Non-required schema properties that are null
produce a validation error: NullableFormat.items.foo.nullable : Null value is not allowed.
To Reproduce
Steps to reproduce the behavior:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.openapi4j.core.model.v3.OAI3;
import org.openapi4j.core.util.TreeUtil;
import org.openapi4j.core.validation.ValidationResults;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Schema;
import java.nio.file.Files;
import java.nio.file.Path;
import org.openapi4j.schema.validator.ValidationContext;
import org.openapi4j.schema.validator.v3.SchemaValidator;
public class NullableFormatBug {
public static void main(String[] args) throws Exception {
String openApi =
"paths:\n"
+ " /example:\n"
+ " post:\n"
+ " requestBody:\n"
+ " content:\n"
+ " 'application/json':\n"
+ " schema:\n"
+ " $ref: '#/components/schemas/NullableFormat'\n"
+ " responses:\n"
+ " '200':\n"
+ " description: Updated\n"
+ "components:\n"
+ " schemas:\n"
+ " NullableFormat:\n"
+ " type: array\n"
+ " items:\n"
+ " type: object\n"
+ " additionalProperties: false\n"
+ " properties:\n"
+ " foo:\n"
+ " type: number\n"
+ " format: double\n";
Path nullableFormatBug = Files.createTempFile("NullableFormatBug", "");
Files.write(nullableFormatBug, openApi.getBytes());
OpenApi3 api = new OpenApi3Parser().parse(nullableFormatBug.toFile(), false);
Schema schema = api.getPath("/example").getOperation("post")
.getRequestBody().getContentMediaType("application/json").getSchema();
JsonNode jsonNode = schema.toNode(api.getContext(), true);
ValidationContext<OAI3> validationContext = new ValidationContext<>(api.getContext());
SchemaValidator schemaValidator = new SchemaValidator(validationContext, "", jsonNode);
String testData1 =
"["
+ "{\n"
+ " \"foo\": null\n"
+ "}"
+ "]";
ObjectMapper mapper = new ObjectMapper();
ValidationResults results = new ValidationResults();
schemaValidator.validate(mapper.readTree(testData1), results);
if (!results.isValid()) {
System.out.println(results);
}
}
}
Expected behavior
Expected no schema validation errors when validating a non-required property that is null
.
Additional context
Seems to happen only for array properties.
Is your feature request related to a problem? Please describe.
I would like to be able to pass context to the custom validators per call.
I create and store an OperationValidator then call methods such as 'validateBody' on this. However my custom validation ideally needs to know whether it is validating a request or a response.
I can imagine other use cases where someone may want to make available the entire request or response object (or another object entirely) to the validators such that they can use this extra context for validation.
Describe the solution you'd like
Probably the most flexible solution is to support passing a generic 'Object' to the 'validateBody' etc validators which is pass around everywhere.
Alternatively there can be a generic object on "ValidationResults" that is already passed everywhere (will need to ensure this object is also added on any copies that are made in anyOf and oneOf validators).
It might be best to replace the existing ValidationResults with a new wrapper class:
class ValidatingContext
{
ValidationResults vr;
Object customData;
// or
virtual Object getCustomData()
}
Describe alternatives you've considered
Additional context
I can help work on a solution if you can give an idea of an approved design.
Describe the bug
When a schema is defined using allOf
and each schema referenced defines additionalProperties: false
then the schema validator seems to be reporting properties that belong to one of the schemas as additional properties and rejects them as errors.
To Reproduce
The code below produces:
Validation error(s) :
.allOf.additionalProperties : Additional property 'hunts' is not allowed.
import static java.lang.System.exit;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.openapi4j.core.exception.EncodeException;
import org.openapi4j.core.model.v3.OAI3;
import org.openapi4j.core.validation.ValidationResults;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.validation.v3.OpenApi3Validator;
import org.openapi4j.schema.validator.ValidationContext;
import org.openapi4j.schema.validator.v3.SchemaValidator;
public class QuickTest {
public static void main(String[] args) throws Exception {
String openApi =
"openapi: \"3.0.0\"\n"
+ "info:\n"
+ " title: Simple Test API\n"
+ " version: 0.1\n"
+ "paths:\n"
+ " /pets:\n"
+ " patch:\n"
+ " requestBody:\n"
+ " content:\n"
+ " application/json:\n"
+ " schema:\n"
+ " $ref: '#/components/schemas/Cat'\n"
+ " responses:\n"
+ " '200':\n"
+ " description: Updated\n"
+ "components:\n"
+ " schemas:\n"
+ " Pet:\n"
+ " type: object\n"
+ " required:\n"
+ " - pet_type\n"
+ " properties:\n"
+ " pet_type:\n"
+ " type: string\n"
+ " discriminator:\n"
+ " propertyName: pet_type\n"
+ " additionalProperties: false\n"
+ " Cat: # \"Cat\" is a value for the pet_type property (the discriminator value)\n"
+ " allOf: # Combines the main `Pet` schema with `Cat`-specific properties \n"
+ " - type: object\n"
+ " required:\n"
+ " - pet_type\n"
+ " properties:\n"
+ " pet_type:\n"
+ " type: string\n"
+ " additionalProperties: false\n"
+ " - type: object\n"
+ " additionalProperties: false\n"
+ " properties:\n"
+ " hunts:\n"
+ " type: boolean\n"
+ " age:\n"
+ " type: integer\n"
+ " example:\n"
+ " {\n"
+ " \"pet_type\": \"Cat\",\n"
+ " \"hunts\": false\n"
+ " }";
Path bug = Files.createTempFile("SchemaCompositionBug", "");
Files.write(bug, openApi.getBytes());
OpenApi3 api = new OpenApi3Parser().parse(bug.toFile(), false);
OpenApi3Validator.instance().validate(api);
api.getComponents().getSchemas().values().forEach(schema -> {
Object example = schema.getExample();
if (example != null) {
try {
final JsonNode dataNode = new ObjectMapper()
.readTree(new ObjectMapper().writeValueAsBytes(example));
final JsonNode schemaNode = schema.toNode(api.getContext(), true);
final ValidationResults results = new ValidationResults();
final ValidationContext<OAI3> validationContext = new ValidationContext<OAI3>(
api.getContext()).setFastFail(true);
final SchemaValidator schemaValidator = new SchemaValidator(validationContext, "",
schemaNode);
schemaValidator.validate(dataNode, results);
if (!results.isValid()) {
System.err.println(results.toString());
exit(-1);
}
} catch (IOException | EncodeException e) {
exit(-1);
}
}
});
}
}
Expected behavior
The above example should produce no validation errors.
Discriminator validator may compute wrongly external references.
This sample below could lead to stack overflows at parsing validation.
Despite the functional validity, the description is correct.
components:
schemas:
Self:
type: object
properties:
self:
$ref: '#/components/schemas/Self'
Foo:
type: object
properties:
child:
$ref: '#/components/schemas/Bar'
Bar:
type: object
properties:
parent:
$ref: '#/components/schemas/Foo'
Describe the bug
A modular OpenAPI spec can include two $refs with the same relative path, but different targets. These should be treated as different refs, but openapi4j-parser sees them as the same.
To Reproduce
See: https://github.com/romacafe/openapi4j-refs.
Basically, this structure contains identical $ref
strings that should refer to different content.
/
+- api.yaml
| +- $ref testType.yaml#/TestType
| \- $ref schema2/schema2.yaml#/Schema2
+- testType.yaml
| \- TestType (definition 1)
\- schema2
+- schema2.yaml
| \- $ref testType.yaml#/TestType
\- testType.yaml
\- TestType (definition 2)
Expected behavior
/testType.yaml#/TestType
and /schema2/testType.yaml#/TestType
should be recognized as different types, even when relative refs (TestType.yaml#/TestType
) look the same.
Additional context
Yes this is somewhat contrived, but large APIs are more maintainable when modularized, and it would be good to know conflicts like this can't happen. Otherwise, we'd need to be careful that all $ref values are unique.
Describe the bug
A spec file that references another via $ref will validate the contents of the referenced spec file, but will not integrate the content into the final parser for use .
To Reproduce
Steps to reproduce the behavior:
A file "openapi.json" that references another via path:
"paths": {
"/pet/{id}": {
"$ref": "other.json#/paths/~1pet~1{id}"
}
And another file "other.json":
"paths": {
"/pet/{id}": {
"get": {
etc
}
}
Then attempt to validate a request against the path.
// api = new OpenApi3Parser().parse("openapi.json");
Path path = api.getPath("/pet/{id}");
Operation op = path.getOperation("get");
// error - method "get" not found
Expected behavior
Expect to be able to use the "root" spec file that references other spec files to transparently validate data as though everything was defined in the "root" spec file.
Additional context
I can verify the referenced file is loaded and validated as part of parsing. Errors in the referenced file are detected correctly. However the contents referenced under the path are only available as the original string reference via the 'ref' property.
Describe the bug
Http status code ranges (e.g. 5XX) in OpenAPI 3 Responses object
do not validate successfully.
To Reproduce
Steps to reproduce the behavior:
responses:
"200":
description: Success
"5XX":
description: Unexpected error
OpenApi3Validator
'5XX' in map 'default|\d\d\d\'
Expected behavior
There should be no validation errors.
Additional context
Status code ranges are allowed in OpenAPI 3.
This code is mostly useless, it should only apply to additionalProperties
field.
'x-www-form-urlencoded' content type should be reviewed for better accuracy on input conversion.
style
and exploded
must be part of this work.
Describe the bug
The Body.from adapter only supports Map<String, Object>
, but it is quite valid to have an array not enclosed within a map. (bare values such as 'true' and '10' are also valid JSON).
To Reproduce
Expected behavior
Expect to be able to build a response/request object from a List<Object>
Additional context
Have tried adding the List<Object>
wrapper and it works as expected.
Will submit a pull request shortly.
Describe the bug
Enums in the Schema object are stored as a list of String's. This is a problem when converted back to JSON as the original type information is lost, making the schema unusable for schema validation
To Reproduce
import com.fasterxml.jackson.databind.JsonNode;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Schema;
import java.nio.file.Files;
import java.nio.file.Path;
public class EnumBug {
public static void main(String[] args) throws Exception {
String openApi =
"paths:\n" +
" /items:\n" +
" get:\n" +
" parameters:\n" +
" - in: query\n" +
" name: sort\n" +
" description: Sort order\n" +
" schema:\n" +
" type: integer\n" +
" enum: [0, 1]";
Path enumBug = Files.createTempFile("EnumBug", "");
Files.write(enumBug, openApi.getBytes());
OpenApi3 api = new OpenApi3Parser().parse(enumBug.toFile(), false);
Schema schema = api.getPath("/items").getOperation("get").getParameters().get(0).getSchema();
JsonNode jsonNode = schema.toNode(api.getContext(), false);
System.out.println(jsonNode.toPrettyString());
}
}
The output enum contains strings:
{
"enum" : [ "0", "1" ],
"type" : "integer"
}
Expected behavior
The output should be an enum of int's
{
"enum" : [ 0, 1 ],
"type" : "integer"
}
Additional context
I am attempting to use openapi4j to validate JSON examples, from the OpenAPI document and at runtime using https://github.com/networknt/json-schema-validator, any enumerated integer value currently fails
For now, we try to get a match from the given response with explicit status code.
Status code ranges are allowed : http://spec.openapis.org/oas/v3.0.2#patterned-fields-0
Explicit status code takes precedence over a range.
Describe the bug
I have one method response (202) with a header. The other responses don't have headers. When I have a 200 response the validator returns:
"Validation error(s) :
Response status '200', ranged or default has not been found. (code: 204)"
To Reproduce
Steps to reproduce the behavior:
Expected behavior
As the API does not declare any header for the response, the response should be valid
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.