Skip to content

Commit bbbbd1c

Browse files
authored
Fix oneOf when discriminator enabled but no discriminator (#1078)
1 parent 92bef22 commit bbbbd1c

File tree

2 files changed

+202
-5
lines changed

2 files changed

+202
-5
lines changed

src/main/java/com/networknt/schema/OneOfValidator.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@
1616

1717
package com.networknt.schema;
1818

19-
import com.fasterxml.jackson.databind.JsonNode;
20-
import com.networknt.schema.utils.SetView;
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.HashSet;
22+
import java.util.LinkedHashSet;
23+
import java.util.List;
24+
import java.util.Set;
2125

2226
import org.slf4j.Logger;
2327
import org.slf4j.LoggerFactory;
2428

25-
import java.util.*;
29+
import com.fasterxml.jackson.databind.JsonNode;
30+
import com.fasterxml.jackson.databind.node.ObjectNode;
31+
import com.networknt.schema.utils.SetView;
2632

2733
/**
2834
* {@link JsonValidator} for oneOf.
@@ -75,10 +81,15 @@ protected Set<ValidationMessage> validate(ExecutionContext executionContext, Jso
7581
if (this.validationContext.getConfig().isDiscriminatorKeywordEnabled()) {
7682
DiscriminatorContext discriminatorContext = new DiscriminatorContext();
7783
executionContext.enterDiscriminatorContext(discriminatorContext, instanceLocation);
78-
84+
7985
// check if discriminator present
8086
discriminator = (DiscriminatorValidator) this.getParentSchema().getValidators().stream()
8187
.filter(v -> "discriminator".equals(v.getKeyword())).findFirst().orElse(null);
88+
if (discriminator != null) {
89+
// this is just to make the discriminator context active
90+
discriminatorContext.registerDiscriminator(discriminator.getSchemaLocation(),
91+
(ObjectNode) discriminator.getSchemaNode());
92+
}
8293
}
8394
executionContext.setFailFast(false);
8495
for (JsonSchema schema : this.schemas) {
@@ -135,7 +146,8 @@ protected Set<ValidationMessage> validate(ExecutionContext executionContext, Jso
135146
// found is null triggers on the correct schema
136147
childErrors = new SetView<>();
137148
childErrors.union(schemaErrors);
138-
} else if (currentDiscriminatorContext.isDiscriminatorIgnore()) {
149+
} else if (currentDiscriminatorContext.isDiscriminatorIgnore()
150+
|| !currentDiscriminatorContext.isActive()) {
139151
// This is the normal handling when discriminators aren't enabled
140152
if (childErrors == null) {
141153
childErrors = new SetView<>();

src/test/java/com/networknt/schema/OneOfValidatorTest.java

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,4 +284,189 @@ void fixedSwaggerIoExample() {
284284
+ "}";
285285
assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN));
286286
}
287+
288+
/**
289+
* Test for when the discriminator keyword is enabled but no discriminator is
290+
* present in the schema. This should process as a normal oneOf and return the
291+
* error messages.
292+
*/
293+
@Test
294+
void oneOfDiscriminatorEnabled() {
295+
String schemaData = "{\r\n"
296+
+ " \"oneOf\": [\r\n"
297+
+ " {\r\n"
298+
+ " \"type\": \"string\"\r\n"
299+
+ " },\r\n"
300+
+ " {\r\n"
301+
+ " \"type\": \"number\"\r\n"
302+
+ " }\r\n"
303+
+ " ]\r\n"
304+
+ "}";
305+
JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
306+
SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build());
307+
String inputData = "{}";
308+
Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
309+
assertEquals(3, messages.size());
310+
}
311+
312+
/**
313+
* Standard case where the discriminator is in the same schema as oneOf.
314+
* <p>
315+
* Note that discriminators do not affect the validation result and can only
316+
* affect the messages returned.
317+
*/
318+
@Test
319+
void oneOfDiscriminatorEnabledWithDiscriminator() {
320+
String schemaData = "{\r\n"
321+
+ " \"discriminator\": {\r\n"
322+
+ " \"propertyName\": \"type\",\r\n"
323+
+ " \"mapping\": {\r\n"
324+
+ " \"string\": \"#/$defs/string\",\r\n"
325+
+ " \"number\": \"#/$defs/number\"\r\n"
326+
+ " }\r\n"
327+
+ " },\r\n"
328+
+ " \"oneOf\": [\r\n"
329+
+ " {\r\n"
330+
+ " \"$ref\": \"#/$defs/string\"\r\n"
331+
+ " },\r\n"
332+
+ " {\r\n"
333+
+ " \"$ref\": \"#/$defs/number\"\r\n"
334+
+ " }\r\n"
335+
+ " ],\r\n"
336+
+ " \"$defs\": {\r\n"
337+
+ " \"string\": {\r\n"
338+
+ " \"properties\": {\r\n"
339+
+ " \"type\": {\r\n"
340+
+ " \"type\": \"string\"\r\n"
341+
+ " },\r\n"
342+
+ " \"value\": {\r\n"
343+
+ " \"type\": \"string\"\r\n"
344+
+ " }\r\n"
345+
+ " }\r\n"
346+
+ " },\r\n"
347+
+ " \"number\": {\r\n"
348+
+ " \"properties\": {\r\n"
349+
+ " \"type\": {\r\n"
350+
+ " \"type\": \"string\"\r\n"
351+
+ " },\r\n"
352+
+ " \"value\": {\r\n"
353+
+ " \"type\": \"number\"\r\n"
354+
+ " }\r\n"
355+
+ " }\r\n"
356+
+ " }\r\n"
357+
+ " }\r\n"
358+
+ "}";
359+
JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
360+
SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build());
361+
// Valid
362+
String inputData = "{\r\n"
363+
+ " \"type\": \"number\",\r\n"
364+
+ " \"value\": 1\r\n"
365+
+ "}";
366+
Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
367+
assertEquals(0, messages.size());
368+
369+
// Invalid only 1 message returned for number
370+
String inputData2 = "{\r\n"
371+
+ " \"type\": \"number\",\r\n"
372+
+ " \"value\": {}\r\n"
373+
+ "}";
374+
Set<ValidationMessage> messages2 = schema.validate(inputData2, InputFormat.JSON);
375+
assertEquals(2, messages2.size());
376+
377+
// Invalid both messages for string and object returned
378+
JsonSchema schema2 = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
379+
SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(false).build());
380+
Set<ValidationMessage> messages3 = schema2.validate(inputData2, InputFormat.JSON);
381+
assertEquals(3, messages3.size());
382+
}
383+
384+
/**
385+
* Subclass case where the discriminator is in an allOf inside one of the oneOf references.
386+
* <p>
387+
* Note that discriminators do not affect the validation result and can only
388+
* affect the messages returned.
389+
*/
390+
@Test
391+
void oneOfDiscriminatorEnabledWithDiscriminatorInSubclass() {
392+
String schemaData = "{\r\n"
393+
+ " \"oneOf\": [\r\n"
394+
+ " {\r\n"
395+
+ " \"$ref\": \"#/$defs/string\"\r\n"
396+
+ " },\r\n"
397+
+ " {\r\n"
398+
+ " \"$ref\": \"#/$defs/number\"\r\n"
399+
+ " }\r\n"
400+
+ " ],\r\n"
401+
+ " \"$defs\": {\r\n"
402+
+ " \"typed\": {\r\n"
403+
+ " \"discriminator\": {\r\n"
404+
+ " \"propertyName\": \"type\",\r\n"
405+
+ " \"mapping\": {\r\n"
406+
+ " \"string\": \"#/$defs/string\",\r\n"
407+
+ " \"number\": \"#/$defs/number\"\r\n"
408+
+ " }\r\n"
409+
+ " }\r\n"
410+
+ " },\r\n"
411+
+ " \"string\": {\r\n"
412+
+ " \"allOf\": [\r\n"
413+
+ " {\r\n"
414+
+ " \"$ref\": \"#/$defs/typed\"\r\n"
415+
+ " },\r\n"
416+
+ " {\r\n"
417+
+ " \"properties\": {\r\n"
418+
+ " \"type\": {\r\n"
419+
+ " \"type\": \"string\"\r\n"
420+
+ " },\r\n"
421+
+ " \"value\": {\r\n"
422+
+ " \"type\": \"string\"\r\n"
423+
+ " }\r\n"
424+
+ " }\r\n"
425+
+ " }\r\n"
426+
+ " ]\r\n"
427+
+ " },\r\n"
428+
+ " \"number\": {\r\n"
429+
+ " \"allOf\": [\r\n"
430+
+ " {\r\n"
431+
+ " \"$ref\": \"#/$defs/typed\"\r\n"
432+
+ " },\r\n"
433+
+ " {\r\n"
434+
+ " \"properties\": {\r\n"
435+
+ " \"type\": {\r\n"
436+
+ " \"type\": \"string\"\r\n"
437+
+ " },\r\n"
438+
+ " \"value\": {\r\n"
439+
+ " \"type\": \"number\"\r\n"
440+
+ " }\r\n"
441+
+ " }\r\n"
442+
+ " }\r\n"
443+
+ " ]\r\n"
444+
+ " }\r\n"
445+
+ " }\r\n"
446+
+ "}";
447+
JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
448+
SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build());
449+
// Valid
450+
String inputData = "{\r\n"
451+
+ " \"type\": \"number\",\r\n"
452+
+ " \"value\": 1\r\n"
453+
+ "}";
454+
Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
455+
assertEquals(0, messages.size());
456+
457+
// Invalid only 1 message returned for number
458+
String inputData2 = "{\r\n"
459+
+ " \"type\": \"number\",\r\n"
460+
+ " \"value\": {}\r\n"
461+
+ "}";
462+
Set<ValidationMessage> messages2 = schema.validate(inputData2, InputFormat.JSON);
463+
assertEquals(2, messages2.size());
464+
465+
// Invalid both messages for string and object returned
466+
JsonSchema schema2 = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
467+
SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(false).build());
468+
Set<ValidationMessage> messages3 = schema2.validate(inputData2, InputFormat.JSON);
469+
assertEquals(3, messages3.size());
470+
}
471+
287472
}

0 commit comments

Comments
 (0)