Skip to content

Commit

Permalink
Add discriminator support for oneOf
Browse files Browse the repository at this point in the history
Signed-off-by: Pavel Demenkov <[email protected]>
  • Loading branch information
dps123 committed Sep 22, 2020
1 parent 3937452 commit 8cd3701
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,34 @@
package io.vertx.ext.json.schema.common;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.pointer.JsonPointer;
import io.vertx.ext.json.schema.NoSyncValidationException;
import io.vertx.ext.json.schema.ValidationException;

import java.util.Arrays;
import java.util.stream.Collectors;

import static io.vertx.ext.json.schema.ValidationException.createException;
import java.util.Map;

public class OneOfValidatorFactory extends BaseCombinatorsValidatorFactory {

@Override
public Validator createValidator(JsonObject schema, JsonPointer scope, SchemaParserInternal parser, MutableStateValidator parent) {
OneOfValidator validator = (OneOfValidator) super.createValidator(schema, scope, parser, parent);
if (schema.containsKey("discriminator") && schema.getValue("discriminator") instanceof JsonObject) {
JsonObject discriminator = schema.getJsonObject("discriminator");
validator.setPropertyName(discriminator.getString("propertyName"));
if (discriminator.containsKey("mapping")) {
validator.setMapping(discriminator.getJsonObject("mapping").getMap().entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().toString()))
);
}
}
return validator;
}

@Override
BaseCombinatorsValidator instantiate(MutableStateValidator parent) {
return new OneOfValidator(parent);
Expand All @@ -33,6 +51,9 @@ String getKeyword() {

class OneOfValidator extends BaseCombinatorsValidator {

private String propertyName;
private Map<String, String> mapping;

public OneOfValidator(MutableStateValidator parent) {
super(parent);
}
Expand All @@ -49,7 +70,7 @@ private boolean isValidSync(SchemaInternal schema, ValidatorContext context, Obj
@Override
public void validateSync(ValidatorContext context, Object in) throws ValidationException, NoSyncValidationException {
this.checkSync();
long validCount = Arrays.stream(schemas).map(s -> isValidSync(s, context, in)).filter(b -> b.equals(true)).count();
long validCount = Arrays.stream(schemas).filter(s -> checkDiscriminator(s, in)).map(s -> isValidSync(s, context, in)).filter(b -> b.equals(true)).count();
if (validCount > 1) throw createException("More than one schema valid", "oneOf", in);
else if (validCount == 0) throw createException("No schema matches", "oneOf", in);
}
Expand All @@ -58,9 +79,30 @@ public void validateSync(ValidatorContext context, Object in) throws ValidationE
public Future<Void> validateAsync(ValidatorContext context, Object in) {
if (isSync()) return validateSyncAsAsync(context, in);
return FutureUtils
.oneOf(Arrays.stream(schemas).map(s -> s.validateAsync(context, in)).collect(Collectors.toList()))
.oneOf(Arrays.stream(schemas).filter(s -> checkDiscriminator(s, in)).map(s -> s.validateAsync(context, in)).collect(Collectors.toList()))
.recover(err -> Future.failedFuture(createException("No schema matches", "oneOf", in, err)));
}
}

private boolean checkDiscriminator(SchemaInternal schema, Object in) {
if (propertyName != null) {
if (in instanceof JsonObject) {
String discriminator = ((JsonObject) in).getString(propertyName);
if (mapping != null) discriminator = mapping.get(discriminator);

JsonObject jsonSchema = ((JsonObject) schema.getJson());
String schemaName = (jsonSchema.containsKey("name")) ? jsonSchema.getString("name") : jsonSchema.getString("$ref");
return discriminator != null && discriminator.equals(schemaName);
}
}
return true;
}

public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}

public void setMapping(Map<String, String> mapping) {
this.mapping = mapping;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Stream<String> getTestFiles() {
"additionalProperties",
"allOf",
"anyOf",
// "discriminator",
"discriminator",
"enum",
"exclusiveMaximum",
"exclusiveMinimum",
Expand Down
169 changes: 169 additions & 0 deletions src/test/resources/tck/openapi3/discriminator.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,174 @@
"valid": false
}
]
},
{
"description": "discriminator with oneOf validation with ref",
"schema": {
"oneOf": [
{
"$ref": "#/definitions/A"
},
{
"$ref": "#/definitions/B"
}
],
"discriminator": {
"propertyName": "id"
},
"definitions": {
"A": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value_a": {
"type": "string"
}
},
"required": ["id", "value_a"]
},
"B": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value_b": {
"type": "string"
}
},
"required": ["id"]
}
}
},
"tests": [
{
"description": "should be valid with id A and property value_a",
"data": {
"id": "A",
"value_a": "a"
},
"valid": true
},
{
"description": "should be invalid with id A and property value_b",
"data": {
"id": "A",
"value_b": "b"
},
"valid": false
},
{
"description": "should be valid with id B and property value_b",
"data": {
"id": "B",
"value_b": "b"
},
"valid": true
},
{
"description": "should be invalid without id",
"data": {
"value_b": "b"
},
"valid": false
},
{
"description": "should be invalid with id A and without value_a",
"data": {
"id": "A"
},
"valid": false
}
]
},
{
"description": "discriminator with oneOf validation with ref and mapping",
"schema": {
"oneOf": [
{
"$ref": "#/definitions/Aref"
},
{
"$ref": "#/definitions/Bref"
}
],
"discriminator": {
"propertyName": "id",
"mapping": {
"A": "#/definitions/Aref",
"B": "#/definitions/Bref"
}
},
"definitions": {
"Aref": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value_a": {
"type": "string"
}
},
"required": ["id", "value_a"]
},
"Bref": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value_b": {
"type": "string"
}
},
"required": ["id"]
}
}
},
"tests": [
{
"description": "should be valid with id A and property value_a",
"data": {
"id": "A",
"value_a": "a"
},
"valid": true
},
{
"description": "should be invalid with id A and property value_b",
"data": {
"id": "A",
"value_b": "b"
},
"valid": false
},
{
"description": "should be valid with id B and property value_b",
"data": {
"id": "B",
"value_b": "b"
},
"valid": true
},
{
"description": "should be invalid without id",
"data": {
"value_b": "b"
},
"valid": false
},
{
"description": "should be invalid with id A and without value_a",
"data": {
"id": "A"
},
"valid": false
}
]
}

]

0 comments on commit 8cd3701

Please sign in to comment.