diff --git a/src/main/java/org/cyclonedx/CycloneDxSchema.java b/src/main/java/org/cyclonedx/CycloneDxSchema.java index c507b20a85..d45644001c 100644 --- a/src/main/java/org/cyclonedx/CycloneDxSchema.java +++ b/src/main/java/org/cyclonedx/CycloneDxSchema.java @@ -22,6 +22,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.networknt.schema.SchemaRegistry; import com.networknt.schema.SchemaRegistryConfig; +import com.networknt.schema.dialect.DefaultDialectRegistry; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.DialectId; +import com.networknt.schema.dialect.DialectRegistry; +import com.networknt.schema.dialect.Draft7; +import com.networknt.schema.keyword.NonValidationKeyword; import com.networknt.schema.serialization.DefaultNodeReader; import org.cyclonedx.generators.json.BomJsonGenerator; import org.cyclonedx.generators.xml.BomXmlGenerator; @@ -96,14 +102,39 @@ public com.networknt.schema.Schema getJsonSchema(Version schemaVersion, final Ob offlineMappings.put("http://cyclonedx.org/schema/bom-1.6.schema.json", "classpath:bom-1.6.schema.json"); JsonNode schemaNode = mapper.readTree(spdxInstream); + final Dialect cycloneDxDialect = getCycloneDxJsonDialect(); + final DialectRegistry defaultDialectRegistry = new DefaultDialectRegistry(); + final DialectRegistry dialectRegistry = (dialectId, schemaRegistry) -> + DialectId.DRAFT_7.equals(dialectId) + ? cycloneDxDialect + : defaultDialectRegistry.getDialect(dialectId, schemaRegistry); SchemaRegistry registry = SchemaRegistry.builder() .nodeReader(DefaultNodeReader.builder().jsonMapper(mapper).build()) .schemaIdResolvers(b -> b.mappings(offlineMappings)) .schemaRegistryConfig(config) + .dialectRegistry(dialectRegistry) .build(); return registry.getSchema(schemaNode); } + /** + * Returns the JSON schema dialect used to interpret the CycloneDX JSON schemas. + *
+ * The CycloneDX JSON schemas declare the draft-07 dialect but make use of the + * CycloneDX-specific {@code meta:enum} and {@code deprecated} annotation keywords, + * which are not part of draft-07. They are registered here as non-validating + * keywords so the underlying validator does not emit "Unknown keyword" warnings. + * The keywords carry no validation semantics, so validation behaviour is unchanged. + * + * @return a draft-07 based dialect that recognises the CycloneDX annotation keywords + */ + static Dialect getCycloneDxJsonDialect() { + return Dialect.builder(Draft7.getInstance()) + .keyword(new NonValidationKeyword("meta:enum")) + .keyword(new NonValidationKeyword("deprecated")) + .build(); + } + private InputStream getJsonSchemaAsStream(final Version schemaVersion) { if (Version.VERSION_12 == schemaVersion) { return this.getClass().getClassLoader().getResourceAsStream("bom-1.2-strict.schema.json"); diff --git a/src/test/java/org/cyclonedx/CycloneDxJsonDialectTest.java b/src/test/java/org/cyclonedx/CycloneDxJsonDialectTest.java new file mode 100644 index 0000000000..d234ce1c88 --- /dev/null +++ b/src/test/java/org/cyclonedx/CycloneDxJsonDialectTest.java @@ -0,0 +1,79 @@ +/* + * This file is part of CycloneDX Core (Java). + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.cyclonedx; + +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Draft7; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.NonValidationKeyword; +import org.cyclonedx.parsers.JsonParser; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Map; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that the CycloneDX JSON dialect registers the CycloneDX-specific + * {@code meta:enum} and {@code deprecated} annotation keywords. + *
+ * The CycloneDX 1.6 JSON schema declares the draft-07 dialect but uses these
+ * two
+ * keywords, which are not part of draft-07. Without registering them the
+ * underlying
+ * {@code json-schema-validator} logs a "Unknown keyword" warning for each one.
+ * Treating
+ * them as {@link NonValidationKeyword}s keeps validation behaviour unchanged
+ * while
+ * suppressing the warnings.
+ */
+class CycloneDxJsonDialectTest {
+
+ @Test
+ void registersCycloneDxAnnotationKeywordsAsNonValidating() {
+ final Map