From 459e85e16d8627534e4bce37afa19167343d253e Mon Sep 17 00:00:00 2001
From: Justin Tay <49700559+justin-tay@users.noreply.github.com>
Date: Fri, 14 Jun 2024 11:23:10 +0800
Subject: [PATCH 01/16] Add builder for SchemaValidatorsConfig.
---
.../schema/SchemaValidatorsConfig.java | 1367 ++++++++++++-----
1 file changed, 962 insertions(+), 405 deletions(-)
diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
index 75e5ff87c..1dc3ee0a5 100644
--- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
+++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2016 Network New Technologies Inc.
*
- * Licensed under the Apache License, Version 2.0 (the "License");
+ * Licensed under the Apache LicenseBuilder Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
@@ -39,18 +39,29 @@
* Configuration for validators.
*/
public class SchemaValidatorsConfig {
+ // This is just a constant for listening to all Keywords.
+ public static final String ALL_KEYWORD_WALK_LISTENER_KEY = "com.networknt.AllKeywordWalkListener";
+
public static final int DEFAULT_PRELOAD_JSON_SCHEMA_REF_MAX_NESTING_DEPTH = 40;
/**
- * Used to validate the acceptable $id values.
+ * The strategy the walker uses to sets nodes that are missing or NullNode to
+ * the default value, if any, and mutate the input json.
*/
- private JsonSchemaIdValidator schemaIdValidator = JsonSchemaIdValidator.DEFAULT;
+ private ApplyDefaultsStrategy applyDefaultsStrategy = ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
/**
- * when validate type, if TYPE_LOOSE = true, will try to convert string to
- * different types to match the type defined in schema.
+ * Controls if schemas loaded from refs will be cached and reused for subsequent runs.
*/
- private boolean typeLoose;
+ private boolean cacheRefs = true;
+
+ /**
+ * When set to true, "messages" provided in schema are used for forming validation errors
+ * else default messages are used
+ */
+ private boolean customMessageSupported = true;
+
+ private ExecutionContextCustomizer executionContextCustomizer;
/**
* When set to true, validator process is stop immediately when a very first
@@ -59,15 +70,25 @@ public class SchemaValidatorsConfig {
private boolean failFast;
/**
- * When set to true, walker sets nodes that are missing or NullNode to the
- * default value, if any, and mutate the input json.
+ * Since Draft 2019-09 format assertions are not enabled by default.
*/
- private ApplyDefaultsStrategy applyDefaultsStrategy = ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
+ private Boolean formatAssertionsEnabled = null;
/**
- * Used to create {@link com.networknt.schema.regex.RegularExpression}.
+ * When a field is set as nullable in the OpenAPI specification, the schema
+ * validator validates that it is nullable however continues with validation
+ * against the nullable field
+ *
+ * If handleNullableField is set to true && incoming field is nullable && value
+ * is field: null --> succeed If handleNullableField is set to false && incoming
+ * field is nullable && value is field: null --> it is up to the type validator
+ * using the SchemaValidator to handle it.
*/
- private RegularExpressionFactory regularExpressionFactory = JDKRegularExpressionFactory.getInstance();
+ private boolean handleNullableField = true;
+
+ private final WalkListenerRunner itemWalkListenerRunner = new DefaultItemWalkListenerRunner(getArrayItemWalkListeners());
+
+ private final List itemWalkListeners;
/**
* When set to true, use Java-specific semantics rather than native JavaScript
@@ -75,16 +96,24 @@ public class SchemaValidatorsConfig {
*/
private boolean javaSemantics;
+ private final WalkListenerRunner keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(getKeywordWalkListenersMap());
+
+ private final Map> keywordWalkListenersMap;
+
+ /**
+ * The Locale to consider when loading validation messages from the default resource bundle.
+ */
+ private Locale locale;
+
/**
* When set to true, can interpret round doubles as integers
*/
private boolean losslessNarrowing;
/**
- * When set to true, "messages" provided in schema are used for forming validation errors
- * else default messages are used
+ * The message source to use for generating localised messages.
*/
- private boolean customMessageSupported = true;
+ private MessageSource messageSource;
/**
* When set to true, support for discriminators is enabled for validations of
@@ -93,37 +122,6 @@ public class SchemaValidatorsConfig {
*/
private boolean openAPI3StyleDiscriminators = false;
- /**
- * Contains a mapping of how strict a keyword's validators should be.
- * Defaults to {@literal true}.
- *
- * Each validator has its own understanding of what constitutes strict
- * and permissive.
- */
- private final Map strictness = new HashMap<>(0);
-
- /**
- * When a field is set as nullable in the OpenAPI specification, the schema
- * validator validates that it is nullable however continues with validation
- * against the nullable field
- *
- * If handleNullableField is set to true && incoming field is nullable && value
- * is field: null --> succeed If handleNullableField is set to false && incoming
- * field is nullable && value is field: null --> it is up to the type validator
- * using the SchemaValidator to handle it.
- */
- private boolean handleNullableField = true;
-
- /**
- * When set to true assumes that schema is used to validate incoming data from an API.
- */
- private Boolean readOnly = null;
-
- /**
- * When set to true assumes that schema is used to to validate outgoing data from an API.
- */
- private Boolean writeOnly = null;
-
/**
* The approach used to generate paths in reported messages, logs and errors. Default is the legacy "JSONPath-like" approach.
*/
@@ -139,163 +137,231 @@ public class SchemaValidatorsConfig {
*/
private int preloadJsonSchemaRefMaxNestingDepth = DEFAULT_PRELOAD_JSON_SCHEMA_REF_MAX_NESTING_DEPTH;
- /**
- * Controls if schemas loaded from refs will be cached and reused for subsequent runs.
- */
- private boolean cacheRefs = true;
-
- // This is just a constant for listening to all Keywords.
- public static final String ALL_KEYWORD_WALK_LISTENER_KEY = "com.networknt.AllKeywordWalkListener";
-
- private final Map> keywordWalkListenersMap = new HashMap<>();
-
- private final List propertyWalkListeners = new ArrayList<>();
-
- private final List itemWalkListeners = new ArrayList<>();
+ private final WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(getPropertyWalkListeners());
- private ExecutionContextCustomizer executionContextCustomizer;
+ private final List propertyWalkListeners;
- @Deprecated
- private boolean loadCollectors = true;
+ /**
+ * When set to true assumes that schema is used to validate incoming data from an API.
+ */
+ private Boolean readOnly = null;
/**
- * The Locale to consider when loading validation messages from the default resource bundle.
+ * Used to create {@link com.networknt.schema.regex.RegularExpression}.
*/
- private Locale locale;
+ private RegularExpressionFactory regularExpressionFactory = JDKRegularExpressionFactory.getInstance();
/**
- * The message source to use for generating localised messages.
+ * Used to validate the acceptable $id values.
*/
- private MessageSource messageSource;
+ private JsonSchemaIdValidator schemaIdValidator = JsonSchemaIdValidator.DEFAULT;
+
+ /**
+ * Contains a mapping of how strict a keyword's validators should be.
+ * Defaults to {@literal true}.
+ *
+ * Each validator has its own understanding of what constitutes strict
+ * and permissive.
+ */
+ private final Map strictness;
/**
- * Since Draft 2019-09 format assertions are not enabled by default.
+ * when validate type, if TYPE_LOOSE = true, will try to convert string to
+ * different types to match the type defined in schema.
*/
- private Boolean formatAssertionsEnabled = null;
+ private boolean typeLoose;
- /************************ START OF UNEVALUATED CHECKS **********************************/
+ /**
+ * When set to true assumes that schema is used to to validate outgoing data from an API.
+ */
+ private Boolean writeOnly = null;
@Deprecated
- public SchemaValidatorsConfig disableUnevaluatedAnalysis() {
- return this;
+ public SchemaValidatorsConfig() {
+ this.strictness = new HashMap<>(0);
+
+ this.keywordWalkListenersMap = new HashMap<>();
+ this.propertyWalkListeners = new ArrayList<>();
+ this.itemWalkListeners = new ArrayList<>();
+ }
+
+ SchemaValidatorsConfig(ApplyDefaultsStrategy applyDefaultsStrategy, boolean cacheRefs,
+ boolean customMessageSupported, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
+ Boolean formatAssertionsEnabled, boolean handleNullableField,
+ List itemWalkListeners, boolean javaSemantics,
+ Map> keywordWalkListenersMap, Locale locale, boolean losslessNarrowing,
+ MessageSource messageSource, boolean openAPI3StyleDiscriminators, PathType pathType,
+ boolean preloadJsonSchema, int preloadJsonSchemaRefMaxNestingDepth,
+ List propertyWalkListeners, Boolean readOnly,
+ RegularExpressionFactory regularExpressionFactory, JsonSchemaIdValidator schemaIdValidator,
+ Map strictness, boolean typeLoose, Boolean writeOnly) {
+ super();
+ this.applyDefaultsStrategy = applyDefaultsStrategy;
+ this.cacheRefs = cacheRefs;
+ this.customMessageSupported = customMessageSupported;
+ this.executionContextCustomizer = executionContextCustomizer;
+ this.failFast = failFast;
+ this.formatAssertionsEnabled = formatAssertionsEnabled;
+ this.handleNullableField = handleNullableField;
+ this.itemWalkListeners = itemWalkListeners;
+ this.javaSemantics = javaSemantics;
+ this.keywordWalkListenersMap = keywordWalkListenersMap;
+ this.locale = locale;
+ this.losslessNarrowing = losslessNarrowing;
+ this.messageSource = messageSource;
+ this.openAPI3StyleDiscriminators = openAPI3StyleDiscriminators;
+ this.pathType = pathType;
+ this.preloadJsonSchema = preloadJsonSchema;
+ this.preloadJsonSchemaRefMaxNestingDepth = preloadJsonSchemaRefMaxNestingDepth;
+ this.propertyWalkListeners = propertyWalkListeners;
+ this.readOnly = readOnly;
+ this.regularExpressionFactory = regularExpressionFactory;
+ this.schemaIdValidator = schemaIdValidator;
+ this.strictness = strictness;
+ this.typeLoose = typeLoose;
+ this.writeOnly = writeOnly;
}
- @Deprecated
- public SchemaValidatorsConfig disableUnevaluatedItems() {
- return this;
+ public void addItemWalkListener(JsonSchemaWalkListener itemWalkListener) {
+ this.itemWalkListeners.add(itemWalkListener);
}
- @Deprecated
- public SchemaValidatorsConfig disableUnevaluatedProperties() {
- return this;
+ public void addItemWalkListeners(List itemWalkListeners) {
+ this.itemWalkListeners.addAll(itemWalkListeners);
}
- @Deprecated
- public SchemaValidatorsConfig enableUnevaluatedAnalysis() {
- return this;
+ public void addKeywordWalkListener(JsonSchemaWalkListener keywordWalkListener) {
+ if (this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) {
+ List keywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, keywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).add(keywordWalkListener);
}
- @Deprecated
- public SchemaValidatorsConfig enableUnevaluatedItems() {
- return this;
+ public void addKeywordWalkListener(String keyword, JsonSchemaWalkListener keywordWalkListener) {
+ if (this.keywordWalkListenersMap.get(keyword) == null) {
+ List keywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(keyword, keywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(keyword).add(keywordWalkListener);
}
- @Deprecated
- public SchemaValidatorsConfig enableUnevaluatedProperties() {
- return this;
+ public void addKeywordWalkListeners(List keywordWalkListeners) {
+ if (this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) {
+ List ikeywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, ikeywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).addAll(keywordWalkListeners);
}
- @Deprecated
- public boolean isUnevaluatedItemsAnalysisDisabled() {
- return false;
+ public void addKeywordWalkListeners(String keyword, List keywordWalkListeners) {
+ if (this.keywordWalkListenersMap.get(keyword) == null) {
+ List ikeywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(keyword, ikeywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(keyword).addAll(keywordWalkListeners);
}
- @Deprecated
- public boolean isUnevaluatedItemsAnalysisEnabled() {
- return !isUnevaluatedItemsAnalysisDisabled();
+ public void addPropertyWalkListener(JsonSchemaWalkListener propertyWalkListener) {
+ this.propertyWalkListeners.add(propertyWalkListener);
}
- @Deprecated
- public boolean isUnevaluatedPropertiesAnalysisDisabled() {
- return false;
+ public void addPropertyWalkListeners(List propertyWalkListeners) {
+ this.propertyWalkListeners.addAll(propertyWalkListeners);
}
- @Deprecated
- public boolean isUnevaluatedPropertiesAnalysisEnabled() {
- return !isUnevaluatedPropertiesAnalysisDisabled();
+ public ApplyDefaultsStrategy getApplyDefaultsStrategy() {
+ return this.applyDefaultsStrategy;
}
- /************************ END OF UNEVALUATED CHECKS **********************************/
-
- /**
- *
- * @return true if type loose is used.
- */
- public boolean isTypeLoose() {
- return this.typeLoose;
+ public List getArrayItemWalkListeners() {
+ return this.itemWalkListeners;
}
- public void setTypeLoose(boolean typeLoose) {
- this.typeLoose = typeLoose;
+ public ExecutionContextCustomizer getExecutionContextCustomizer() {
+ return this.executionContextCustomizer;
}
/**
- * When enabled,
- * {@link JsonValidator#validate(ExecutionContext, JsonNode, JsonNode, JsonNodePath)}
- * doesn't return any {@link java.util.Set}<{@link ValidationMessage}>,
- * instead a {@link JsonSchemaException} is thrown as soon as a validation
- * errors is discovered.
- *
- * @param failFast boolean
+ * Gets the format assertion enabled flag.
+ *
+ * This defaults to null meaning that it will follow the defaults of the
+ * specification.
+ *
+ * Since draft 2019-09 this will default to false unless enabled by using the
+ * $vocabulary keyword.
+ *
+ * @return the format assertions enabled flag
*/
- public void setFailFast(final boolean failFast) {
- this.failFast = failFast;
- }
-
- public boolean isFailFast() {
- return this.failFast;
+ public Boolean getFormatAssertionsEnabled() {
+ return formatAssertionsEnabled;
}
- public void setApplyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) {
- this.applyDefaultsStrategy = applyDefaultsStrategy != null ? applyDefaultsStrategy
- : ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
+ WalkListenerRunner getItemWalkListenerRunner() {
+ return this.itemWalkListenerRunner;
}
- public ApplyDefaultsStrategy getApplyDefaultsStrategy() {
- return this.applyDefaultsStrategy;
+ WalkListenerRunner getKeywordWalkListenerRunner() {
+ return this.keywordWalkListenerRunner;
}
- public boolean isHandleNullableField() {
- return this.handleNullableField;
+ public Map> getKeywordWalkListenersMap() {
+ return this.keywordWalkListenersMap;
}
- public void setHandleNullableField(boolean handleNullableField) {
- this.handleNullableField = handleNullableField;
+ /**
+ * Get the locale to consider when generating localised messages (default is the
+ * JVM default).
+ *
+ * This locale is on a schema basis and will be used as the default locale for
+ * {@link com.networknt.schema.ExecutionConfig}.
+ *
+ * @return The locale.
+ */
+ public Locale getLocale() {
+ if (this.locale == null) {
+ // This should not be cached as it can be changed using Locale#setDefault(Locale)
+ return Locale.getDefault();
+ }
+ return this.locale;
}
/**
- * Gets whether to use a ECMA-262 compliant regular expression validator.
- *
- * This defaults to the false and setting true require inclusion of optional
- * org.jruby.joni:joni or org.graalvm.js:js dependencies.
+ * Get the message source to use for generating localised messages.
+ *
+ * @return the message source
+ */
+ public MessageSource getMessageSource() {
+ if (this.messageSource == null) {
+ return DefaultMessageSource.getInstance();
+ }
+ return this.messageSource;
+ }
+
+ /**
+ * Get the approach used to generate paths in messages, logs and errors.
*
- * @return true if ECMA-262 compliant
+ * @return The path generation approach.
*/
- public boolean isEcma262Validator() {
- return !(this.regularExpressionFactory instanceof JDKRegularExpressionFactory);
+ public PathType getPathType() {
+ return this.pathType;
}
/**
- * Sets whether to use a ECMA-262 compliant regular expression validator.
- *
- * This defaults to the false and setting true require inclusion of optional
- * org.jruby.joni:joni or org.graalvm.js:js dependencies.
+ * Gets the max depth of the evaluation path to preload when preloading refs.
*
- * @param ecma262Validator true if ECMA-262 compliant
+ * @return the max depth to preload
*/
- public void setEcma262Validator(boolean ecma262Validator) {
- this.regularExpressionFactory = ecma262Validator ? ECMAScriptRegularExpressionFactory.getInstance()
- : JDKRegularExpressionFactory.getInstance();
+ public int getPreloadJsonSchemaRefMaxNestingDepth() {
+ return preloadJsonSchemaRefMaxNestingDepth;
+ }
+
+ WalkListenerRunner getPropertyWalkListenerRunner() {
+ return this.propertyWalkListenerRunner;
+ }
+
+ public List getPropertyWalkListeners() {
+ return this.propertyWalkListeners;
}
/**
@@ -311,142 +377,214 @@ public RegularExpressionFactory getRegularExpressionFactory() {
}
/**
- * Sets the regular expression factory.
- *
- * This defaults to the JDKRegularExpressionFactory and the implementations
- * require inclusion of optional org.jruby.joni:joni or org.graalvm.js:js dependencies.
- *
- * @see JDKRegularExpressionFactory
- * @see ECMAScriptRegularExpressionFactory
- * @param regularExpressionFactory the factory
+ * Gets the schema id validator to validate $id.
+ *
+ * @return the validator
*/
- public void setRegularExpressionFactory(RegularExpressionFactory regularExpressionFactory) {
- this.regularExpressionFactory = regularExpressionFactory;
- }
-
- public boolean isJavaSemantics() {
- return this.javaSemantics;
+ public JsonSchemaIdValidator getSchemaIdValidator() {
+ return schemaIdValidator;
}
- public void setJavaSemantics(boolean javaSemantics) {
- this.javaSemantics = javaSemantics;
+ /**
+ * Gets if schemas loaded from refs will be cached and reused for subsequent
+ * runs.
+ *
+ * @return true if schemas loaded from refs should be cached
+ */
+ public boolean isCacheRefs() {
+ return cacheRefs;
}
public boolean isCustomMessageSupported() {
return customMessageSupported;
}
- public void setCustomMessageSupported(boolean customMessageSupported) {
- this.customMessageSupported = customMessageSupported;
- }
-
- public void addKeywordWalkListener(JsonSchemaWalkListener keywordWalkListener) {
- if (this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) {
- List keywordWalkListeners = new ArrayList<>();
- this.keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, keywordWalkListeners);
- }
- this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).add(keywordWalkListener);
+ /**
+ * Gets whether to use a ECMA-262 compliant regular expression validator.
+ *
+ * This defaults to the false and setting true require inclusion of optional
+ * org.jruby.joni:joni or org.graalvm.js:js dependencies.
+ *
+ * @return true if ECMA-262 compliant
+ */
+ public boolean isEcma262Validator() {
+ return !(this.regularExpressionFactory instanceof JDKRegularExpressionFactory);
}
-
- public void addKeywordWalkListener(String keyword, JsonSchemaWalkListener keywordWalkListener) {
- if (this.keywordWalkListenersMap.get(keyword) == null) {
- List keywordWalkListeners = new ArrayList<>();
- this.keywordWalkListenersMap.put(keyword, keywordWalkListeners);
- }
- this.keywordWalkListenersMap.get(keyword).add(keywordWalkListener);
+
+ public boolean isFailFast() {
+ return this.failFast;
}
- public void addKeywordWalkListeners(List keywordWalkListeners) {
- if (this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) {
- List ikeywordWalkListeners = new ArrayList<>();
- this.keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, ikeywordWalkListeners);
- }
- this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).addAll(keywordWalkListeners);
+ public boolean isHandleNullableField() {
+ return this.handleNullableField;
}
- public void addKeywordWalkListeners(String keyword, List keywordWalkListeners) {
- if (this.keywordWalkListenersMap.get(keyword) == null) {
- List ikeywordWalkListeners = new ArrayList<>();
- this.keywordWalkListenersMap.put(keyword, ikeywordWalkListeners);
- }
- this.keywordWalkListenersMap.get(keyword).addAll(keywordWalkListeners);
+ public boolean isJavaSemantics() {
+ return this.javaSemantics;
}
- public void addPropertyWalkListeners(List propertyWalkListeners) {
- this.propertyWalkListeners.addAll(propertyWalkListeners);
+ public boolean isLosslessNarrowing() {
+ return this.losslessNarrowing;
}
- public void addPropertyWalkListener(JsonSchemaWalkListener propertyWalkListener) {
- this.propertyWalkListeners.add(propertyWalkListener);
+ /**
+ * Indicates whether OpenAPI 3 style discriminators should be supported
+ *
+ * @return true in case discriminators are enabled
+ * @since 1.0.51
+ */
+ public boolean isOpenAPI3StyleDiscriminators() {
+ return this.openAPI3StyleDiscriminators;
}
- public void addItemWalkListener(JsonSchemaWalkListener itemWalkListener) {
- this.itemWalkListeners.add(itemWalkListener);
+ /**
+ * Gets if the schema should be preloaded.
+ *
+ * @return true if it should be preloaded
+ */
+ public boolean isPreloadJsonSchema() {
+ return preloadJsonSchema;
}
- public void addItemWalkListeners(List itemWalkListeners) {
- this.itemWalkListeners.addAll(itemWalkListeners);
+ public boolean isReadOnly() {
+ return null != this.readOnly && this.readOnly;
}
- public List getPropertyWalkListeners() {
- return this.propertyWalkListeners;
+ /**
+ * Answers whether a keyword's validators may relax their analysis. The
+ * default is to perform strict checking. One must explicitly allow a
+ * validator to be more permissive.
+ *
+ * Each validator has its own understanding of what is permissive and
+ * strict. Consult the keyword's documentation for details.
+ *
+ * @param keyword the keyword to adjust (not null)
+ * @return Whether to perform a strict validation.
+ */
+ public boolean isStrict(String keyword) {
+ return isStrict(keyword, Boolean.TRUE);
}
- public Map> getKeywordWalkListenersMap() {
- return this.keywordWalkListenersMap;
+ /**
+ * Determines if the validator should perform strict checking.
+ *
+ * @param keyword the keyword
+ * @param defaultValue the default value
+ * @return whether to perform a strict validation
+ */
+ public boolean isStrict(String keyword, Boolean defaultValue) {
+ return this.strictness.getOrDefault(Objects.requireNonNull(keyword, "keyword cannot be null"), defaultValue);
}
- public List getArrayItemWalkListeners() {
- return this.itemWalkListeners;
+ /**
+ *
+ * @return true if type loose is used.
+ */
+ public boolean isTypeLoose() {
+ return this.typeLoose;
}
- private final WalkListenerRunner itemWalkListenerRunner = new DefaultItemWalkListenerRunner(getArrayItemWalkListeners());
-
- WalkListenerRunner getItemWalkListenerRunner() {
- return this.itemWalkListenerRunner;
+ public boolean isWriteOnly() {
+ return null != this.writeOnly && this.writeOnly;
}
-
- private final WalkListenerRunner keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(getKeywordWalkListenersMap());
- WalkListenerRunner getKeywordWalkListenerRunner() {
- return this.keywordWalkListenerRunner;
+ public void setApplyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) {
+ this.applyDefaultsStrategy = applyDefaultsStrategy != null ? applyDefaultsStrategy
+ : ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
}
- private final WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(getPropertyWalkListeners());
-
- WalkListenerRunner getPropertyWalkListenerRunner() {
- return this.propertyWalkListenerRunner;
+ /**
+ * Sets if schemas loaded from refs will be cached and reused for subsequent
+ * runs.
+ *
+ * Note that setting this to false will affect performance as refs will need to
+ * be repeatedly resolved for each evaluation run. It may be needed to be set to
+ * false if there are multiple nested applicators like anyOf, oneOf and allOf as
+ * that will consume a lot of memory to cache all the permutations.
+ *
+ * @param cacheRefs true to cache
+ */
+ public void setCacheRefs(boolean cacheRefs) {
+ this.cacheRefs = cacheRefs;
}
- public SchemaValidatorsConfig() {
+ public void setCustomMessageSupported(boolean customMessageSupported) {
+ this.customMessageSupported = customMessageSupported;
}
- public ExecutionContextCustomizer getExecutionContextCustomizer() {
- return this.executionContextCustomizer;
+ /**
+ * Sets whether to use a ECMA-262 compliant regular expression validator.
+ *
+ * This defaults to the false and setting true require inclusion of optional
+ * org.jruby.joni:joni or org.graalvm.js:js dependencies.
+ *
+ * @param ecma262Validator true if ECMA-262 compliant
+ */
+ public void setEcma262Validator(boolean ecma262Validator) {
+ this.regularExpressionFactory = ecma262Validator ? ECMAScriptRegularExpressionFactory.getInstance()
+ : JDKRegularExpressionFactory.getInstance();
}
public void setExecutionContextCustomizer(ExecutionContextCustomizer executionContextCustomizer) {
this.executionContextCustomizer = executionContextCustomizer;
}
- public boolean isLosslessNarrowing() {
- return this.losslessNarrowing;
+ /**
+ * When enabled,
+ * {@link JsonValidator#validate(ExecutionContext, JsonNode, JsonNode, JsonNodePath)}
+ * doesn't return any {@link java.util.Set}<{@link ValidationMessage}>,
+ * instead a {@link JsonSchemaException} is thrown as soon as a validation
+ * errors is discovered.
+ *
+ * @param failFast boolean
+ */
+ public void setFailFast(final boolean failFast) {
+ this.failFast = failFast;
}
- public void setLosslessNarrowing(boolean losslessNarrowing) {
- this.losslessNarrowing = losslessNarrowing;
+ /**
+ * Sets the format assertion enabled flag.
+ *
+ * @param formatAssertionsEnabled the format assertions enabled flag
+ */
+ public void setFormatAssertionsEnabled(Boolean formatAssertionsEnabled) {
+ this.formatAssertionsEnabled = formatAssertionsEnabled;
+ }
+
+ public void setHandleNullableField(boolean handleNullableField) {
+ this.handleNullableField = handleNullableField;
+ }
+
+ public void setJavaSemantics(boolean javaSemantics) {
+ this.javaSemantics = javaSemantics;
}
/**
- * Indicates whether OpenAPI 3 style discriminators should be supported
- *
- * @return true in case discriminators are enabled
- * @since 1.0.51
+ * Set the locale to consider when generating localised messages.
+ *
+ * Note that this locale is set on a schema basis. To configure the schema on a
+ * per execution basis use
+ * {@link com.networknt.schema.ExecutionConfig#setLocale(Locale)}.
+ *
+ * @param locale The locale.
*/
- public boolean isOpenAPI3StyleDiscriminators() {
- return this.openAPI3StyleDiscriminators;
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ public void setLosslessNarrowing(boolean losslessNarrowing) {
+ this.losslessNarrowing = losslessNarrowing;
}
+ /**
+ * Set the message source to use for generating localised messages.
+ *
+ * @param messageSource the message source
+ */
+ public void setMessageSource(MessageSource messageSource) {
+ this.messageSource = messageSource;
+ }
+
/**
* When enabled, the validation of anyOf and allOf in
* polymorphism will respect OpenAPI 3 style discriminators as described in the
@@ -485,94 +623,57 @@ public void setOpenAPI3StyleDiscriminators(boolean openAPI3StyleDiscriminators)
}
/**
- * Sets if collectors are to be loaded.
- *
- * This is deprecated in favor of the caller calling {@link CollectorContext#loadCollectors()} manually.
- *
- * @param loadCollectors to load collectors
+ * Set the approach used to generate paths in messages, logs and errors (default is PathType.LEGACY).
+ *
+ * @param pathType The path generation approach.
*/
- @Deprecated
- public void setLoadCollectors(boolean loadCollectors) {
- this.loadCollectors = loadCollectors;
+ public void setPathType(PathType pathType) {
+ this.pathType = pathType;
}
/**
- * Gets if collectors are to be loaded.
+ * Sets if the schema should be preloaded.
*
- * @return if collectors are to be loader
- */
- @Deprecated
- public boolean doLoadCollectors() {
- return this.loadCollectors;
- }
-
- public boolean isReadOnly() {
- return null != this.readOnly && this.readOnly;
- }
-
- public void setReadOnly(boolean readOnly) {
- this.readOnly = readOnly;
- }
-
- public boolean isWriteOnly() {
- return null != this.writeOnly && this.writeOnly;
- }
-
- public void setWriteOnly(boolean writeOnly) {
- this.writeOnly = writeOnly;
- }
-
- /**
- * Use {@code isReadOnly} or {@code isWriteOnly}
- * @return true if schema is used to write data
+ * @param preloadJsonSchema true to preload
*/
- @Deprecated
- public boolean isWriteMode() {
- return null == this.writeOnly || this.writeOnly;
+ public void setPreloadJsonSchema(boolean preloadJsonSchema) {
+ this.preloadJsonSchema = preloadJsonSchema;
}
/**
- * Set the approach used to generate paths in messages, logs and errors (default is PathType.LEGACY).
+ * Sets the max depth of the evaluation path to preload when preloading refs.
*
- * @param pathType The path generation approach.
+ * @param preloadJsonSchemaRefMaxNestingDepth the max depth to preload
*/
- public void setPathType(PathType pathType) {
- this.pathType = pathType;
+ public void setPreloadJsonSchemaRefMaxNestingDepth(int preloadJsonSchemaRefMaxNestingDepth) {
+ this.preloadJsonSchemaRefMaxNestingDepth = preloadJsonSchemaRefMaxNestingDepth;
}
- /**
- * Get the approach used to generate paths in messages, logs and errors.
- *
- * @return The path generation approach.
- */
- public PathType getPathType() {
- return this.pathType;
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
}
/**
- * Answers whether a keyword's validators may relax their analysis. The
- * default is to perform strict checking. One must explicitly allow a
- * validator to be more permissive.
+ * Sets the regular expression factory.
*
- * Each validator has its own understanding of what is permissive and
- * strict. Consult the keyword's documentation for details.
- *
- * @param keyword the keyword to adjust (not null)
- * @return Whether to perform a strict validation.
+ * This defaults to the JDKRegularExpressionFactory and the implementations
+ * require inclusion of optional org.jruby.joni:joni or org.graalvm.js:js dependencies.
+ *
+ * @see JDKRegularExpressionFactory
+ * @see ECMAScriptRegularExpressionFactory
+ * @param regularExpressionFactory the factory
*/
- public boolean isStrict(String keyword) {
- return isStrict(keyword, Boolean.TRUE);
+ public void setRegularExpressionFactory(RegularExpressionFactory regularExpressionFactory) {
+ this.regularExpressionFactory = regularExpressionFactory;
}
/**
- * Determines if the validator should perform strict checking.
- *
- * @param keyword the keyword
- * @param defaultValue the default value
- * @return whether to perform a strict validation
+ * Sets the schema id validator to validate $id.
+ *
+ * @param schemaIdValidator the validator
*/
- public boolean isStrict(String keyword, Boolean defaultValue) {
- return this.strictness.getOrDefault(Objects.requireNonNull(keyword, "keyword cannot be null"), defaultValue);
+ public void setSchemaIdValidator(JsonSchemaIdValidator schemaIdValidator) {
+ this.schemaIdValidator = schemaIdValidator;
}
/**
@@ -587,157 +688,613 @@ public void setStrict(String keyword, boolean strict) {
this.strictness.put(Objects.requireNonNull(keyword, "keyword cannot be null"), strict);
}
+ public void setTypeLoose(boolean typeLoose) {
+ this.typeLoose = typeLoose;
+ }
+
+ public void setWriteOnly(boolean writeOnly) {
+ this.writeOnly = writeOnly;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
/**
- * Get the locale to consider when generating localised messages (default is the
- * JVM default).
- *
- * This locale is on a schema basis and will be used as the default locale for
- * {@link com.networknt.schema.ExecutionConfig}.
- *
- * @return The locale.
+ * Builder for {@link SchemaValidatorsConfig}.
*/
- public Locale getLocale() {
- if (this.locale == null) {
- // This should not be cached as it can be changed using Locale#setDefault(Locale)
- return Locale.getDefault();
+ public static class Builder {
+ private ApplyDefaultsStrategy applyDefaultsStrategy = ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
+ private boolean cacheRefs = true;
+ private boolean customMessageEnabled = false;
+ private ExecutionContextCustomizer executionContextCustomizer = null;
+ private boolean failFast = false;
+ private Boolean formatAssertionsEnabled = false;
+ private boolean handleNullableField = false;
+ private List itemWalkListeners = new ArrayList<>();
+ private boolean javaSemantics = false;
+ private Map> keywordWalkListeners = new HashMap<>();
+ private Locale locale = null; // This must be null to use Locale.getDefault() as the default can be changed
+ private boolean losslessNarrowing = false;
+ private MessageSource messageSource = null;
+ private boolean openAPI3StyleDiscriminators = false;
+ private PathType pathType = PathType.JSON_POINTER;
+ private boolean preloadJsonSchema = true;
+ private int preloadJsonSchemaRefMaxNestingDepth = DEFAULT_PRELOAD_JSON_SCHEMA_REF_MAX_NESTING_DEPTH;
+ private List propertyWalkListeners = new ArrayList<>();
+ private Boolean readOnly = null;
+ private RegularExpressionFactory regularExpressionFactory = JDKRegularExpressionFactory.getInstance();
+ private JsonSchemaIdValidator schemaIdValidator = JsonSchemaIdValidator.DEFAULT;
+ private Map strictness = new HashMap<>(0);
+ private boolean typeLoose = false;
+ private Boolean writeOnly = null;
+
+ /**
+ * Sets the strategy the walker uses to sets nodes to the default value.
+ *
+ * Defaults to {@link ApplyDefaultsStrategy#EMPTY_APPLY_DEFAULTS_STRATEGY}.
+ *
+ * @param applyDefaultsStrategy the strategy
+ * @return the builder
+ */
+ public Builder applyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) {
+ this.applyDefaultsStrategy = applyDefaultsStrategy;
+ return this;
+ }
+ /**
+ * Sets if schemas loaded from refs will be cached and reused for subsequent runs.
+ *
+ * Defaults to true.
+ *
+ * @param cacheRefs true to cache
+ * @return the builder
+ */
+ public Builder cacheRefs(boolean cacheRefs) {
+ this.cacheRefs = cacheRefs;
+ return this;
+ }
+ /**
+ * Sets if the message keyword for setting custom messages in the schema is enabled.
+ *
+ * Defaults to false.
+ *
+ * @param messageKeywordEnabled true to enable
+ * @return the builder
+ */
+ public Builder messageKeywordEnabled(boolean messageKeywordEnabled) {
+ this.customMessageEnabled = messageKeywordEnabled;
+ return this;
+ }
+ /**
+ * Sets the execution context customizer that is run before each run.
+ *
+ * @param executionContextCustomizer the customizer
+ * @return the builder
+ */
+ public Builder executionContextCustomizer(ExecutionContextCustomizer executionContextCustomizer) {
+ this.executionContextCustomizer = executionContextCustomizer;
+ return this;
+ }
+
+ /**
+ * Sets if the validation should immediately return once a validation error has
+ * occurred. This can improve performance if inputs are invalid but cannot
+ * return all error messages to the caller.
+ *
+ * Defaults to false.
+ *
+ * @param failFast true to enable
+ * @return the builder
+ */
+ public Builder failFast(boolean failFast) {
+ this.failFast = failFast;
+ return this;
+ }
+
+ /**
+ * Sets if format assertions are enabled. If format assertions are not enabled
+ * the format keyword will behave like a annotation and not attempt to validate
+ * if the inputs are valid.
+ *
+ * Defaults to not enabling format assertions for Draft 2019-09 and above and
+ * enabling format assertions for Draft 7 and below.
+ *
+ * @param formatAssertionsEnabled true to enable
+ * @return the builder
+ */
+ public Builder formatAssertionsEnabled(Boolean formatAssertionsEnabled) {
+ this.formatAssertionsEnabled = formatAssertionsEnabled;
+ return this;
+ }
+
+ /**
+ * Sets if the nullable keyword is enabled.
+ *
+ * @param nullableKeywordEnabled true to enable
+ * @return the builder
+ */
+ public Builder nullableKeywordEnabled(boolean nullableKeywordEnabled) {
+ this.handleNullableField = nullableKeywordEnabled;
+ return this;
+ }
+ public Builder itemWalkListeners(List itemWalkListeners) {
+ this.itemWalkListeners = itemWalkListeners;
+ return this;
+ }
+ public Builder javaSemantics(boolean javaSemantics) {
+ this.javaSemantics = javaSemantics;
+ return this;
+ }
+ public Builder keywordWalkListeners(Map> keywordWalkListeners) {
+ this.keywordWalkListeners = keywordWalkListeners;
+ return this;
+ }
+ /**
+ * Set the locale to consider when generating localised messages.
+ *
+ * Note that this locale is set on a schema basis. To configure the schema on a
+ * per execution basis use
+ * {@link com.networknt.schema.ExecutionConfig#setLocale(Locale)}.
+ *
+ * Defaults to use {@link Locale#getDefault()}.
+ *
+ * @param locale The locale.
+ */
+ public Builder locale(Locale locale) {
+ this.locale = locale;
+ return this;
+ }
+ public Builder losslessNarrowing(boolean losslessNarrowing) {
+ this.losslessNarrowing = losslessNarrowing;
+ return this;
+ }
+ /**
+ * Sets the message source to use for generating localised messages.
+ *
+ * @param messageSource the message source
+ * @return the builder
+ */
+ public Builder messageSource(MessageSource messageSource) {
+ this.messageSource = messageSource;
+ return this;
+ }
+ /**
+ * Sets if the discriminator keyword is enabled.
+ *
+ * Defaults to false.
+ *
+ * @param discriminatorKeywordEnabled true to enable
+ * @return the builder
+ */
+ public Builder discriminatorKeywordEnabled(boolean discriminatorKeywordEnabled) {
+ this.openAPI3StyleDiscriminators = discriminatorKeywordEnabled;
+ return this;
+ }
+ /**
+ * Sets the path type to use when reporting the instance location of errors.
+ *
+ * Defaults to {@link PathType#JSON_POINTER}.
+ *
+ * @param pathType the path type
+ * @return the path type
+ */
+ public Builder pathType(PathType pathType) {
+ this.pathType = pathType;
+ return this;
+ }
+ /**
+ * Sets if the schema should be preloaded.
+ *
+ * Defaults to true.
+ *
+ * @param preloadJsonSchema true to preload
+ * @return the builder
+ */
+ public Builder preloadJsonSchema(boolean preloadJsonSchema) {
+ this.preloadJsonSchema = preloadJsonSchema;
+ return this;
+ }
+ /**
+ * Sets the max depth of the evaluation path to preload when preloading refs.
+ *
+ * Defaults to 40.
+ *
+ * @param preloadJsonSchemaRefMaxNestingDepth to preload
+ * @return the builder
+ */
+ public Builder preloadJsonSchemaRefMaxNestingDepth(int preloadJsonSchemaRefMaxNestingDepth) {
+ this.preloadJsonSchemaRefMaxNestingDepth = preloadJsonSchemaRefMaxNestingDepth;
+ return this;
+ }
+ public Builder propertyWalkListeners(List propertyWalkListeners) {
+ this.propertyWalkListeners = propertyWalkListeners;
+ return this;
+ }
+ public Builder readOnly(Boolean readOnly) {
+ this.readOnly = readOnly;
+ return this;
+ }
+ /**
+ * Sets the regular expression factory.
+ *
+ * Defaults to the {@link JDKRegularExpressionFactory}
+ *
+ * The {@link ECMAScriptRegularExpressionFactory} requires the inclusion of
+ * optional org.jruby.joni:joni or org.graalvm.js:js dependencies.
+ *
+ * @see JDKRegularExpressionFactory
+ * @see ECMAScriptRegularExpressionFactory
+ * @param regularExpressionFactory the factory
+ * @return the builder
+ */
+ public Builder regularExpressionFactory(RegularExpressionFactory regularExpressionFactory) {
+ this.regularExpressionFactory = regularExpressionFactory;
+ return this;
+ }
+ /**
+ * Sets the schema id validator to use.
+ *
+ * Defaults to {@link JsonSchemaIdValidator#DEFAULT}.
+ *
+ * @param schemaIdValidator the builder
+ * @return the builder
+ */
+ public Builder schemaIdValidator(JsonSchemaIdValidator schemaIdValidator) {
+ this.schemaIdValidator = schemaIdValidator;
+ return this;
+ }
+ public Builder strict(Map strict) {
+ this.strictness = strict;
+ return this;
+ }
+ public Builder typeLoose(boolean typeLoose) {
+ this.typeLoose = typeLoose;
+ return this;
+ }
+ public Builder writeOnly(Boolean writeOnly) {
+ this.writeOnly = writeOnly;
+ return this;
+ }
+ public SchemaValidatorsConfig build() {
+ return new ImmutableSchemaValidatorsConfig(applyDefaultsStrategy, cacheRefs, customMessageEnabled,
+ executionContextCustomizer, failFast, formatAssertionsEnabled, handleNullableField,
+ itemWalkListeners, javaSemantics, keywordWalkListeners, locale, losslessNarrowing, messageSource,
+ openAPI3StyleDiscriminators, pathType, preloadJsonSchema, preloadJsonSchemaRefMaxNestingDepth,
+ propertyWalkListeners, readOnly, regularExpressionFactory, schemaIdValidator, strictness, typeLoose,
+ writeOnly);
+ }
+ public Builder strict(String keyword, boolean strict) {
+ this.strictness.put(Objects.requireNonNull(keyword, "keyword cannot be null"), strict);
+ return this;
}
- return this.locale;
}
/**
- * Set the locale to consider when generating localised messages.
+ * {@link SchemaValidatorsConfig} that throws on mutators or deprecated methods.
*
- * Note that this locale is set on a schema basis. To configure the schema on a
- * per execution basis use
- * {@link com.networknt.schema.ExecutionConfig#setLocale(Locale)}.
- *
- * @param locale The locale.
+ * The {@link SchemaValidatorsConfig} will be made immutable in a future breaking release.
*/
- public void setLocale(Locale locale) {
- this.locale = locale;
- }
+ public static class ImmutableSchemaValidatorsConfig extends SchemaValidatorsConfig {
+ public ImmutableSchemaValidatorsConfig(ApplyDefaultsStrategy applyDefaultsStrategy, boolean cacheRefs,
+ boolean customMessageSupported, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
+ Boolean formatAssertionsEnabled, boolean handleNullableField,
+ List itemWalkListeners, boolean javaSemantics,
+ Map> keywordWalkListenersMap, Locale locale,
+ boolean losslessNarrowing, MessageSource messageSource, boolean openAPI3StyleDiscriminators,
+ PathType pathType, boolean preloadJsonSchema, int preloadJsonSchemaRefMaxNestingDepth,
+ List propertyWalkListeners, Boolean readOnly,
+ RegularExpressionFactory regularExpressionFactory, JsonSchemaIdValidator schemaIdValidator,
+ Map strictness, boolean typeLoose, Boolean writeOnly) {
+ super(applyDefaultsStrategy, cacheRefs, customMessageSupported, executionContextCustomizer, failFast,
+ formatAssertionsEnabled, handleNullableField, itemWalkListeners, javaSemantics, keywordWalkListenersMap, locale,
+ losslessNarrowing, messageSource, openAPI3StyleDiscriminators, pathType, preloadJsonSchema,
+ preloadJsonSchemaRefMaxNestingDepth, propertyWalkListeners, readOnly, regularExpressionFactory,
+ schemaIdValidator, strictness, typeLoose, writeOnly);
+ }
- /**
- * Get the message source to use for generating localised messages.
- *
- * @return the message source
- */
- public MessageSource getMessageSource() {
- if (this.messageSource == null) {
- return DefaultMessageSource.getInstance();
+ @Override
+ public void addItemWalkListener(JsonSchemaWalkListener itemWalkListener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addItemWalkListeners(List itemWalkListeners) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addKeywordWalkListener(JsonSchemaWalkListener keywordWalkListener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addKeywordWalkListener(String keyword, JsonSchemaWalkListener keywordWalkListener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addKeywordWalkListeners(List keywordWalkListeners) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addKeywordWalkListeners(String keyword, List keywordWalkListeners) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addPropertyWalkListener(JsonSchemaWalkListener propertyWalkListener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addPropertyWalkListeners(List propertyWalkListeners) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isWriteOnly() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setApplyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setCacheRefs(boolean cacheRefs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setCustomMessageSupported(boolean customMessageSupported) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setEcma262Validator(boolean ecma262Validator) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setExecutionContextCustomizer(ExecutionContextCustomizer executionContextCustomizer) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFailFast(boolean failFast) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFormatAssertionsEnabled(Boolean formatAssertionsEnabled) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setHandleNullableField(boolean handleNullableField) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setJavaSemantics(boolean javaSemantics) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setLocale(Locale locale) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setLosslessNarrowing(boolean losslessNarrowing) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMessageSource(MessageSource messageSource) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setOpenAPI3StyleDiscriminators(boolean openAPI3StyleDiscriminators) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPathType(PathType pathType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreloadJsonSchema(boolean preloadJsonSchema) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreloadJsonSchemaRefMaxNestingDepth(int preloadJsonSchemaRefMaxNestingDepth) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setRegularExpressionFactory(RegularExpressionFactory regularExpressionFactory) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSchemaIdValidator(JsonSchemaIdValidator schemaIdValidator) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setStrict(String keyword, boolean strict) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setTypeLoose(boolean typeLoose) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setWriteOnly(boolean writeOnly) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isWriteMode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setLoadCollectors(boolean loadCollectors) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean doLoadCollectors() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SchemaValidatorsConfig disableUnevaluatedAnalysis() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SchemaValidatorsConfig disableUnevaluatedItems() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SchemaValidatorsConfig disableUnevaluatedProperties() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SchemaValidatorsConfig enableUnevaluatedAnalysis() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SchemaValidatorsConfig enableUnevaluatedItems() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SchemaValidatorsConfig enableUnevaluatedProperties() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUnevaluatedItemsAnalysisDisabled() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUnevaluatedItemsAnalysisEnabled() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUnevaluatedPropertiesAnalysisDisabled() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUnevaluatedPropertiesAnalysisEnabled() {
+ throw new UnsupportedOperationException();
}
- return this.messageSource;
}
+ /* Below are deprecated for removal */
+ @Deprecated
+ private boolean loadCollectors = true;
+
/**
- * Set the message source to use for generating localised messages.
- *
- * @param messageSource the message source
+ * Use {@code isReadOnly} or {@code isWriteOnly}
+ * @return true if schema is used to write data
*/
- public void setMessageSource(MessageSource messageSource) {
- this.messageSource = messageSource;
+ @Deprecated
+ public boolean isWriteMode() {
+ return null == this.writeOnly || this.writeOnly;
}
/**
- * Gets the format assertion enabled flag.
- *
- * This defaults to null meaning that it will follow the defaults of the
- * specification.
+ * Sets if collectors are to be loaded.
*
- * Since draft 2019-09 this will default to false unless enabled by using the
- * $vocabulary keyword.
+ * This is deprecated in favor of the caller calling {@link CollectorContext#loadCollectors()} manually.
*
- * @return the format assertions enabled flag
+ * @param loadCollectors to load collectors
*/
- public Boolean getFormatAssertionsEnabled() {
- return formatAssertionsEnabled;
+ @Deprecated
+ public void setLoadCollectors(boolean loadCollectors) {
+ this.loadCollectors = loadCollectors;
}
/**
- * Sets the format assertion enabled flag.
+ * Gets if collectors are to be loaded.
*
- * @param formatAssertionsEnabled the format assertions enabled flag
+ * @return if collectors are to be loader
*/
- public void setFormatAssertionsEnabled(Boolean formatAssertionsEnabled) {
- this.formatAssertionsEnabled = formatAssertionsEnabled;
+ @Deprecated
+ public boolean doLoadCollectors() {
+ return this.loadCollectors;
}
- /**
- * Gets the schema id validator to validate $id.
- *
- * @return the validator
- */
- public JsonSchemaIdValidator getSchemaIdValidator() {
- return schemaIdValidator;
+ @Deprecated
+ public SchemaValidatorsConfig disableUnevaluatedAnalysis() {
+ return this;
}
- /**
- * Sets the schema id validator to validate $id.
- *
- * @param schemaIdValidator the validator
- */
- public void setSchemaIdValidator(JsonSchemaIdValidator schemaIdValidator) {
- this.schemaIdValidator = schemaIdValidator;
+ @Deprecated
+ public SchemaValidatorsConfig disableUnevaluatedItems() {
+ return this;
}
- /**
- * Gets if the schema should be preloaded.
- *
- * @return true if it should be preloaded
- */
- public boolean isPreloadJsonSchema() {
- return preloadJsonSchema;
+ @Deprecated
+ public SchemaValidatorsConfig disableUnevaluatedProperties() {
+ return this;
}
- /**
- * Sets if the schema should be preloaded.
- *
- * @param preloadJsonSchema true to preload
- */
- public void setPreloadJsonSchema(boolean preloadJsonSchema) {
- this.preloadJsonSchema = preloadJsonSchema;
+ @Deprecated
+ public SchemaValidatorsConfig enableUnevaluatedAnalysis() {
+ return this;
}
- /**
- * Gets the max depth of the evaluation path to preload when preloading refs.
- *
- * @return the max depth to preload
- */
- public int getPreloadJsonSchemaRefMaxNestingDepth() {
- return preloadJsonSchemaRefMaxNestingDepth;
+ @Deprecated
+ public SchemaValidatorsConfig enableUnevaluatedItems() {
+ return this;
}
- /**
- * Sets the max depth of the evaluation path to preload when preloading refs.
- *
- * @param preloadJsonSchemaRefMaxNestingDepth the max depth to preload
- */
- public void setPreloadJsonSchemaRefMaxNestingDepth(int preloadJsonSchemaRefMaxNestingDepth) {
- this.preloadJsonSchemaRefMaxNestingDepth = preloadJsonSchemaRefMaxNestingDepth;
+ @Deprecated
+ public SchemaValidatorsConfig enableUnevaluatedProperties() {
+ return this;
}
- /**
- * Gets if schemas loaded from refs will be cached and reused for subsequent
- * runs.
- *
- * @return true if schemas loaded from refs should be cached
- */
- public boolean isCacheRefs() {
- return cacheRefs;
+ @Deprecated
+ public boolean isUnevaluatedItemsAnalysisDisabled() {
+ return false;
}
- /**
- * Sets if schemas loaded from refs will be cached and reused for subsequent
- * runs.
- *
- * Note that setting this to false will affect performance as refs will need to
- * be repeatedly resolved for each evaluation run. It may be needed to be set to
- * false if there are multiple nested applicators like anyOf, oneOf and allOf as
- * that will consume a lot of memory to cache all the permutations.
- *
- * @param cacheRefs true to cache
- */
- public void setCacheRefs(boolean cacheRefs) {
- this.cacheRefs = cacheRefs;
+ @Deprecated
+ public boolean isUnevaluatedItemsAnalysisEnabled() {
+ return !isUnevaluatedItemsAnalysisDisabled();
+ }
+
+ @Deprecated
+ public boolean isUnevaluatedPropertiesAnalysisDisabled() {
+ return false;
+ }
+
+ @Deprecated
+ public boolean isUnevaluatedPropertiesAnalysisEnabled() {
+ return !isUnevaluatedPropertiesAnalysisDisabled();
}
}
From cf5f59cc1f9a65aa7d3a19e4fe1b71e9b04fc4f5 Mon Sep 17 00:00:00 2001
From: Justin Tay <49700559+justin-tay@users.noreply.github.com>
Date: Fri, 14 Jun 2024 11:42:20 +0800
Subject: [PATCH 02/16] Use an error message keyword instead of just a boolean
---
.../networknt/schema/BaseJsonValidator.java | 10 +--
.../com/networknt/schema/JsonMetaSchema.java | 2 +-
.../java/com/networknt/schema/JsonSchema.java | 10 +--
.../schema/SchemaValidatorsConfig.java | 67 ++++++++++++++-----
.../schema/ValidationMessageHandler.java | 35 +++++-----
5 files changed, 80 insertions(+), 44 deletions(-)
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
index 87e9311a7..7c3ee1833 100644
--- a/src/main/java/com/networknt/schema/BaseJsonValidator.java
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java
@@ -51,8 +51,8 @@ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationP
ValidationContext validationContext, boolean suppressSubSchemaRetrieval) {
super(errorMessageType,
(validationContext != null && validationContext.getConfig() != null)
- ? validationContext.getConfig().isCustomMessageSupported()
- : true,
+ ? validationContext.getConfig().getErrorMessageKeyword()
+ : null,
(validationContext != null && validationContext.getConfig() != null)
? validationContext.getConfig().getMessageSource()
: DefaultMessageSource.getInstance(),
@@ -70,7 +70,7 @@ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationP
* @param schemaNode the schema node
* @param validationContext the validation context
* @param errorMessageType the error message type
- * @param customErrorMessagesEnabled whether custom error msessages are enabled
+ * @param errorMessageKeyword the error message keyword
* @param messageSource the message source
* @param keyword the keyword
* @param parentSchema the parent schema
@@ -86,7 +86,7 @@ protected BaseJsonValidator(
ValidationContext validationContext,
/* Below from ValidationMessageHandler */
ErrorMessageType errorMessageType,
- boolean customErrorMessagesEnabled,
+ String errorMessageKeyword,
MessageSource messageSource,
Keyword keyword,
JsonSchema parentSchema,
@@ -94,7 +94,7 @@ protected BaseJsonValidator(
JsonNodePath evaluationPath,
JsonSchema evaluationParentSchema,
Map errorMessage) {
- super(errorMessageType, customErrorMessagesEnabled, messageSource, keyword,
+ super(errorMessageType, errorMessageKeyword, messageSource, keyword,
parentSchema, schemaLocation, evaluationPath, evaluationParentSchema, errorMessage);
this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
this.schemaNode = schemaNode;
diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java
index 4ae26bb4c..2cb451545 100644
--- a/src/main/java/com/networknt/schema/JsonMetaSchema.java
+++ b/src/main/java/com/networknt/schema/JsonMetaSchema.java
@@ -478,7 +478,7 @@ public JsonValidator newValidator(ValidationContext validationContext, SchemaLoc
try {
Keyword kw = this.keywords.get(keyword);
if (kw == null) {
- if ("message".equals(keyword) && validationContext.getConfig().isCustomMessageSupported()) {
+ if (keyword.equals(validationContext.getConfig().getErrorMessageKeyword())) {
return null;
}
if (ValidatorTypeCode.DISCRIMINATOR.getValue().equals(keyword)
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index 1c3182f47..3b05a51db 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -162,7 +162,7 @@ private JsonSchema(ValidationContext validationContext, SchemaLocation schemaLoc
* @param schemaNode the schema node
* @param validationContext the validation context
* @param errorMessageType the error message type
- * @param customErrorMessagesEnabled whether custom error msessages are enabled
+ * @param errorMessageKeyword the error message keyword
* @param messageSource the message source
* @param keyword the keyword
* @param parentSchema the parent schema
@@ -184,7 +184,7 @@ protected JsonSchema(
ValidationContext validationContext,
/* Below from ValidationMessageHandler */
ErrorMessageType errorMessageType,
- boolean customErrorMessagesEnabled,
+ String errorMessageKeyword,
MessageSource messageSource,
Keyword keyword,
JsonSchema parentSchema,
@@ -192,7 +192,7 @@ protected JsonSchema(
JsonNodePath evaluationPath,
JsonSchema evaluationParentSchema,
Map errorMessage) {
- super(suppressSubSchemaRetrieval, schemaNode, validationContext, errorMessageType, customErrorMessagesEnabled, messageSource, keyword,
+ super(suppressSubSchemaRetrieval, schemaNode, validationContext, errorMessageType, errorMessageKeyword, messageSource, keyword,
parentSchema, schemaLocation, evaluationPath, evaluationParentSchema, errorMessage);
this.validators = validators;
this.validatorsLoaded = validatorsLoaded;
@@ -238,7 +238,7 @@ public JsonSchema fromRef(JsonSchema refEvaluationParentSchema, JsonNodePath ref
schemaNode,
validationContext,
/* Below from ValidationMessageHandler */
- errorMessageType, customErrorMessagesEnabled, messageSource,
+ errorMessageType, errorMessageKeyword, messageSource,
keyword, parentSchema, schemaLocation, evaluationPath,
evaluationParentSchema, errorMessage);
}
@@ -266,7 +266,7 @@ public JsonSchema withConfig(SchemaValidatorsConfig config) {
validationContext,
/* Below from ValidationMessageHandler */
errorMessageType,
- customErrorMessagesEnabled,
+ errorMessageKeyword,
messageSource,
keyword,
parentSchema,
diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
index 1dc3ee0a5..495a42602 100644
--- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
+++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
@@ -59,7 +59,7 @@ public class SchemaValidatorsConfig {
* When set to true, "messages" provided in schema are used for forming validation errors
* else default messages are used
*/
- private boolean customMessageSupported = true;
+ private String errorMessageKeyword = "message";
private ExecutionContextCustomizer executionContextCustomizer;
@@ -86,7 +86,7 @@ public class SchemaValidatorsConfig {
*/
private boolean handleNullableField = true;
- private final WalkListenerRunner itemWalkListenerRunner = new DefaultItemWalkListenerRunner(getArrayItemWalkListeners());
+ private final WalkListenerRunner itemWalkListenerRunner;
private final List itemWalkListeners;
@@ -96,7 +96,7 @@ public class SchemaValidatorsConfig {
*/
private boolean javaSemantics;
- private final WalkListenerRunner keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(getKeywordWalkListenersMap());
+ private final WalkListenerRunner keywordWalkListenerRunner;
private final Map> keywordWalkListenersMap;
@@ -137,7 +137,7 @@ public class SchemaValidatorsConfig {
*/
private int preloadJsonSchemaRefMaxNestingDepth = DEFAULT_PRELOAD_JSON_SCHEMA_REF_MAX_NESTING_DEPTH;
- private final WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(getPropertyWalkListeners());
+ private final WalkListenerRunner propertyWalkListenerRunner;
private final List propertyWalkListeners;
@@ -176,6 +176,18 @@ public class SchemaValidatorsConfig {
*/
private Boolean writeOnly = null;
+ /**
+ * Constructor to create an instance.
+ *
+ * This is deprecated in favor of using the builder
+ * {@link SchemaValidatorsConfig#builder()} to create an instance. Migration
+ * note: The builder has different defaults from the constructor.
+ *
+ * - customMessageSupported (errorMessageKeyword): change from message to null
+ *
- pathType: changed from PathType.LEGACY to PathType.JSON_POINTER.
+ *
- handleNullableField (nullableKeywordEnabled): changed from true to false
+ *
+ */
@Deprecated
public SchemaValidatorsConfig() {
this.strictness = new HashMap<>(0);
@@ -183,10 +195,14 @@ public SchemaValidatorsConfig() {
this.keywordWalkListenersMap = new HashMap<>();
this.propertyWalkListeners = new ArrayList<>();
this.itemWalkListeners = new ArrayList<>();
+
+ this.itemWalkListenerRunner = new DefaultItemWalkListenerRunner(getArrayItemWalkListeners());
+ this.keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(getKeywordWalkListenersMap());
+ this.propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(getPropertyWalkListeners());
}
SchemaValidatorsConfig(ApplyDefaultsStrategy applyDefaultsStrategy, boolean cacheRefs,
- boolean customMessageSupported, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
+ String errorMessageKeyword, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
Boolean formatAssertionsEnabled, boolean handleNullableField,
List itemWalkListeners, boolean javaSemantics,
Map> keywordWalkListenersMap, Locale locale, boolean losslessNarrowing,
@@ -198,7 +214,7 @@ public SchemaValidatorsConfig() {
super();
this.applyDefaultsStrategy = applyDefaultsStrategy;
this.cacheRefs = cacheRefs;
- this.customMessageSupported = customMessageSupported;
+ this.errorMessageKeyword = errorMessageKeyword;
this.executionContextCustomizer = executionContextCustomizer;
this.failFast = failFast;
this.formatAssertionsEnabled = formatAssertionsEnabled;
@@ -220,6 +236,10 @@ public SchemaValidatorsConfig() {
this.strictness = strictness;
this.typeLoose = typeLoose;
this.writeOnly = writeOnly;
+
+ this.itemWalkListenerRunner = new DefaultItemWalkListenerRunner(getArrayItemWalkListeners());
+ this.keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(getKeywordWalkListenersMap());
+ this.propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(getPropertyWalkListeners());
}
public void addItemWalkListener(JsonSchemaWalkListener itemWalkListener) {
@@ -395,8 +415,13 @@ public boolean isCacheRefs() {
return cacheRefs;
}
+ @Deprecated
public boolean isCustomMessageSupported() {
- return customMessageSupported;
+ return this.errorMessageKeyword != null;
+ }
+
+ public String getErrorMessageKeyword() {
+ return this.errorMessageKeyword;
}
/**
@@ -508,8 +533,16 @@ public void setCacheRefs(boolean cacheRefs) {
this.cacheRefs = cacheRefs;
}
+ /**
+ * Sets whether custom error messages in the schema are used.
+ *
+ * This is deprecated in favor of setting the error message keyword to use.
+ *
+ * @param customMessageSupported true to use message as the error message keyword
+ */
+ @Deprecated
public void setCustomMessageSupported(boolean customMessageSupported) {
- this.customMessageSupported = customMessageSupported;
+ this.errorMessageKeyword = customMessageSupported ? "message" : null;
}
/**
@@ -706,7 +739,7 @@ public static Builder builder() {
public static class Builder {
private ApplyDefaultsStrategy applyDefaultsStrategy = ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
private boolean cacheRefs = true;
- private boolean customMessageEnabled = false;
+ private String errorMessageKeyword = null;
private ExecutionContextCustomizer executionContextCustomizer = null;
private boolean failFast = false;
private Boolean formatAssertionsEnabled = false;
@@ -754,15 +787,15 @@ public Builder cacheRefs(boolean cacheRefs) {
return this;
}
/**
- * Sets if the message keyword for setting custom messages in the schema is enabled.
+ * Sets the error message keyword for setting custom messages in the schema.
*
- * Defaults to false.
+ * Defaults to null meaning custom messages are not enabled.
*
- * @param messageKeywordEnabled true to enable
+ * @param errorMessageKeyword to use for custom messages in the schema
* @return the builder
*/
- public Builder messageKeywordEnabled(boolean messageKeywordEnabled) {
- this.customMessageEnabled = messageKeywordEnabled;
+ public Builder errorMessageKeyword(String errorMessageKeyword) {
+ this.errorMessageKeyword = errorMessageKeyword;
return this;
}
/**
@@ -956,7 +989,7 @@ public Builder writeOnly(Boolean writeOnly) {
return this;
}
public SchemaValidatorsConfig build() {
- return new ImmutableSchemaValidatorsConfig(applyDefaultsStrategy, cacheRefs, customMessageEnabled,
+ return new ImmutableSchemaValidatorsConfig(applyDefaultsStrategy, cacheRefs, errorMessageKeyword,
executionContextCustomizer, failFast, formatAssertionsEnabled, handleNullableField,
itemWalkListeners, javaSemantics, keywordWalkListeners, locale, losslessNarrowing, messageSource,
openAPI3StyleDiscriminators, pathType, preloadJsonSchema, preloadJsonSchemaRefMaxNestingDepth,
@@ -976,7 +1009,7 @@ public Builder strict(String keyword, boolean strict) {
*/
public static class ImmutableSchemaValidatorsConfig extends SchemaValidatorsConfig {
public ImmutableSchemaValidatorsConfig(ApplyDefaultsStrategy applyDefaultsStrategy, boolean cacheRefs,
- boolean customMessageSupported, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
+ String errorMessageKeyword, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
Boolean formatAssertionsEnabled, boolean handleNullableField,
List itemWalkListeners, boolean javaSemantics,
Map> keywordWalkListenersMap, Locale locale,
@@ -985,7 +1018,7 @@ public ImmutableSchemaValidatorsConfig(ApplyDefaultsStrategy applyDefaultsStrate
List propertyWalkListeners, Boolean readOnly,
RegularExpressionFactory regularExpressionFactory, JsonSchemaIdValidator schemaIdValidator,
Map strictness, boolean typeLoose, Boolean writeOnly) {
- super(applyDefaultsStrategy, cacheRefs, customMessageSupported, executionContextCustomizer, failFast,
+ super(applyDefaultsStrategy, cacheRefs, errorMessageKeyword, executionContextCustomizer, failFast,
formatAssertionsEnabled, handleNullableField, itemWalkListeners, javaSemantics, keywordWalkListenersMap, locale,
losslessNarrowing, messageSource, openAPI3StyleDiscriminators, pathType, preloadJsonSchema,
preloadJsonSchemaRefMaxNestingDepth, propertyWalkListeners, readOnly, regularExpressionFactory,
diff --git a/src/main/java/com/networknt/schema/ValidationMessageHandler.java b/src/main/java/com/networknt/schema/ValidationMessageHandler.java
index 1183f4ada..c0ca43d34 100644
--- a/src/main/java/com/networknt/schema/ValidationMessageHandler.java
+++ b/src/main/java/com/networknt/schema/ValidationMessageHandler.java
@@ -14,7 +14,7 @@
*/
public abstract class ValidationMessageHandler {
protected final ErrorMessageType errorMessageType;
- protected final boolean customErrorMessagesEnabled;
+ protected final String errorMessageKeyword;
protected final MessageSource messageSource;
protected final Keyword keyword;
protected final JsonSchema parentSchema;
@@ -23,7 +23,7 @@ public abstract class ValidationMessageHandler {
protected final JsonSchema evaluationParentSchema;
protected final Map errorMessage;
- protected ValidationMessageHandler(ErrorMessageType errorMessageType, boolean customErrorMessagesEnabled,
+ protected ValidationMessageHandler(ErrorMessageType errorMessageType, String errorMessageKeyword,
MessageSource messageSource, Keyword keyword, JsonSchema parentSchema, SchemaLocation schemaLocation,
JsonNodePath evaluationPath) {
ErrorMessageType currentErrorMessageType = errorMessageType;
@@ -32,14 +32,15 @@ protected ValidationMessageHandler(ErrorMessageType errorMessageType, boolean cu
this.evaluationPath = Objects.requireNonNull(evaluationPath);
this.parentSchema = parentSchema;
this.evaluationParentSchema = null;
- this.customErrorMessagesEnabled = customErrorMessagesEnabled;
+ this.errorMessageKeyword = errorMessageKeyword;
this.keyword = keyword;
Map currentErrorMessage = null;
if (this.keyword != null) {
- if (this.customErrorMessagesEnabled && keyword != null && parentSchema != null) {
- currentErrorMessage = getErrorMessage(parentSchema.getSchemaNode(), keyword.getValue());
+ if (this.errorMessageKeyword != null && keyword != null && parentSchema != null) {
+ currentErrorMessage = getErrorMessage(this.errorMessageKeyword, parentSchema.getSchemaNode(),
+ keyword.getValue());
}
String errorCodeKey = getErrorCodeKey(keyword.getValue());
if (errorCodeKey != null && this.parentSchema != null) {
@@ -60,7 +61,7 @@ protected ValidationMessageHandler(ErrorMessageType errorMessageType, boolean cu
* Constructor to create a copy using fields.
*
* @param errorMessageType the error message type
- * @param customErrorMessagesEnabled whether custom error msessages are enabled
+ * @param errorMessageKeyword the error message keyword
* @param messageSource the message source
* @param keyword the keyword
* @param parentSchema the parent schema
@@ -69,11 +70,11 @@ protected ValidationMessageHandler(ErrorMessageType errorMessageType, boolean cu
* @param evaluationParentSchema the evaluation parent schema
* @param errorMessage the error message
*/
- protected ValidationMessageHandler(ErrorMessageType errorMessageType, boolean customErrorMessagesEnabled,
+ protected ValidationMessageHandler(ErrorMessageType errorMessageType, String errorMessageKeyword,
MessageSource messageSource, Keyword keyword, JsonSchema parentSchema, SchemaLocation schemaLocation,
JsonNodePath evaluationPath, JsonSchema evaluationParentSchema, Map errorMessage) {
this.errorMessageType = errorMessageType;
- this.customErrorMessagesEnabled = customErrorMessagesEnabled;
+ this.errorMessageKeyword = errorMessageKeyword;
this.messageSource = messageSource;
this.keyword = keyword;
this.parentSchema = parentSchema;
@@ -104,9 +105,9 @@ protected ErrorMessageType getErrorMessageType() {
* @param keyword the keyword
* @return the custom error message
*/
- protected Map getErrorMessage(JsonNode schemaNode, String keyword) {
+ protected Map getErrorMessage(String errorMessageKeyword, JsonNode schemaNode, String keyword) {
final JsonSchema parentSchema = this.parentSchema;
- final JsonNode message = getMessageNode(schemaNode, parentSchema, keyword);
+ final JsonNode message = getMessageNode(errorMessageKeyword, schemaNode, parentSchema, keyword);
if (message != null) {
JsonNode messageNode = message.get(keyword);
if (messageNode != null) {
@@ -126,16 +127,18 @@ protected Map getErrorMessage(JsonNode schemaNode, String keywor
return Collections.emptyMap();
}
- protected JsonNode getMessageNode(JsonNode schemaNode, JsonSchema parentSchema, String pname) {
- if (schemaNode.get("message") != null && schemaNode.get("message").get(pname) != null) {
- return schemaNode.get("message");
+ protected JsonNode getMessageNode(String errorMessageKeyword, JsonNode schemaNode, JsonSchema parentSchema,
+ String pname) {
+ if (schemaNode.get(errorMessageKeyword) != null && schemaNode.get(errorMessageKeyword).get(pname) != null) {
+ return schemaNode.get(errorMessageKeyword);
}
JsonNode messageNode;
- messageNode = schemaNode.get("message");
+ messageNode = schemaNode.get(errorMessageKeyword);
if (messageNode == null && parentSchema != null) {
- messageNode = parentSchema.schemaNode.get("message");
+ messageNode = parentSchema.schemaNode.get(errorMessageKeyword);
if (messageNode == null) {
- return getMessageNode(parentSchema.schemaNode, parentSchema.getParentSchema(), pname);
+ return getMessageNode(errorMessageKeyword, parentSchema.schemaNode, parentSchema.getParentSchema(),
+ pname);
}
}
return messageNode;
From 6c8e5b26d9367d147d84ac841bca4efde4ff2d15 Mon Sep 17 00:00:00 2001
From: Justin Tay <49700559+justin-tay@users.noreply.github.com>
Date: Fri, 14 Jun 2024 17:12:00 +0800
Subject: [PATCH 03/16] Add tests
---
.../schema/SchemaValidatorsConfigTest.java | 48 ++++++++++++++
.../schema/ValidationMessageHandlerTest.java | 64 +++++++++++++++++++
2 files changed, 112 insertions(+)
create mode 100644 src/test/java/com/networknt/schema/ValidationMessageHandlerTest.java
diff --git a/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java b/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java
index 511eac987..1e013e6c3 100644
--- a/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java
+++ b/src/test/java/com/networknt/schema/SchemaValidatorsConfigTest.java
@@ -25,6 +25,7 @@
/**
* Test for SchemaValidatorsConfig.
*/
+@SuppressWarnings("deprecation")
class SchemaValidatorsConfigTest {
@Test
void defaultEcma262Validator() {
@@ -46,4 +47,51 @@ void setEcma262Validator() {
assertFalse(config.isEcma262Validator());
}
+ @Test
+ void constructorPathType() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ assertEquals(PathType.LEGACY, config.getPathType());
+ }
+
+ @Test
+ void builderPathType() {
+ SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build();
+ assertEquals(PathType.JSON_POINTER, config.getPathType());
+ }
+
+ @Test
+ void constructorCustomMessageSupported() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ assertEquals(true, config.isCustomMessageSupported());
+ }
+
+ @Test
+ void builderCustomMessageSupported() {
+ SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build();
+ assertEquals(false, config.isCustomMessageSupported());
+ }
+
+ @Test
+ void constructorHandleNullableField() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ assertEquals(true, config.isHandleNullableField());
+ }
+
+ @Test
+ void builderHandleNullableField() {
+ SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build();
+ assertEquals(false, config.isHandleNullableField());
+ }
+
+ @Test
+ void constructorMutable() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ assertDoesNotThrow(() -> config.setFailFast(true));
+ }
+
+ @Test
+ void builderImmutable() {
+ SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build();
+ assertThrows(UnsupportedOperationException.class, () -> config.setFailFast(true));
+ }
}
diff --git a/src/test/java/com/networknt/schema/ValidationMessageHandlerTest.java b/src/test/java/com/networknt/schema/ValidationMessageHandlerTest.java
new file mode 100644
index 000000000..a0b9c3cec
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ValidationMessageHandlerTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * ValidationMessageHandlerTest.
+ */
+class ValidationMessageHandlerTest {
+ @Test
+ void errorMessage() {
+ String schemaData = "{\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"required\": [\r\n"
+ + " \"foo\"\r\n"
+ + " ],\r\n"
+ + " \"properties\": {\r\n"
+ + " \"foo\": {\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"additionalProperties\": false,\r\n"
+ + " \"errorMessage\": {\r\n"
+ + " \"type\": \"should be an object\",\r\n"
+ + " \"required\": \"should have property foo\",\r\n"
+ + " \"additionalProperties\": \"should not have properties other than foo\"\r\n"
+ + " }\r\n"
+ + "}";
+ String inputData = "{\r\n"
+ + " \"foo\": \"a\",\r\n"
+ + " \"bar\": 2\r\n"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
+ SchemaValidatorsConfig.builder().errorMessageKeyword("errorMessage").build());
+ List messages = schema.validate(inputData, InputFormat.JSON).stream().collect(Collectors.toList());
+ assertFalse(messages.isEmpty());
+ assertEquals("/foo", messages.get(0).getInstanceLocation().toString());
+ assertEquals("should be an object", messages.get(0).getMessage());
+ assertEquals("", messages.get(1).getInstanceLocation().toString());
+ assertEquals("should not have properties other than foo", messages.get(1).getMessage());
+ }
+
+}
From 2d4bd8232f0cc0e2d283c83cdc31457c55dcacd9 Mon Sep 17 00:00:00 2001
From: Justin Tay <49700559+justin-tay@users.noreply.github.com>
Date: Sun, 16 Jun 2024 12:00:30 +0800
Subject: [PATCH 04/16] Refactor
---
.../schema/SchemaValidatorsConfig.java | 87 +++++++++++++++----
1 file changed, 71 insertions(+), 16 deletions(-)
diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
index 495a42602..b84605e7d 100644
--- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
+++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
@@ -34,6 +34,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Configuration for validators.
@@ -84,7 +85,7 @@ public class SchemaValidatorsConfig {
* field is nullable && value is field: null --> it is up to the type validator
* using the SchemaValidator to handle it.
*/
- private boolean handleNullableField = true;
+ private boolean nullableKeywordEnabled = true;
private final WalkListenerRunner itemWalkListenerRunner;
@@ -120,7 +121,7 @@ public class SchemaValidatorsConfig {
* oneOf, anyOf and allOf as described on GitHub.
*/
- private boolean openAPI3StyleDiscriminators = false;
+ private boolean discriminatorKeywordEnabled = false;
/**
* The approach used to generate paths in reported messages, logs and errors. Default is the legacy "JSONPath-like" approach.
@@ -203,10 +204,10 @@ public SchemaValidatorsConfig() {
SchemaValidatorsConfig(ApplyDefaultsStrategy applyDefaultsStrategy, boolean cacheRefs,
String errorMessageKeyword, ExecutionContextCustomizer executionContextCustomizer, boolean failFast,
- Boolean formatAssertionsEnabled, boolean handleNullableField,
+ Boolean formatAssertionsEnabled, boolean nullableKeywordEnabled,
List itemWalkListeners, boolean javaSemantics,
Map> keywordWalkListenersMap, Locale locale, boolean losslessNarrowing,
- MessageSource messageSource, boolean openAPI3StyleDiscriminators, PathType pathType,
+ MessageSource messageSource, boolean discriminatorKeywordEnabled, PathType pathType,
boolean preloadJsonSchema, int preloadJsonSchemaRefMaxNestingDepth,
List propertyWalkListeners, Boolean readOnly,
RegularExpressionFactory regularExpressionFactory, JsonSchemaIdValidator schemaIdValidator,
@@ -218,14 +219,14 @@ public SchemaValidatorsConfig() {
this.executionContextCustomizer = executionContextCustomizer;
this.failFast = failFast;
this.formatAssertionsEnabled = formatAssertionsEnabled;
- this.handleNullableField = handleNullableField;
+ this.nullableKeywordEnabled = nullableKeywordEnabled;
this.itemWalkListeners = itemWalkListeners;
this.javaSemantics = javaSemantics;
this.keywordWalkListenersMap = keywordWalkListenersMap;
this.locale = locale;
this.losslessNarrowing = losslessNarrowing;
this.messageSource = messageSource;
- this.openAPI3StyleDiscriminators = openAPI3StyleDiscriminators;
+ this.discriminatorKeywordEnabled = discriminatorKeywordEnabled;
this.pathType = pathType;
this.preloadJsonSchema = preloadJsonSchema;
this.preloadJsonSchemaRefMaxNestingDepth = preloadJsonSchemaRefMaxNestingDepth;
@@ -440,8 +441,23 @@ public boolean isFailFast() {
return this.failFast;
}
+ /**
+ * Deprecated use {{@link #isNullableKeywordEnabled()} instead.
+ *
+ * @return true if the nullable keyword is enabled
+ */
+ @Deprecated
public boolean isHandleNullableField() {
- return this.handleNullableField;
+ return isNullableKeywordEnabled();
+ }
+
+ /**
+ * Gets if the nullable keyword is enabled.
+ *
+ * @return true if the nullable keyword is enabled
+ */
+ public boolean isNullableKeywordEnabled() {
+ return this.nullableKeywordEnabled;
}
public boolean isJavaSemantics() {
@@ -454,12 +470,24 @@ public boolean isLosslessNarrowing() {
/**
* Indicates whether OpenAPI 3 style discriminators should be supported
+ *
+ * Deprecated use {{@link #isDiscriminatorKeywordEnabled()} instead.
*
* @return true in case discriminators are enabled
* @since 1.0.51
*/
+ @Deprecated
public boolean isOpenAPI3StyleDiscriminators() {
- return this.openAPI3StyleDiscriminators;
+ return isDiscriminatorKeywordEnabled();
+ }
+
+ /**
+ * Gets if the discriminator keyword is enabled.
+ *
+ * @return true if the discriminator keyword is enabled
+ */
+ public boolean isDiscriminatorKeywordEnabled() {
+ return this.discriminatorKeywordEnabled;
}
/**
@@ -585,7 +613,7 @@ public void setFormatAssertionsEnabled(Boolean formatAssertionsEnabled) {
}
public void setHandleNullableField(boolean handleNullableField) {
- this.handleNullableField = handleNullableField;
+ this.nullableKeywordEnabled = handleNullableField;
}
public void setJavaSemantics(boolean javaSemantics) {
@@ -652,7 +680,7 @@ public void setMessageSource(MessageSource messageSource) {
* @since 1.0.51
*/
public void setOpenAPI3StyleDiscriminators(boolean openAPI3StyleDiscriminators) {
- this.openAPI3StyleDiscriminators = openAPI3StyleDiscriminators;
+ this.discriminatorKeywordEnabled = openAPI3StyleDiscriminators;
}
/**
@@ -743,14 +771,14 @@ public static class Builder {
private ExecutionContextCustomizer executionContextCustomizer = null;
private boolean failFast = false;
private Boolean formatAssertionsEnabled = false;
- private boolean handleNullableField = false;
+ private boolean nullableKeywordEnabled = false;
private List itemWalkListeners = new ArrayList<>();
private boolean javaSemantics = false;
private Map> keywordWalkListeners = new HashMap<>();
private Locale locale = null; // This must be null to use Locale.getDefault() as the default can be changed
private boolean losslessNarrowing = false;
private MessageSource messageSource = null;
- private boolean openAPI3StyleDiscriminators = false;
+ private boolean discriminatorKeywordEnabled = false;
private PathType pathType = PathType.JSON_POINTER;
private boolean preloadJsonSchema = true;
private int preloadJsonSchemaRefMaxNestingDepth = DEFAULT_PRELOAD_JSON_SCHEMA_REF_MAX_NESTING_DEPTH;
@@ -847,7 +875,7 @@ public Builder formatAssertionsEnabled(Boolean formatAssertionsEnabled) {
* @return the builder
*/
public Builder nullableKeywordEnabled(boolean nullableKeywordEnabled) {
- this.handleNullableField = nullableKeywordEnabled;
+ this.nullableKeywordEnabled = nullableKeywordEnabled;
return this;
}
public Builder itemWalkListeners(List itemWalkListeners) {
@@ -900,7 +928,7 @@ public Builder messageSource(MessageSource messageSource) {
* @return the builder
*/
public Builder discriminatorKeywordEnabled(boolean discriminatorKeywordEnabled) {
- this.openAPI3StyleDiscriminators = discriminatorKeywordEnabled;
+ this.discriminatorKeywordEnabled = discriminatorKeywordEnabled;
return this;
}
/**
@@ -990,9 +1018,9 @@ public Builder writeOnly(Boolean writeOnly) {
}
public SchemaValidatorsConfig build() {
return new ImmutableSchemaValidatorsConfig(applyDefaultsStrategy, cacheRefs, errorMessageKeyword,
- executionContextCustomizer, failFast, formatAssertionsEnabled, handleNullableField,
+ executionContextCustomizer, failFast, formatAssertionsEnabled, nullableKeywordEnabled,
itemWalkListeners, javaSemantics, keywordWalkListeners, locale, losslessNarrowing, messageSource,
- openAPI3StyleDiscriminators, pathType, preloadJsonSchema, preloadJsonSchemaRefMaxNestingDepth,
+ discriminatorKeywordEnabled, pathType, preloadJsonSchema, preloadJsonSchemaRefMaxNestingDepth,
propertyWalkListeners, readOnly, regularExpressionFactory, schemaIdValidator, strictness, typeLoose,
writeOnly);
}
@@ -1000,6 +1028,33 @@ public Builder strict(String keyword, boolean strict) {
this.strictness.put(Objects.requireNonNull(keyword, "keyword cannot be null"), strict);
return this;
}
+ public Builder keywordWalkListener(String keyword, JsonSchemaWalkListener keywordWalkListener) {
+ this.keywordWalkListeners.computeIfAbsent(keyword, key -> new ArrayList<>()).add(keywordWalkListener);
+ return this;
+ }
+ public Builder keywordWalkListener(JsonSchemaWalkListener keywordWalkListener) {
+ return keywordWalkListener(ALL_KEYWORD_WALK_LISTENER_KEY, keywordWalkListener);
+ }
+ public Builder keywordWalkListeners(Consumer