Skip to content

Commit 4880a71

Browse files
committed
Refactor discriminator
1 parent f9c8d56 commit 4880a71

File tree

4 files changed

+267
-172
lines changed

4 files changed

+267
-172
lines changed

src/main/java/com/networknt/schema/keyword/AnyOfValidator.java

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -106,55 +106,56 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo
106106
numberOfValidSubSchemas++;
107107
}
108108

109-
if (errors.isEmpty() && (!this.schemaContext.isDiscriminatorKeywordEnabled())
110-
&& canShortCircuit() && canShortCircuit(executionContext)) {
109+
if (errors.isEmpty() && (!this.schemaContext.isDiscriminatorKeywordEnabled()) && canShortCircuit()
110+
&& canShortCircuit(executionContext)) {
111111
// Successful so return only the existing errors, ie. no new errors
112112
executionContext.setErrors(existingErrors);
113113
return;
114114
} else if (this.schemaContext.isDiscriminatorKeywordEnabled()) {
115-
JsonNode refNode = schema.getSchemaNode().get("$ref");
116-
DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation);
117-
boolean discriminatorMatchFound = false;
118-
if (refNode != null) {
119-
// Check if there is a match
120-
String discriminatingValue = state.getDiscriminatingValue();
121-
if (discriminatingValue != null) {
122-
String ref = refNode.asText();
123-
if (state.isExplicitMapping() && ref.equals(discriminatingValue)) {
124-
// Explicit matching
125-
discriminatorMatchFound = true;
126-
state.setMatch(ref);
127-
} else if (!state.isExplicitMapping() && ref.endsWith(discriminatingValue)) {
128-
// Implicit matching
129-
discriminatorMatchFound = true;
130-
state.setMatch(ref);
131-
}
132-
}
133-
}
115+
JsonNode refNode = schema.getSchemaNode().get("$ref");
116+
DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation);
117+
boolean discriminatorMatchFound = false;
118+
if (refNode != null) {
119+
// Check if there is a match
120+
String discriminatingValue = state.getMappedSchema();
121+
if (discriminatingValue != null) {
122+
String ref = refNode.asText();
123+
if (state.isExplicitMapping() && ref.equals(discriminatingValue)) {
124+
// Explicit matching
125+
discriminatorMatchFound = true;
126+
state.setMatchedSchema(ref);
127+
} else if (!state.isExplicitMapping() && ref.endsWith(discriminatingValue)) {
128+
// Implicit matching
129+
discriminatorMatchFound = true;
130+
state.setMatchedSchema(ref);
131+
}
132+
}
133+
}
134134
if (discriminatorMatchFound) {
135-
/*
136-
* Note that discriminator cannot change the outcome of the evaluation but can be used to filter off
137-
* any additional messages
138-
*/
139-
if (!errors.isEmpty()) {
140-
/*
141-
* This means that the discriminated value has errors and doesn't match so these errors
142-
* are the only ones that will be reported *IF* there are no other schemas that successfully
143-
* validate to meet the requirement of anyOf.
144-
*
145-
* If there are any successful schemas as per anyOf, all these errors will be discarded.
146-
*/
147-
discriminatorErrors = new ArrayList<>(errors);
148-
allErrors = null; // This is no longer needed
149-
}
135+
/*
136+
* Note that discriminator cannot change the outcome of the evaluation but can
137+
* be used to filter off any additional messages
138+
*/
139+
if (!errors.isEmpty()) {
140+
/*
141+
* This means that the discriminated value has errors and doesn't match so these
142+
* errors are the only ones that will be reported *IF* there are no other
143+
* schemas that successfully validate to meet the requirement of anyOf.
144+
*
145+
* If there are any successful schemas as per anyOf, all these errors will be
146+
* discarded.
147+
*/
148+
discriminatorErrors = new ArrayList<>(errors);
149+
allErrors = null; // This is no longer needed
150+
}
150151
}
151152
}
152-
153153
/*
154-
* This adds all the errors for this schema to the list that contains all the errors for later reporting.
154+
* This adds all the errors for this schema to the list that contains all the
155+
* errors for later reporting.
155156
*
156-
* There's no need to add these if there was a discriminator match with errors as only the discriminator
157-
* errors will be reported if all the schemas fail.
157+
* There's no need to add these if there was a discriminator match with errors
158+
* as only the discriminator errors will be reported if all the schemas fail.
158159
*/
159160
if (!errors.isEmpty() && discriminatorErrors == null) {
160161
if (allErrors == null) {
@@ -169,21 +170,28 @@ && canShortCircuit() && canShortCircuit(executionContext)) {
169170
}
170171

171172
if (this.schemaContext.isDiscriminatorKeywordEnabled()) {
172-
// https://spec.openapis.org/oas/v3.1.0#discriminator-object
173-
// If the discriminator value does not match an implicit or explicit mapping, no schema can be determined and validation SHOULD fail. Mapping keys MUST be string values, but tooling MAY convert response values to strings for comparison.
174-
175-
/*
176-
* The only case where the discriminator can change the outcome of the result is if the discriminator value does not match an implicit or explicit mapping
177-
*/
178-
DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation);
179-
if (state != null && state.getMatch() == null && state.getPropertyValue() != null) {
180-
// state.getPropertyValue() == null means there is no value at propertyName
181-
existingErrors.add(error().keyword("discriminator").instanceNode(node).instanceLocation(instanceLocation)
182-
.locale(executionContext.getExecutionConfig().getLocale())
183-
.arguments(
184-
"based on the provided discriminator. No alternative could be chosen based on the discriminator property")
185-
.build());
186-
}
173+
/*
174+
* The only case where the discriminator can change the outcome of the result is
175+
* if the discriminator value does not match an implicit or explicit mapping
176+
*/
177+
/*
178+
* If the discriminator value does not match an implicit or explicit mapping, no
179+
* schema can be determined and validation SHOULD fail. Mapping keys MUST be
180+
* string values, but tooling MAY convert response values to strings for
181+
* comparison.
182+
*
183+
* https://spec.openapis.org/oas/v3.1.0#discriminator-object
184+
*/
185+
DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation);
186+
if (state != null && state.getMatchedSchema() == null && state.getDiscriminatingValue() != null) {
187+
// state.getPropertyValue() == null means there is no value at propertyName
188+
existingErrors.add(error().keyword("discriminator").instanceNode(node)
189+
.instanceLocation(instanceLocation)
190+
.locale(executionContext.getExecutionConfig().getLocale())
191+
.arguments(
192+
"based on the provided discriminator. No alternative could be chosen based on the discriminator property")
193+
.build());
194+
}
187195
}
188196
} finally {
189197
if (this.schemaContext.isDiscriminatorKeywordEnabled()) {
@@ -194,11 +202,12 @@ && canShortCircuit() && canShortCircuit(executionContext)) {
194202
// Successful so return only the existing errors, ie. no new errors
195203
executionContext.setErrors(existingErrors);
196204
} else {
197-
if (discriminatorErrors != null) {
198-
// If errors are present matching the discriminator, only these errors should be reported
205+
if (discriminatorErrors != null) {
206+
// If errors are present matching the discriminator, only these errors should be
207+
// reported
199208
existingErrors.addAll(discriminatorErrors);
200-
} else if (allErrors != null) {
201-
// As the anyOf has failed, report all the errors
209+
} else if (allErrors != null) {
210+
// As the anyOf has failed, report all the errors
202211
existingErrors.addAll(allErrors);
203212
}
204213
executionContext.setErrors(existingErrors);

src/main/java/com/networknt/schema/keyword/DiscriminatorState.java

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,83 @@
44
* Discriminator state for an instance location.
55
*/
66
public class DiscriminatorState {
7-
private String propertyName;
8-
private String propertyValue;
9-
private String discriminatingValue = null;
10-
private boolean explicitMapping = false;
11-
private String match;
12-
13-
public String getPropertyName() {
14-
return propertyName;
15-
}
16-
17-
public void setPropertyName(String propertyName) {
18-
this.propertyName = propertyName;
19-
}
20-
21-
public String getPropertyValue() {
22-
return propertyValue;
23-
}
24-
25-
public void setPropertyValue(String propertyValue) {
26-
this.propertyValue = propertyValue;
27-
}
28-
29-
public String getDiscriminatingValue() {
30-
return discriminatingValue;
31-
}
32-
33-
public void setDiscriminatingValue(String discriminatingValue) {
34-
this.discriminatingValue = discriminatingValue;
35-
}
36-
37-
public boolean isExplicitMapping() {
38-
return explicitMapping;
39-
}
40-
41-
public void setExplicitMapping(boolean explicitMapping) {
42-
this.explicitMapping = explicitMapping;
43-
}
44-
45-
public void setMatch(String match) {
46-
this.match = match;
47-
}
48-
49-
public String getMatch() {
50-
return this.match;
51-
}
7+
private String propertyName;
8+
private String discriminatingValue;
9+
private String mappedSchema = null;
10+
private boolean explicitMapping = false;
11+
private String matchedSchema;
12+
13+
/**
14+
* Gets the property name defined in the discriminator keyword schema.
15+
*
16+
* @return
17+
*/
18+
public String getPropertyName() {
19+
return propertyName;
20+
}
21+
22+
/**
23+
* Sets the property name defined in the discriminator keyword schema.
24+
*
25+
* @param propertyName the property name
26+
*/
27+
public void setPropertyName(String propertyName) {
28+
this.propertyName = propertyName;
29+
}
30+
31+
/**
32+
* Gets the discriminating value, which is the value in the payload
33+
* corresponding with the property name.
34+
*
35+
* @return the discriminating value
36+
*/
37+
public String getDiscriminatingValue() {
38+
return discriminatingValue;
39+
}
40+
41+
/**
42+
* Sets the discriminating value, which is the value in the payload
43+
* corresponding with the property name.
44+
*
45+
* @param discriminatingValue
46+
*/
47+
public void setDiscriminatingValue(String discriminatingValue) {
48+
this.discriminatingValue = discriminatingValue;
49+
}
50+
51+
/**
52+
* Gets the mapped schema which is the discriminating value mapped to the schema
53+
* name or uri.
54+
*
55+
* @return the mapped schema
56+
*/
57+
public String getMappedSchema() {
58+
return mappedSchema;
59+
}
60+
61+
/**
62+
* Sets the mapped schema which is the discriminating value mapped to the schema
63+
* name or uri.
64+
*
65+
* @param mappedSchema the mapped schema
66+
*/
67+
public void setMappedSchema(String mappedSchema) {
68+
this.mappedSchema = mappedSchema;
69+
}
70+
71+
public boolean isExplicitMapping() {
72+
return explicitMapping;
73+
}
74+
75+
public void setExplicitMapping(boolean explicitMapping) {
76+
this.explicitMapping = explicitMapping;
77+
}
78+
79+
public void setMatchedSchema(String matchedSchema) {
80+
this.matchedSchema = matchedSchema;
81+
}
82+
83+
public String getMatchedSchema() {
84+
return this.matchedSchema;
85+
}
5286
}

0 commit comments

Comments
 (0)