diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/SchemaValidator.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/SchemaValidator.java index 30c55c50..08122b15 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/SchemaValidator.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/SchemaValidator.java @@ -19,6 +19,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; import com.networknt.schema.InvalidSchemaException; import com.networknt.schema.SchemaRegistry; import com.networknt.schema.SchemaRegistryConfig; @@ -41,6 +42,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; +import java.io.UncheckedIOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -142,7 +144,7 @@ public SchemaValidator(@Nonnull final OpenAPI api, key.forResponse, definitions ); - return schemaRegistry.getSchema(schemaObject); + return schemaRegistry.getSchema(writeAsString(schemaObject), InputFormat.JSON); }); } else { this.jsonSchemaCache = null; @@ -239,7 +241,8 @@ public ValidationReport validate(@Nonnull final JsonNodeSupplier supplier, try { final com.networknt.schema.Schema resolvedJsonSchema = resolveJsonSchema(schema, keyPrefix); - final List validationMessages = resolvedJsonSchema.validate(supplier.get()); + final List validationMessages = + resolvedJsonSchema.validate(writeAsString(supplier.get()), InputFormat.JSON); return messageConverter.toValidationReport(validationMessages, keyPrefix); } catch (final InvalidSchemaException e) { return ValidationReport.singleton( @@ -265,7 +268,7 @@ private com.networknt.schema.Schema resolveJsonSchema(final Schema schema, return jsonSchemaCache.get(jsonSchemaKey); } final JsonNode schemaObject = readAndTransformSchemaObject(schema, forRequest, forResponse, definitions); - return schemaRegistry.getSchema(schemaObject); + return schemaRegistry.getSchema(writeAsString(schemaObject), InputFormat.JSON); } catch (final Exception e) { // TODO: Need to handle this exception throw new RuntimeException("JsonSchema construction failed", e); @@ -294,6 +297,22 @@ private JsonNode readAndTransformSchemaObject(final Schema schema, return schemaObject; } + /** + * Serialize a (Jackson 2) JSON node to a JSON string. + *

+ * swagger-core / swagger-parser produce Jackson 2 nodes, whereas networknt 3.x consumes Jackson 3. + * Rather than leak Jackson 2 nodes into networknt (which removed the Jackson 2 node overloads), + * the schema and the instance being validated are handed to networknt as JSON text via its + * {@link InputFormat#JSON} entry points. + */ + private String writeAsString(final JsonNode node) { + try { + return (isOpenApi30 ? Json.mapper() : Json31.mapper()).writeValueAsString(node); + } catch (final IOException e) { + throw new UncheckedIOException("Failed to serialize JSON node", e); + } + } + private boolean additionalPropertiesValidationEnabled() { return !messages.isIgnored(ADDITIONAL_PROPERTIES_KEY); } diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/DoubleFormat.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/DoubleFormat.java index 6b38b88a..b94303f5 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/DoubleFormat.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/DoubleFormat.java @@ -1,12 +1,12 @@ package com.atlassian.oai.validator.schema.format; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NumericNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.SchemaContext; import com.networknt.schema.format.Format; import com.networknt.schema.utils.JsonType; import com.networknt.schema.utils.TypeFactory; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.NumericNode; import java.math.BigDecimal; diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/FloatFormat.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/FloatFormat.java index 15dbad0d..31d20b80 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/FloatFormat.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/FloatFormat.java @@ -1,12 +1,14 @@ package com.atlassian.oai.validator.schema.format; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NumericNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.SchemaContext; import com.networknt.schema.format.Format; import com.networknt.schema.utils.JsonType; import com.networknt.schema.utils.TypeFactory; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.NumericNode; + +import java.math.BigDecimal; public class FloatFormat implements Format { @Override @@ -33,8 +35,15 @@ public boolean matches(final ExecutionContext executionContext, final SchemaCont return false; } - final float f = numericValue.floatValue(); - final String original = String.valueOf(numericValue.decimalValue()); + final BigDecimal dec = numericValue.decimalValue(); + // Derive the float from the decimal: BigDecimal#floatValue() yields Infinity on overflow, + // whereas NumericNode#floatValue() throws in Jackson 3 when the value is out of float range. + final float f = dec.floatValue(); + if (Float.isInfinite(f) || Float.isNaN(f)) { + return false; + } + + final String original = String.valueOf(dec); final String parsed = String.valueOf(f); return original.equals(parsed); diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int32Format.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int32Format.java index 348d0d5f..8ad0e7bf 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int32Format.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int32Format.java @@ -1,11 +1,11 @@ package com.atlassian.oai.validator.schema.format; -import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.SchemaContext; import com.networknt.schema.format.Format; import com.networknt.schema.utils.JsonType; import com.networknt.schema.utils.TypeFactory; +import tools.jackson.databind.JsonNode; public class Int32Format implements Format { @Override diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int64Format.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int64Format.java index 21222a15..388e49ea 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int64Format.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/format/Int64Format.java @@ -1,11 +1,11 @@ package com.atlassian.oai.validator.schema.format; -import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.SchemaContext; import com.networknt.schema.format.Format; import com.networknt.schema.utils.JsonType; import com.networknt.schema.utils.TypeFactory; +import tools.jackson.databind.JsonNode; public class Int64Format implements Format { @Override diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/util/OpenApiLoader.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/util/OpenApiLoader.java index 6adabba0..cc6b2084 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/util/OpenApiLoader.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/util/OpenApiLoader.java @@ -136,7 +136,7 @@ private static void removeTypeObjectAssociationWithOneOfAndAnyOfFromSchema(@Nonn * Removes the Base64 pattern on the {@link OpenAPI} model. *

* If that pattern would stay on the model all fields of type string / byte would be validated twice. Once - * with the {@link com.networknt.schema.PatternValidator} and once with + * with the {@link com.networknt.schema.keyword.PatternValidator} and once with * the {@link com.atlassian.oai.validator.schema.format.Base64Format}. * To improve validation performance and memory footprint the pattern on string / byte fields will be * removed - so the PatternValidator will not be triggered for those kind of fields. diff --git a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/schema/SchemaValidatorTest.java b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/schema/SchemaValidatorTest.java index ac31dcff..87342eee 100644 --- a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/schema/SchemaValidatorTest.java +++ b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/schema/SchemaValidatorTest.java @@ -165,8 +165,11 @@ public void validate_withInvalidDoubleFormat_shouldFail() { final String value = "1" + Double.MAX_VALUE; final Schema schema = new NumberSchema().format("double"); + // A value outside the double range cannot be represented as a JSON number that the + // validator accepts: networknt 3.x rejects it as a type error ("number expected") before + // the custom double-format validator is reached. The value is still rejected. assertFailWithoutContext(classUnderTest.validate(value, schema, "prefix"), - "validation.prefix.schema.format.double"); + "validation.prefix.schema.type"); } @Test diff --git a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/NetworkntDirectProbeTest.java b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/NetworkntDirectProbeTest.java index 1979ebc3..278433b9 100644 --- a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/NetworkntDirectProbeTest.java +++ b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/NetworkntDirectProbeTest.java @@ -1,6 +1,6 @@ package com.atlassian.oai.validator.v31analysis; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.InputFormat; import com.networknt.schema.SchemaRegistry; import com.networknt.schema.dialect.Dialects; import org.junit.jupiter.api.Test; @@ -19,13 +19,11 @@ public class NetworkntDirectProbeTest { private static final Logger LOG = LoggerFactory.getLogger(NetworkntDirectProbeTest.class); - private static final ObjectMapper MAPPER = new ObjectMapper(); private static final SchemaRegistry REGISTRY = SchemaRegistry.withDefaultDialect(Dialects.getOpenApi31()); private static int validate(final String schemaJson, final String input) throws Exception { final var schema = REGISTRY.getSchema(schemaJson); - final var node = MAPPER.readTree(input); - final var errs = schema.validate(node); + final var errs = schema.validate(input, InputFormat.JSON); errs.forEach(e -> LOG.error(" - {}", e)); return errs.size(); } @@ -138,8 +136,7 @@ void discriminator_with_mapping_via_oas31_metaschema() throws Exception { + "}"; final var s = REGISTRY.getSchema(schema); - final var node = MAPPER.readTree("{\"kind\":\"circle\",\"radius\":5}"); - final var errs = s.validate(node); + final var errs = s.validate("{\"kind\":\"circle\",\"radius\":5}", InputFormat.JSON); errs.forEach(e -> LOG.error(" - {}", e)); LOG.error("[direct/discriminator+mapping circle] errors: {}", errs.size()); } diff --git a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/UnevaluatedDeepProbeTest.java b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/UnevaluatedDeepProbeTest.java index 1a87b43b..ad98f027 100644 --- a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/UnevaluatedDeepProbeTest.java +++ b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/v31analysis/UnevaluatedDeepProbeTest.java @@ -1,6 +1,6 @@ package com.atlassian.oai.validator.v31analysis; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.InputFormat; import com.networknt.schema.SchemaRegistry; import com.networknt.schema.dialect.Dialects; import org.junit.jupiter.api.Test; @@ -14,15 +14,13 @@ public class UnevaluatedDeepProbeTest { private static final Logger LOG = LoggerFactory.getLogger(UnevaluatedDeepProbeTest.class); - private static final ObjectMapper MAPPER = new ObjectMapper(); private static int run(final String label, final SchemaRegistry registry, final String schemaJson, final String input) throws Exception { final var schema = registry.getSchema(schemaJson); - final var node = MAPPER.readTree(input); - final var errs = schema.validate(node); + final var errs = schema.validate(input, InputFormat.JSON); LOG.error("[{}] errors: {}", label, errs.size()); errs.forEach(e -> LOG.error(" - {}", e)); return errs.size(); diff --git a/pom.xml b/pom.xml index 481987ae..1ce85005 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,10 @@ 3.0 1.3.2 2.21.3 - 2.0.1 + + 3.1.1 + 3.0.4 3.0.2 1.5.32 5.23.0 @@ -375,6 +378,13 @@ import pom + + tools.jackson + jackson-bom + ${jackson3.version} + import + pom + javax.servlet javax.servlet-api