Skip to content

Commit 8ea5d5f

Browse files
committed
Merge branch 'jorgesartori-master'
2 parents 577b647 + 88224f7 commit 8ea5d5f

File tree

5 files changed

+175
-103
lines changed

5 files changed

+175
-103
lines changed

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

Lines changed: 16 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -16,82 +16,35 @@
1616

1717
package com.networknt.schema;
1818

19-
import com.fasterxml.jackson.databind.JsonNode;
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
2022
import org.slf4j.Logger;
2123
import org.slf4j.LoggerFactory;
2224

23-
import java.util.*;
25+
import com.fasterxml.jackson.databind.JsonNode;
2426

2527
public class ReadOnlyValidator extends BaseJsonValidator implements JsonValidator {
2628
private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class);
2729

28-
private List<String> fieldNames = new ArrayList<String>();
30+
private Boolean writeMode;
2931

30-
public ReadOnlyValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
32+
public ReadOnlyValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
33+
ValidationContext validationContext) {
3134
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.READ_ONLY, validationContext);
32-
if (schemaNode.isArray()) {
33-
int size = schemaNode.size();
34-
for (int i = 0; i < size; i++) {
35-
fieldNames.add(schemaNode.get(i).asText());
36-
}
37-
}
38-
35+
this.writeMode = validationContext.getConfig().isWriteMode();
36+
String mode = writeMode ? "write mode" : "read mode";
37+
logger.debug("Loaded ReadOnlyValidator for property {} as {}", parentSchema, mode);
3938
parseErrorCode(getValidatorType().getErrorCodeKey());
4039
}
4140

4241
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
4342
debug(logger, node, rootNode, at);
44-
45-
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
46-
47-
for (String fieldName : fieldNames) {
48-
JsonNode propertyNode = node.get(fieldName);
49-
String datapath = "";
50-
if (at.equals("$")) {
51-
datapath = datapath + "#original." + fieldName;
52-
} else {
53-
datapath = datapath + "#original." + at.substring(2) + "." + fieldName;
54-
}
55-
JsonNode originalNode = getNode(datapath, rootNode);
56-
57-
boolean theSame = propertyNode != null && originalNode != null && propertyNode.equals(originalNode);
58-
if (!theSame) {
59-
errors.add(buildValidationMessage(at));
60-
}
61-
}
62-
63-
return Collections.unmodifiableSet(errors);
64-
}
65-
66-
private JsonNode getNode(String datapath, JsonNode data) {
67-
String path = getSubString(datapath,"$.");
68-
69-
String[] parts = path.split("\\.");
70-
JsonNode result = null;
71-
for (int i = 0; i < parts.length; i++) {
72-
if (parts[i].contains("[")) {
73-
int idx1 = parts[i].indexOf("[");
74-
int idx2 = parts[i].indexOf("]");
75-
String key = parts[i].substring(0, idx1).trim();
76-
int idx = Integer.parseInt(parts[i].substring(idx1 + 1, idx2).trim());
77-
result = data.get(key).get(idx);
78-
} else {
79-
result = data.get(parts[i]);
80-
}
81-
if (result == null) {
82-
break;
83-
}
84-
data = result;
85-
}
86-
return result;
87-
}
88-
89-
private String getSubString(String datapath, String keyword){
90-
String path = datapath;
91-
if (path.startsWith(keyword)) {
92-
path = path.substring(2);
93-
}
94-
return path;
43+
Set<ValidationMessage> errors= new HashSet<ValidationMessage>();
44+
if (writeMode) {
45+
errors.add(buildValidationMessage(at));
46+
}
47+
return errors;
9548
}
9649

97-
}
50+
}

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

Lines changed: 79 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,20 @@
2929

3030
public class SchemaValidatorsConfig {
3131
/**
32-
* when validate type, if TYPE_LOOSE = true, will try to convert string to different types to match the type defined in
33-
* schema.
32+
* when validate type, if TYPE_LOOSE = true, will try to convert string to
33+
* different types to match the type defined in schema.
3434
*/
3535
private boolean typeLoose;
3636

3737
/**
38-
* When set to true, validator process is stop immediately when a very first validation error is discovered.
38+
* When set to true, validator process is stop immediately when a very first
39+
* validation error is discovered.
3940
*/
4041
private boolean failFast;
4142

4243
/**
43-
* When set to true, walker sets nodes that are missing or NullNode to the default value, if any, and mutate the input json.
44+
* When set to true, walker sets nodes that are missing or NullNode to the
45+
* default value, if any, and mutate the input json.
4446
*/
4547
private ApplyDefaultsStrategy applyDefaultsStrategy;
4648

@@ -50,7 +52,8 @@ public class SchemaValidatorsConfig {
5052
private boolean ecma262Validator;
5153

5254
/**
53-
* When set to true, use Java-specific semantics rather than native JavaScript semantics
55+
* When set to true, use Java-specific semantics rather than native JavaScript
56+
* semantics
5457
*/
5558
private boolean javaSemantics;
5659

@@ -60,40 +63,49 @@ public class SchemaValidatorsConfig {
6063
private boolean losslessNarrowing;
6164

6265
/**
63-
* When set to true, support for discriminators is enabled for validations of oneOf, anyOf and allOf as described
64-
* on <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminatorObject">GitHub</a>.
66+
* When set to true, support for discriminators is enabled for validations of
67+
* oneOf, anyOf and allOf as described on <a href=
68+
* "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminatorObject">GitHub</a>.
6569
*/
6670
private boolean openAPI3StyleDiscriminators = false;
6771

6872
/**
69-
* Map of public, normally internet accessible schema URLs to alternate locations; this allows for offline
70-
* validation of schemas that refer to public URLs. This is merged with any mappings the {@link JsonSchemaFactory}
71-
* may have been built with.
73+
* Map of public, normally internet accessible schema URLs to alternate
74+
* locations; this allows for offline validation of schemas that refer to public
75+
* URLs. This is merged with any mappings the {@link JsonSchemaFactory} may have
76+
* been built with.
7277
*/
7378
private Map<String, String> uriMappings = new HashMap<String, String>();
7479

7580
private CompositeURITranslator uriTranslators = new CompositeURITranslator();
7681

7782
/**
78-
* When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable
79-
* however continues with validation against the nullable field
83+
* When a field is set as nullable in the OpenAPI specification, the schema
84+
* validator validates that it is nullable however continues with validation
85+
* against the nullable field
8086
* <p>
81-
* If handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed
82-
* If handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type
83-
* validator using the SchemaValidator to handle it.
87+
* If handleNullableField is set to true && incoming field is nullable && value
88+
* is field: null --> succeed If handleNullableField is set to false && incoming
89+
* field is nullable && value is field: null --> it is up to the type validator
90+
* using the SchemaValidator to handle it.
8491
*/
8592
private boolean handleNullableField = true;
8693

8794
/**
88-
* When set to true resets the {@link CollectorContext} by calling {@link CollectorContext#reset()}.
95+
* When set to true resets the {@link CollectorContext} by calling
96+
* {@link CollectorContext#reset()}.
8997
*/
9098
private boolean resetCollectorContext = true;
9199

100+
/**
101+
* When set to true considers that schema is used to write data then ReadOnlyValidator is activated. Default true.
102+
*/
103+
private boolean writeMode = true;
104+
92105
// This is just a constant for listening to all Keywords.
93106
public static final String ALL_KEYWORD_WALK_LISTENER_KEY = "com.networknt.AllKeywordWalkListener";
94107

95-
private final Map<String, List<JsonSchemaWalkListener>> keywordWalkListenersMap = new HashMap<String,
96-
List<JsonSchemaWalkListener>>();
108+
private final Map<String, List<JsonSchemaWalkListener>> keywordWalkListenersMap = new HashMap<String, List<JsonSchemaWalkListener>>();
97109

98110
private final List<JsonSchemaWalkListener> propertyWalkListeners = new ArrayList<JsonSchemaWalkListener>();
99111

@@ -112,9 +124,11 @@ public void setTypeLoose(boolean typeLoose) {
112124
}
113125

114126
/**
115-
* When enabled, {@link JsonValidator#validate(JsonNode, JsonNode, String)}
116-
* or {@link JsonValidator#validate(JsonNode)} doesn't return any {@link Set}&lt;{@link ValidationMessage}&gt;,
117-
* instead a {@link JsonSchemaException} is thrown as soon as a validation errors is discovered.
127+
* When enabled, {@link JsonValidator#validate(JsonNode, JsonNode, String)} or
128+
* {@link JsonValidator#validate(JsonNode)} doesn't return any
129+
* {@link Set}&lt;{@link ValidationMessage}&gt;, instead a
130+
* {@link JsonSchemaException} is thrown as soon as a validation errors is
131+
* discovered.
118132
*
119133
* @param failFast boolean
120134
*/
@@ -267,6 +281,7 @@ public void setLosslessNarrowing(boolean losslessNarrowing) {
267281

268282
/**
269283
* Indicates whether OpenAPI 3 style discriminators should be supported
284+
*
270285
* @return true in case discriminators are enabled
271286
* @since 1.0.51
272287
*/
@@ -275,26 +290,36 @@ public boolean isOpenAPI3StyleDiscriminators() {
275290
}
276291

277292
/**
278-
* When enabled, the validation of <code>anyOf</code> and <code>allOf</code> in polymorphism will respect
279-
* OpenAPI 3 style discriminators as described in the
280-
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminatorObject">OpenAPI 3.0.3 spec</a>.
281-
* The presence of a discriminator configuration on the schema will lead to the following changes in the behavior:
293+
* When enabled, the validation of <code>anyOf</code> and <code>allOf</code> in
294+
* polymorphism will respect OpenAPI 3 style discriminators as described in the
295+
* <a href=
296+
* "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminatorObject">OpenAPI
297+
* 3.0.3 spec</a>. The presence of a discriminator configuration on the schema
298+
* will lead to the following changes in the behavior:
282299
* <ul>
283-
* <li>for <code>oneOf</code> the spec is unfortunately very vague. Whether <code>oneOf</code> semantics should be
284-
* affected by discriminators or not is not even 100% clear within the members of the OAS steering committee. Therefore
285-
* <code>oneOf</code> at the moment ignores discriminators</li>
286-
* <li>for <code>anyOf</code> the validation will choose one of the candidate schemas for validation based on the
287-
* discriminator property value and will pass validation when this specific schema passes. This is in particular useful
288-
* when the payload could match multiple candidates in the <code>anyOf</code> list and could lead to ambiguity. Example:
289-
* type B has all mandatory properties of A and adds more mandatory ones. Whether the payload is an A or B is determined
290-
* via the discriminator property name. A payload indicating it is an instance of B then requires passing the validation
291-
* of B and passing the validation of A would not be sufficient anymore.</li>
292-
* <li>for <code>allOf</code> use cases with discriminators defined on the copied-in parent type, it is possible to
293-
* automatically validate against a subtype. Example: some schema specifies that there is a field of type A. A carries
294-
* a discriminator field and B inherits from A. Then B is automatically a candidate for validation as well and will be
295-
* chosen in case the discriminator property matches</li>
300+
* <li>for <code>oneOf</code> the spec is unfortunately very vague. Whether
301+
* <code>oneOf</code> semantics should be affected by discriminators or not is
302+
* not even 100% clear within the members of the OAS steering committee.
303+
* Therefore <code>oneOf</code> at the moment ignores discriminators</li>
304+
* <li>for <code>anyOf</code> the validation will choose one of the candidate
305+
* schemas for validation based on the discriminator property value and will
306+
* pass validation when this specific schema passes. This is in particular
307+
* useful when the payload could match multiple candidates in the
308+
* <code>anyOf</code> list and could lead to ambiguity. Example: type B has all
309+
* mandatory properties of A and adds more mandatory ones. Whether the payload
310+
* is an A or B is determined via the discriminator property name. A payload
311+
* indicating it is an instance of B then requires passing the validation of B
312+
* and passing the validation of A would not be sufficient anymore.</li>
313+
* <li>for <code>allOf</code> use cases with discriminators defined on the
314+
* copied-in parent type, it is possible to automatically validate against a
315+
* subtype. Example: some schema specifies that there is a field of type A. A
316+
* carries a discriminator field and B inherits from A. Then B is automatically
317+
* a candidate for validation as well and will be chosen in case the
318+
* discriminator property matches</li>
296319
* </ul>
297-
* @param openAPI3StyleDiscriminators whether or not discriminators should be used. Defaults to <code>false</code>
320+
*
321+
* @param openAPI3StyleDiscriminators whether or not discriminators should be
322+
* used. Defaults to <code>false</code>
298323
* @since 1.0.51
299324
*/
300325
public void setOpenAPI3StyleDiscriminators(boolean openAPI3StyleDiscriminators) {
@@ -316,4 +341,18 @@ public boolean isResetCollectorContext() {
316341
public void setResetCollectorContext(boolean resetCollectorContext) {
317342
this.resetCollectorContext = resetCollectorContext;
318343
}
319-
}
344+
345+
public boolean isWriteMode() {
346+
return writeMode;
347+
}
348+
349+
/**
350+
*
351+
* When set to true considers that schema is used to write data then ReadOnlyValidator is activated. Default true.
352+
*
353+
* @param writeMode
354+
*/
355+
public void setWriteMode(boolean writeMode) {
356+
this.writeMode = writeMode;
357+
}
358+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.networknt.schema;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.util.Set;
10+
import java.util.stream.Collectors;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
import com.fasterxml.jackson.databind.node.ObjectNode;
16+
17+
class ReadOnlyValidatorTest {
18+
19+
@Test
20+
void givenConfigWriteFalseWhenReadOnlyTrueThenAllows() throws IOException {
21+
ObjectNode node = getJsonNode();
22+
Set<ValidationMessage> errors = loadJsonSchema(false).validate(node);
23+
assertTrue(errors.isEmpty());
24+
}
25+
26+
@Test
27+
void givenConfigWriteTrueWhenReadOnlyTrueThenDenies() throws IOException {
28+
ObjectNode node = getJsonNode();
29+
Set<ValidationMessage> errors = loadJsonSchema(true).validate(node);
30+
assertFalse(errors.isEmpty());
31+
assertEquals("$.firstName: is a readonly field, it cannot be changed",
32+
errors.stream().map(e -> e.getMessage()).collect(Collectors.toList()).get(0));
33+
}
34+
35+
private JsonSchema loadJsonSchema(Boolean write) {
36+
JsonSchema schema = this.getJsonSchema(write);
37+
schema.initializeValidators();
38+
return schema;
39+
40+
}
41+
42+
private JsonSchema getJsonSchema(Boolean write) {
43+
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
44+
SchemaValidatorsConfig schemaConfig = createSchemaConfig(write);
45+
InputStream schema = getClass().getClassLoader().getResourceAsStream("schema/read-only-schema.json");
46+
return factory.getSchema(schema, schemaConfig);
47+
}
48+
49+
private SchemaValidatorsConfig createSchemaConfig(Boolean write) {
50+
SchemaValidatorsConfig config = new SchemaValidatorsConfig();
51+
config.setWriteMode(write);
52+
return config;
53+
}
54+
55+
private ObjectNode getJsonNode() throws IOException {
56+
InputStream node = getClass().getClassLoader().getResourceAsStream("data/read-only-data.json");
57+
ObjectMapper mapper = new ObjectMapper();
58+
return (ObjectNode) mapper.readTree(node);
59+
}
60+
61+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"firstName": "George",
3+
"lastName": "Harrison"
4+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "Read Only Schema",
4+
"description": "Testing Read Only Schema Validation",
5+
"type": "object",
6+
"properties": {
7+
"firstName": {
8+
"type": "string",
9+
"readOnly": true
10+
},
11+
"lastName": {
12+
"type": "string"
13+
}
14+
}
15+
}

0 commit comments

Comments
 (0)