diff --git a/core/pom.xml b/core/pom.xml index 9de7e9bdf8..c40a357443 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -169,6 +169,11 @@ oauth2-openid 1.2.0 + + com.networknt + json-schema-validator + 1.5.9 + com.jayway.jsonpath json-path diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java index b0d038be08..f183ae349d 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java @@ -17,6 +17,7 @@ import com.predic8.membrane.annot.*; import com.predic8.membrane.core.exchange.*; import com.predic8.membrane.core.interceptor.*; +import com.predic8.membrane.core.interceptor.schemavalidation.json.*; import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.*; @@ -32,6 +33,7 @@ import static com.predic8.membrane.core.interceptor.Outcome.ABORT; import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.resolver.ResolverMap.*; +import static com.predic8.membrane.core.util.TextUtil.linkURL; /** * Basically switches over {@link WSDLValidator}, {@link XMLSchemaValidator}, @@ -49,6 +51,13 @@ public class ValidatorInterceptor extends AbstractInterceptor implements Applica private String schema; private String serviceName; private String jsonSchema; + + /** + * Schema version e.g. JSON Schema version 04, 07, 2020-12 + * Could also be used for XML or WSDL schema versions later. + */ + private String schemaVersion = "2020-12"; + private String schematron; private String failureHandler; private boolean skipFaults; @@ -89,7 +98,7 @@ private MessageValidator getMessageValidator() throws Exception { return new XMLSchemaValidator(resourceResolver, combine(getBaseLocation(), schema), createFailureHandler()); } if (jsonSchema != null) { - return new JSONSchemaValidator(resourceResolver, combine(getBaseLocation(), jsonSchema), createFailureHandler()); + return new JSONYAMLSchemaValidator(resourceResolver, combine(getBaseLocation(), jsonSchema), createFailureHandler(), schemaVersion); } if (schematron != null) { return new SchematronValidator(combine(getBaseLocation(), schematron), createFailureHandler(), router, applicationContext); @@ -206,6 +215,20 @@ public void setJsonSchema(String jsonSchema) { this.jsonSchema = jsonSchema; } + public String getSchemaVersion() { + return schemaVersion; + } + + /** + * @description The version of the Schema. + * @example 04, 05, 06, 07, 2019-09, 2020-12 + * @default 2020-12 + */ + @MCAttribute + public void setSchemaVersion(String version) { + this.schemaVersion = version; + } + public String getSchematron() { return schematron; } @@ -263,19 +286,19 @@ public String getLongDescription() { sb.append(" according to "); if (wsdl != null) { sb.append("the WSDL at
"); - sb.append(TextUtil.linkURL(wsdl)); + sb.append(linkURL(wsdl)); } if (schema != null) { sb.append("the XML Schema at
"); - sb.append(TextUtil.linkURL(schema)); + sb.append(linkURL(schema)); } if (jsonSchema != null) { sb.append("the JSON Schema at
"); - sb.append(TextUtil.linkURL(jsonSchema)); + sb.append(linkURL(jsonSchema)); } if (schematron != null) { sb.append("the Schematron at
"); - sb.append(TextUtil.linkURL(schematron)); + sb.append(linkURL(schematron)); } sb.append(" ."); return sb.toString(); @@ -290,7 +313,7 @@ public interface FailureHandler { private FailureHandler createFailureHandler() { if (failureHandler == null || failureHandler.equals("response")) - return null; + return (msg,exchange) -> {}; if (failureHandler.equals("log")) return (message, exc) -> log.info("Validation failure: {}", message); throw new IllegalArgumentException("Unknown failureHandler type: " + failureHandler); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParser.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParser.java new file mode 100644 index 0000000000..c5fe867ebc --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParser.java @@ -0,0 +1,41 @@ +/* Copyright 2025 predic8 GmbH, www.predic8.com + + 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.predic8.membrane.core.interceptor.schemavalidation.json; + +import com.networknt.schema.*; +import com.predic8.membrane.core.util.*; +import org.jetbrains.annotations.*; + +import static com.networknt.schema.SchemaId.*; + +public class JSONSchemaVersionParser { + + public static SpecVersion.VersionFlag parse(String version) { + return SpecVersion.VersionFlag.fromId(aliasToSpecId(version)).get(); + } + + static @NotNull String aliasToSpecId(String alias) { + if (alias == null) + throw new ConfigurationException("Unknown JSON Schema version: " + alias); + return switch (alias) { + case "04","draft-04" -> V4; + case "06","draft-06" -> V6; + case "07","draft-07" -> V7; + case "2019-09" -> V201909; + case "2020-12" -> V202012; + default -> throw new ConfigurationException("Unknown JSON Schema version: " + alias); + }; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONYAMLSchemaValidator.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONYAMLSchemaValidator.java new file mode 100644 index 0000000000..bcf89d2a47 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONYAMLSchemaValidator.java @@ -0,0 +1,181 @@ +/* Copyright 2012 predic8 GmbH, www.predic8.com + + 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.predic8.membrane.core.interceptor.schemavalidation.json; + +import com.github.fge.jsonschema.*; +import com.networknt.schema.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.http.*; +import com.predic8.membrane.core.interceptor.Interceptor.*; +import com.predic8.membrane.core.interceptor.*; +import com.predic8.membrane.core.interceptor.schemavalidation.*; +import com.predic8.membrane.core.interceptor.schemavalidation.ValidatorInterceptor.*; +import com.predic8.membrane.core.resolver.*; +import org.jetbrains.annotations.*; +import org.slf4j.*; + +import java.nio.charset.*; +import java.util.*; +import java.util.concurrent.atomic.*; + +import static com.networknt.schema.InputFormat.JSON; +import static com.predic8.membrane.core.exceptions.ProblemDetails.*; +import static com.predic8.membrane.core.interceptor.Outcome.*; +import static java.nio.charset.StandardCharsets.*; + +public class JSONYAMLSchemaValidator extends AbstractMessageValidator { + + private static final Logger log = LoggerFactory.getLogger(JSONYAMLSchemaValidator.class); + + private final Resolver resolver; + private final String jsonSchema; + private final FailureHandler failureHandler; + + private final AtomicLong valid = new AtomicLong(); + private final AtomicLong invalid = new AtomicLong(); + private final SpecVersion.VersionFlag schemaId; + + /** + * JsonSchemaFactory instances are thread-safe provided its configuration is not modified. + */ + JsonSchemaFactory jsonSchemaFactory; + + SchemaValidatorsConfig config; + + /** + * JsonSchema instances are thread-safe provided its configuration is not modified. + */ + JsonSchema schema; + + public JSONYAMLSchemaValidator(Resolver resolver, String jsonSchema, FailureHandler failureHandler, String schemaVersion) { + this.resolver = resolver; + this.jsonSchema = jsonSchema; + this.failureHandler = failureHandler; + this.schemaId = JSONSchemaVersionParser.parse( schemaVersion); + } + + public JSONYAMLSchemaValidator(Resolver resolver, String jsonSchema, FailureHandler failureHandler) { + this(resolver, jsonSchema, failureHandler, "2020-12"); + } + + @Override + public String getName() { + return "JSON Schema Validator"; + } + + @Override + public void init() { + super.init(); + + jsonSchemaFactory = JsonSchemaFactory.getInstance(schemaId, builder -> + builder.schemaLoaders(loaders -> loaders.add(new MembraneSchemaLoader(resolver))) + // builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.example.org/", "classpath:/")) + ); + + SchemaValidatorsConfig.Builder builder = SchemaValidatorsConfig.builder(); + // By default the JDK regular expression implementation which is not ECMA 262 compliant is used + // Note that setting this requires including optional dependencies + // builder.regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance()); + // builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance()); + config = builder.build(); + + // If the schema data does not specify an $id the absolute IRI of the schema location will be used as the $id. + schema= jsonSchemaFactory.getSchema(SchemaLocation.of( jsonSchema), config); + schema.initializeValidators(); + + } + + public Outcome validateMessage(Exchange exc, Flow flow) throws Exception { + return validateMessage(exc, flow, UTF_8); + } + + public Outcome validateMessage(Exchange exc, Flow flow, Charset ignored) throws Exception { + + Set assertions = schema.validate(exc.getMessage(flow).getBodyAsStringDecoded(), JSON); + + if (assertions.isEmpty()) { + valid.incrementAndGet(); + return CONTINUE; + } + invalid.incrementAndGet(); + + + log.debug("Validation failed: {}", assertions); + + List> mapForProblemDetails = getMapForProblemDetails(assertions); + failureHandler.handleFailure(mapForProblemDetails.toString(), exc); + + user(false, getName()) + .title(getErrorTitle()) + .addSubType("validation") + .component(getName()) + .internal("flow", flow.name()) + .internal("errors", mapForProblemDetails) + .buildAndSetResponse(exc); + + return ABORT; + } + + private @NotNull List> getMapForProblemDetails(Set assertions) { + return assertions.stream().map(this::validationMessageToProblemDetailsMap).toList(); + } + + private @NotNull Map validationMessageToProblemDetailsMap(ValidationMessage vm) { + Map m = new LinkedHashMap<>(); + m.put("message", vm.getMessage()); + m.put("code", vm.getCode()); + m.put("key", vm.getMessageKey()); + if (vm.getDetails() != null) + m.put("details", vm.getDetails()); + m.put("type", vm.getType()); + m.put("error", vm.getError()); + m.put("pointer", getPointer(vm.getEvaluationPath())); + m.put("node", vm.getInstanceNode()); + return m; + } + + private String getPointer(JsonNodePath evaluationPath) { + if (evaluationPath == null || evaluationPath.getNameCount() == 0) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < evaluationPath.getNameCount(); i++) { + sb.append('/'); + String part = evaluationPath.getName(i); + + // escape according to RFC 6901 + part = part.replace("~", "~0").replace("/", "~1"); + + sb.append(part); + } + return sb.toString(); + } + + @Override + public long getValid() { + return valid.get(); + } + + @Override + public long getInvalid() { + return invalid.get(); + } + + @Override + public String getErrorTitle() { + return "JSON validation failed"; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/MembraneSchemaLoader.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/MembraneSchemaLoader.java new file mode 100644 index 0000000000..41a0e400d8 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/MembraneSchemaLoader.java @@ -0,0 +1,33 @@ +/* Copyright 2025 predic8 GmbH, www.predic8.com + + 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.predic8.membrane.core.interceptor.schemavalidation.json; + +import com.networknt.schema.*; +import com.networknt.schema.resource.*; +import com.predic8.membrane.core.resolver.*; + +public class MembraneSchemaLoader implements SchemaLoader { + + private final Resolver resolver; + + public MembraneSchemaLoader(Resolver resolver) { + this.resolver = resolver; + } + + @Override + public InputStreamSource getSchema(AbsoluteIri absoluteIri) { + return () -> resolver.resolve(absoluteIri.toString()); + } +} diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONSchemaValidationTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONSchemaValidationTest.java index 43163282f9..4e0bf3354a 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONSchemaValidationTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONSchemaValidationTest.java @@ -78,8 +78,6 @@ void inValid1() throws Exception { assertEquals("JSON validation failed", jn.get("title").textValue()); assertEquals("https://membrane-api.io/problems/user/validation",jn.get("type").textValue()); assertEquals(1, jn.get("errors").size()); - -// System.out.println("exc.getResponse().getBodyAsStringDecoded() = " + exc.getResponse().getBodyAsStringDecoded()); } @Test @@ -147,8 +145,6 @@ void inValid2() throws Exception { assertEquals("JSON validation failed", jn.get("title").textValue()); assertEquals("https://membrane-api.io/problems/user/validation",jn.get("type").textValue()); assertEquals(2, jn.get("errors").size()); - - System.out.println("exc.getResponse().getBodyAsStringDecoded() = " + exc.getResponse().getBodyAsStringDecoded()); } private static @NotNull JSONSchemaValidator getValidator(String schema) { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONYAMLSchemaValidatorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONYAMLSchemaValidatorTest.java new file mode 100644 index 0000000000..3b14d2e5f7 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/JSONYAMLSchemaValidatorTest.java @@ -0,0 +1,77 @@ +/* Copyright 2025 predic8 GmbH, www.predic8.com + + 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.predic8.membrane.core.interceptor.schemavalidation; + +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.interceptor.schemavalidation.json.*; +import com.predic8.membrane.core.resolver.*; +import com.predic8.membrane.core.util.*; +import org.junit.jupiter.api.*; + +import static com.predic8.membrane.core.http.Request.get; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST; +import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class JSONYAMLSchemaValidatorTest { + + JSONYAMLSchemaValidator validator; + + @BeforeEach + void setup() { + validator = new JSONYAMLSchemaValidator(new ClasspathSchemaResolver(), "/validation/json-schema/simple-schema.json", (a,b) -> {}); + validator.init(); + } + + @Test + void invalidSchemaVersion() { + assertThrows(ConfigurationException.class, () -> + new JSONYAMLSchemaValidator(new ClasspathSchemaResolver(), "doesn't matter", null,"unknown version")); + } + + @Test + void simple() throws Exception { + Exchange exc = get("/foo").body(""" + { + "name": "Robert" + } + """).buildExchange(); + assertEquals(CONTINUE, validator.validateMessage( exc, REQUEST)); + assertEquals(1, validator.getValid()); + } + + @Test + void invalidNumber() throws Exception { + Exchange exc = get("/foo").body(""" + { + "age": -1 + } + """).buildExchange(); + validator.validateMessage( exc, REQUEST); + assertEquals(1, validator.getInvalid()); + assertEquals(0, validator.getValid()); + } + + @Test + void additionalProperty() throws Exception { + Exchange exc = get("/foo").body(""" + { + "unknown": "foo" + } + """).buildExchange(); + validator.validateMessage( exc, REQUEST); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParserTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParserTest.java new file mode 100644 index 0000000000..ddfec39971 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParserTest.java @@ -0,0 +1,47 @@ +/* Copyright 2025 predic8 GmbH, www.predic8.com + + 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.predic8.membrane.core.interceptor.schemavalidation.json; + +import com.predic8.membrane.core.util.*; +import org.junit.jupiter.api.*; + +import static com.networknt.schema.SpecVersion.VersionFlag.*; +import static com.predic8.membrane.core.interceptor.schemavalidation.json.JSONSchemaVersionParser.*; +import static org.junit.jupiter.api.Assertions.*; + +class JSONSchemaVersionParserTest { + + @Test + void parseUnknownVersion() { + assertThrows(ConfigurationException.class, () -> parse("invalid-version")); + } + + @Test + void parseNullVersion() { + assertThrows(ConfigurationException.class, () -> parse(null)); + } + + @Test + void parseFromAlias() { + assertEquals(V4, parse("04")); + assertEquals(V6, parse("06")); + assertEquals(V7, parse("07")); + assertEquals(V4, parse("draft-04")); + assertEquals(V6, parse("draft-06")); + assertEquals(V7, parse("draft-07")); + assertEquals(V201909, parse("2019-09")); + assertEquals(V202012, parse("2020-12")); + } +} \ No newline at end of file diff --git a/core/src/test/resources/validation/json-schema/simple-schema.json b/core/src/test/resources/validation/json-schema/simple-schema.json new file mode 100644 index 0000000000..84188d3292 --- /dev/null +++ b/core/src/test/resources/validation/json-schema/simple-schema.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 3 + }, + "age": { + "type": "integer", + "minimum": 0 + }, + "zip": { + "type": "string", + "pattern": "^\\d{5}$" + } + } +} \ No newline at end of file diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/validation/JSONSchemaValidationExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/validation/JSONSchemaValidationExampleTest.java index 510f0de6ed..3f3df6cef0 100644 --- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/validation/JSONSchemaValidationExampleTest.java +++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/validation/JSONSchemaValidationExampleTest.java @@ -17,10 +17,11 @@ import com.predic8.membrane.examples.util.*; import org.junit.jupiter.api.*; -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; +import static com.predic8.membrane.core.http.MimeType.*; +import static io.restassured.RestAssured.*; +import static io.restassured.http.ContentType.*; import static java.io.File.*; -import static java.lang.Thread.sleep; +import static org.hamcrest.Matchers.*; public class JSONSchemaValidationExampleTest extends DistributionExtractingTestcase { @@ -29,30 +30,69 @@ protected String getExampleDirName() { return "validation" + separator + "json-schema"; } - @Test - public void test() throws Exception { - try(Process2 ignored = startServiceProxyScript()) { - for (int port : new int[] { 2000, 2001 }) { - // @formatter:off - // Test good JSON - given() - .contentType(JSON) - .body(readFileFromBaseDir("good" + port + ".json")) - .when() - .post("http://localhost:" + port + "/") - .then() - .statusCode(200); - - // Test bad JSON - given() - .contentType(JSON) - .body(readFileFromBaseDir("bad" + port + ".json")) - .when() - .post("http://localhost:" + port + "/") - .then() - .statusCode(400); - // @formatter:on - } - } - } + @Test + void port2000() throws Exception { + try(Process2 ignored = startServiceProxyScript()) { + + // @formatter:off + // Test good JSON + given() + .contentType(JSON) + .body(readFileFromBaseDir("good2000.json")) + .when() + .post("http://localhost:2000") + .then() + .statusCode(200); + + // Test bad JSON + given() + .contentType(JSON) + .body(readFileFromBaseDir("bad2000.json")) + .when() + .post("http://localhost:2000") + .then() + .statusCode(400) + .contentType(APPLICATION_PROBLEM_JSON) + .body("title", equalTo("JSON validation failed")) + .body("type", equalTo("https://membrane-api.io/problems/user/validation")) + .body(containsString("p1")) + .body("errors.find { it.pointer == '/required' }.message", containsString("not found")); + // @formatter:on + + } + } + + @Test + void port2001() throws Exception { + try(Process2 ignored = startServiceProxyScript()) { + + // @formatter:off + // Test good JSON + given() + .contentType(JSON) + .body(readFileFromBaseDir("good2001.json")) + .when() + .post("http://localhost:2001") + .then() + .statusCode(200); + + // Test bad JSON + given() + .contentType(JSON) + .body(readFileFromBaseDir("bad2001.json")) + .when() + .post("http://localhost:2001") + .then() + .statusCode(400) + .contentType(APPLICATION_PROBLEM_JSON) + .body("title", equalTo("JSON validation failed")) + .body("type", equalTo("https://membrane-api.io/problems/user/validation")) + .body("errors.find { it.pointer == '/properties/id/type' }.message", containsString("integer expected")) + .body("errors.find { it.pointer == '/properties/price/type' }.message", containsString("number expected")) + .body("errors.find { it.pointer == '/properties/tags/type' }.message", containsString("array expected")) + .body("errors.find { it.pointer == '/properties/weight/minimum' }.message", containsString("700")); + // @formatter:on + + } + } } \ No newline at end of file diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index fd817c22fa..bc4b0a1dce 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -12,6 +12,10 @@ - LogInterceptor: - Remove: headerOnly - Rewrite JSONAssert Tests with RESTAssured +- ValidatorInterceptor: remove FailureHandler + - Predominantly used for logging; move logging into validators. + - Migration: replace FailureHandler usages with validator-level logging; ensure correlation IDs/Exchange context remain available for logs. + - Check if it is used by customer installations # 6.5.0 @@ -41,6 +45,10 @@ - public abstract void init() throws Exception; - getEndSessionEndpoint() throws Exception - doDynamicRegistration(List callbackURLs) throws Exception +## Release Notes: + +- JSON Schema validation support for JSON Schema 2019-09 and 2020-12 (via networknt json-schema-validator). + - Document how to select the schema version (e.g., schemaVersion attribute) and the "format" behavior (annotation vs assertion), with a link to usage docs/examples. # 6.3.0