From ab66335f19708831aa39b95d6cf8ab6cbc049725 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:29:20 +0800 Subject: [PATCH] Allow for default dialect id not to be specified and throw an exception --- README.md | 10 +++++++ .../schema/MissingSchemaKeywordException.java | 29 +++++++++++++++++++ .../com/networknt/schema/SchemaRegistry.java | 7 +++-- .../networknt/schema/SchemaRegistryTest.java | 29 +++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/networknt/schema/MissingSchemaKeywordException.java diff --git a/README.md b/README.md index 7f95eb76a..cecbdbb0c 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,16 @@ List errors = schema.validate(input, InputFormat.JSON, executionContext - }); ``` +### Require schema dialect to be specified + +The specification allows for the `$schema` keyword not to be specified, in which case the schema will default to the default dialect specified. + +The following example creates a `SchemaRegistry` that does not specify a default dialect and will throw a `MissingSchemaKeywordException` if the schema does not specify a dialect using the `$schema` keyword. + +```java +SchemaRegistry registry = SchemaRegistry.builder().dialectRegistry(new BasicDialectRegistry(Dialects.getDraft202012())).build(); +``` + ### Results and output formats #### Results diff --git a/src/main/java/com/networknt/schema/MissingSchemaKeywordException.java b/src/main/java/com/networknt/schema/MissingSchemaKeywordException.java new file mode 100644 index 000000000..080760864 --- /dev/null +++ b/src/main/java/com/networknt/schema/MissingSchemaKeywordException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 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; + +/** + * Thrown when the $schema keyword is expected as no default dialect id was + * specified. + */ +public class MissingSchemaKeywordException extends SchemaException { + private static final long serialVersionUID = 1L; + + public MissingSchemaKeywordException(String message) { + super(message); + } +} diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index 8095d4176..25c58839b 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -218,9 +218,6 @@ public SchemaRegistry build() { private SchemaRegistry(NodeReader nodeReader, String defaultDialectId, SchemaLoader schemaLoader, boolean schemaCacheEnabled, DialectRegistry dialectRegistry, SchemaRegistryConfig schemaRegistryConfig) { - if (defaultDialectId == null || defaultDialectId.trim().isEmpty()) { - throw new IllegalArgumentException("defaultDialectId must not be null or empty"); - } this.nodeReader = nodeReader != null ? nodeReader : BasicNodeReader.getInstance(); this.defaultDialectId = defaultDialectId; this.schemaLoader = schemaLoader != null ? schemaLoader : SchemaLoader.getDefault(); @@ -549,6 +546,10 @@ private Dialect getDialectOrDefault(final JsonNode schemaNode) { throw new SchemaException("Unknown dialect: " + iriNode); } final String iri = iriNode == null || iriNode.isNull() ? defaultDialectId : iriNode.textValue(); + if (iri == null) { + throw new MissingSchemaKeywordException( + "The $schema keyword that indicates the schema dialect must be specified."); + } return getDialect(iri); } diff --git a/src/test/java/com/networknt/schema/SchemaRegistryTest.java b/src/test/java/com/networknt/schema/SchemaRegistryTest.java index 4a569eb47..1afff4380 100644 --- a/src/test/java/com/networknt/schema/SchemaRegistryTest.java +++ b/src/test/java/com/networknt/schema/SchemaRegistryTest.java @@ -25,7 +25,9 @@ import org.junit.jupiter.api.Test; +import com.networknt.schema.dialect.BasicDialectRegistry; import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; /** * Tests for JsonSchemaFactory. @@ -87,4 +89,31 @@ public void run() { }); assertFalse(failed.get()); } + + @Test + void noDefaultDialect() { + SchemaRegistry registry = SchemaRegistry.builder() + .dialectRegistry(new BasicDialectRegistry(Dialects.getDraft202012())).build(); + assertThrows(MissingSchemaKeywordException.class, () -> { + registry.getSchema("{\"type\":\"object\"}"); + }); + } + + @Test + void noDefaultDialectButSchemaSpecified() { + SchemaRegistry registry = SchemaRegistry.builder() + .dialectRegistry(new BasicDialectRegistry(Dialects.getDraft202012())).build(); + assertDoesNotThrow(() -> { + registry.getSchema("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\"}"); + }); + } + + @Test + void noDefaultDialectButSchemaSpecifiedButNotInRegistry() { + SchemaRegistry registry = SchemaRegistry.builder() + .dialectRegistry(new BasicDialectRegistry(Dialects.getDraft201909())).build(); + assertThrows(InvalidSchemaException.class, () -> { + registry.getSchema("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\"}"); + }); + } }