Repository: johnzon Updated Branches: refs/heads/master 5f5b40906 -> 947c91b8a
JOHNZON-171 basic jsonschema validation module - not yet complete Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/947c91b8 Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/947c91b8 Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/947c91b8 Branch: refs/heads/master Commit: 947c91b8a6b17d87b464a8a87423ea7b577d27f7 Parents: 5f5b409 Author: Romain Manni-Bucau <[email protected]> Authored: Sun Apr 29 20:09:40 2018 +0200 Committer: Romain Manni-Bucau <[email protected]> Committed: Sun Apr 29 20:09:40 2018 +0200 ---------------------------------------------------------------------- johnzon-jsonschema/pom.xml | 43 +++ .../johnzon/jsonschema/JsonSchemaValidator.java | 59 ++++ .../jsonschema/JsonSchemaValidatorFactory.java | 156 +++++++++ .../johnzon/jsonschema/ValidationResult.java | 90 +++++ .../jsonschema/spi/ValidationContext.java | 61 ++++ .../jsonschema/spi/ValidationExtension.java | 31 ++ .../spi/builtin/BaseNumberValidationImpl.java | 70 ++++ .../jsonschema/spi/builtin/EnumValidation.java | 78 +++++ .../spi/builtin/ExclusiveMaximumValidation.java | 67 ++++ .../spi/builtin/ExclusiveMinimumValidation.java | 67 ++++ .../spi/builtin/MaxLengthValidation.java | 73 ++++ .../spi/builtin/MaximumValidation.java | 67 ++++ .../spi/builtin/MinLengthValidation.java | 73 ++++ .../spi/builtin/MinimumValidation.java | 67 ++++ .../spi/builtin/MultipleOfValidation.java | 68 ++++ .../spi/builtin/PatternValidation.java | 125 +++++++ .../spi/builtin/RequiredValidation.java | 86 +++++ .../jsonschema/spi/builtin/TypeValidation.java | 86 +++++ .../jsonschema/JsonSchemaValidatorTest.java | 351 +++++++++++++++++++ pom.xml | 1 + 20 files changed, 1719 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/pom.xml ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/pom.xml b/johnzon-jsonschema/pom.xml new file mode 100644 index 0000000..988941e --- /dev/null +++ b/johnzon-jsonschema/pom.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>johnzon</artifactId> + <groupId>org.apache.johnzon</groupId> + <version>1.1.8-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>johnzon-jsonschema</artifactId> + <name>Johnzon :: JSON Schema</name> + + <dependencies> + <dependency> + <groupId>org.apache.johnzon</groupId> + <artifactId>johnzon-core</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidator.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidator.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidator.java new file mode 100644 index 0000000..f2f2372 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidator.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; + +import java.util.Collection; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonObject; +import javax.json.JsonValue; + +public class JsonSchemaValidator implements Function<JsonObject, ValidationResult>, AutoCloseable { + private static final ValidationResult SUCCESS = new ValidationResult(emptyList()); + private final Function<JsonValue, Stream<ValidationResult.ValidationError>> validationFunction; + + JsonSchemaValidator(final Function<JsonValue, Stream<ValidationResult.ValidationError>> validationFunction) { + this.validationFunction = validationFunction; + } + + @Override + public ValidationResult apply(final JsonObject object) { + final Collection<ValidationResult.ValidationError> errors = validationFunction.apply(object).collect(toList()); + if (!errors.isEmpty()) { + return new ValidationResult(errors); + } + return SUCCESS; + } + + @Override + public void close() { + // no-op + } + + @Override + public String toString() { + return "JsonSchemaValidator{" + + "validationFunction=" + validationFunction + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java new file mode 100644 index 0000000..b409406 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema; + +import static java.util.Arrays.asList; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; +import org.apache.johnzon.jsonschema.spi.builtin.EnumValidation; +import org.apache.johnzon.jsonschema.spi.builtin.ExclusiveMaximumValidation; +import org.apache.johnzon.jsonschema.spi.builtin.ExclusiveMinimumValidation; +import org.apache.johnzon.jsonschema.spi.builtin.MaxLengthValidation; +import org.apache.johnzon.jsonschema.spi.builtin.MaximumValidation; +import org.apache.johnzon.jsonschema.spi.builtin.MinLengthValidation; +import org.apache.johnzon.jsonschema.spi.builtin.MinimumValidation; +import org.apache.johnzon.jsonschema.spi.builtin.MultipleOfValidation; +import org.apache.johnzon.jsonschema.spi.builtin.PatternValidation; +import org.apache.johnzon.jsonschema.spi.builtin.RequiredValidation; +import org.apache.johnzon.jsonschema.spi.builtin.TypeValidation; + +public class JsonSchemaValidatorFactory implements AutoCloseable { + private static final String[] ROOT_PATH = new String[0]; + private static final Function<JsonValue, Stream<ValidationResult.ValidationError>> NO_VALIDATION = new Function<JsonValue, Stream<ValidationResult.ValidationError>>() { + @Override + public Stream<ValidationResult.ValidationError> apply(JsonValue jsonValue) { + return Stream.empty(); + } + + @Override + public String toString() { + return "NoValidation"; + } + }; + + private final List<ValidationExtension> extensions = new ArrayList<>(); + + public JsonSchemaValidatorFactory() { + extensions.addAll(asList( + new RequiredValidation(), + new TypeValidation(), + new EnumValidation(), + new MultipleOfValidation(), + new MaximumValidation(), + new MinimumValidation(), + new ExclusiveMaximumValidation(), + new ExclusiveMinimumValidation(), + new MaxLengthValidation(), + new MinLengthValidation(), + new PatternValidation() + // todo: http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.4 and following + )); + extensions.addAll(new ArrayList<>(StreamSupport.stream(ServiceLoader.load(ValidationExtension.class).spliterator(), false) + .collect(toList()))); + } + + public JsonSchemaValidatorFactory appendExtensions(final ValidationExtension... extensions) { + this.extensions.addAll(asList(extensions)); + return this; + } + + public JsonSchemaValidatorFactory setExtensions(final ValidationExtension... extensions) { + this.extensions.clear(); + return appendExtensions(extensions); + } + + public JsonSchemaValidator newInstance(final JsonObject schema) { + return new JsonSchemaValidator(buildValidator(ROOT_PATH, schema)); + } + + @Override + public void close() { + // no-op for now + } + + private Function<JsonValue, Stream<ValidationResult.ValidationError>> buildValidator(final String[] path, + final JsonObject schema) { + final List<Function<JsonValue, Stream<ValidationResult.ValidationError>>> directValidations = buildDirectValidations(path, schema).collect(toList()); + final Function<JsonValue, Stream<ValidationResult.ValidationError>> nestedValidations = buildNestedValidations(path, schema); + return new ValidationsFunction(Stream.concat(directValidations.stream(), Stream.of(nestedValidations)).collect(toList())); + } + + private Stream<Function<JsonValue, Stream<ValidationResult.ValidationError>>> buildDirectValidations(final String[] path, final JsonObject schema) { + final ValidationContext model = new ValidationContext(path, schema); + return extensions.stream().map(e -> e.create(model)).filter(Optional::isPresent).map(Optional::get); + } + + private Function<JsonValue, Stream<ValidationResult.ValidationError>> buildNestedValidations(final String[] path, final JsonObject schema) { + return ofNullable(schema.get("properties")) + .filter(it -> it.getValueType() == JsonValue.ValueType.OBJECT) + .map(it -> it.asJsonObject().entrySet().stream() + .filter(obj -> obj.getValue().getValueType() == JsonValue.ValueType.OBJECT) + .map(obj -> { + final String[] fieldPath = Stream.concat(Stream.of(path), Stream.of(obj.getKey())).toArray(String[]::new); + return buildValidator(fieldPath, obj.getValue().asJsonObject()); + }) + .collect(toList())) + .map(this::toFunction) + .orElse(NO_VALIDATION); + } + + private Function<JsonValue, Stream<ValidationResult.ValidationError>> toFunction( + final List<Function<JsonValue, Stream<ValidationResult.ValidationError>>> validations) { + return new ValidationsFunction(validations); + } + + private static class ValidationsFunction implements Function<JsonValue, Stream<ValidationResult.ValidationError>> { + private final List<Function<JsonValue, Stream<ValidationResult.ValidationError>>> delegates; + + private ValidationsFunction(final List<Function<JsonValue, Stream<ValidationResult.ValidationError>>> validations) { + // unwrap when possible to simplify the stack and make toString readable (debug) + this.delegates = validations.stream() + .flatMap(it -> ValidationsFunction.class.isInstance(it) ? ValidationsFunction.class.cast(it).delegates.stream() : Stream.of(it)) + .filter(it -> it != NO_VALIDATION) + .collect(toList()); + } + + @Override + public Stream<ValidationResult.ValidationError> apply(final JsonValue jsonValue) { + return delegates.stream().flatMap(v -> v.apply(jsonValue)); + } + + @Override + public String toString() { + return delegates.toString(); + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/ValidationResult.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/ValidationResult.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/ValidationResult.java new file mode 100644 index 0000000..6d574c9 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/ValidationResult.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema; + +import java.util.Collection; + +public class ValidationResult { + private Collection<ValidationError> errors; + + public ValidationResult() { + // no-op + } + + ValidationResult(final Collection<ValidationError> errors) { + this.errors = errors; + } + + public boolean isSuccess() { + return errors == null || errors.isEmpty(); + } + + public Collection<ValidationError> getErrors() { + return errors; + } + + public void setErrors(final Collection<ValidationError> errors) { + this.errors = errors; + } + + @Override + public String toString() { + return "ValidationResult{" + + "errors=" + errors + + '}'; + } + + public static class ValidationError { + private String field; + private String message; + + public ValidationError() { + // no-op + } + + public ValidationError(final String field, final String message) { + this.field = field; + this.message = message; + } + + public String getField() { + return field; + } + + public void setField(final String field) { + this.field = field; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + @Override + public String toString() { + return "ValidationError{" + + "field='" + field + '\'' + + ", message='" + message + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationContext.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationContext.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationContext.java new file mode 100644 index 0000000..63535a5 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationContext.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi; + +import static java.util.stream.Collectors.joining; + +import java.util.stream.Stream; + +import javax.json.JsonObject; +import javax.json.JsonValue; + +public class ValidationContext { + private final String[] path; + private final JsonObject schema; + + public ValidationContext(final String[] path, final JsonObject schema) { + this.path = path; + this.schema = schema; + } + + public String[] getPath() { + return path; + } + + public JsonObject getSchema() { + return schema; + } + + public JsonValue readValue(final JsonValue root) { // move to JsonPointer? requires to store a provider if we want + JsonValue current = root; + for (final String segment : path) { + if (current == null) { + return null; + } + if (current.getValueType() == JsonValue.ValueType.OBJECT) { + current = current.asJsonObject().get(segment); + } + } + return current; + } + + public String toPointer() { + return Stream.of(path).collect(joining("/", "/", "")); + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationExtension.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationExtension.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationExtension.java new file mode 100644 index 0000000..0b9b22c --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/ValidationExtension.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; + +public interface ValidationExtension { + Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(ValidationContext model); +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/BaseNumberValidationImpl.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/BaseNumberValidationImpl.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/BaseNumberValidationImpl.java new file mode 100644 index 0000000..e98e998 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/BaseNumberValidationImpl.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; + +abstract class BaseNumberValidationImpl implements Function<JsonValue, Stream<ValidationResult.ValidationError>> { + protected final String pointer; + protected final Function<JsonObject, JsonValue> extractor; + protected final double bound; + private final JsonValue.ValueType validType; + + BaseNumberValidationImpl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound, + final JsonValue.ValueType validType) { + this.bound = bound; + this.pointer = pointer; + this.extractor = extractor; + this.validType = validType; + } + + @Override + public Stream<ValidationResult.ValidationError> apply(final JsonValue obj) { + if (obj == null || obj == JsonValue.NULL) { + return Stream.empty(); + } + final JsonValue value = extractor.apply(obj.asJsonObject()); + if (value == null || value.getValueType() != validType) { + return Stream.empty(); + } + final double val = toNumber(value); + if (val <= 0) { + return toError(val); + } + if (isValid(val)) { + return Stream.empty(); + } + return toError(val); + } + + protected double toNumber(final JsonValue value) { + return JsonNumber.class.cast(value).doubleValue(); + } + + protected abstract boolean isValid(double val); + + protected abstract Stream<ValidationResult.ValidationError> toError(double val); +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/EnumValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/EnumValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/EnumValidation.java new file mode 100644 index 0000000..3cacbed --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/EnumValidation.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import static java.util.Optional.ofNullable; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class EnumValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + return ofNullable(model.getSchema().get("enum")) + .filter(it -> it.getValueType() == JsonValue.ValueType.ARRAY) + .map(JsonValue::asJsonArray) + .map(values -> new Impl(values, model.toPointer(), model::readValue)); + } + + private static class Impl implements Function<JsonValue, Stream<ValidationResult.ValidationError>> { + private final Collection<JsonValue> valid; + private final String pointer; + private final Function<JsonObject, JsonValue> extractor; + + private Impl(final Collection<JsonValue> valid, final String pointer, final Function<JsonObject, JsonValue> extractor) { + this.valid = valid; + this.pointer = pointer; + this.extractor = extractor; + } + + @Override + public Stream<ValidationResult.ValidationError> apply(final JsonValue obj) { + if (obj == null || obj == JsonValue.NULL) { + return Stream.empty(); + } + final JsonValue value = extractor.apply(obj.asJsonObject()); + if (value != null && !JsonValue.NULL.equals(value)) { + return Stream.empty(); + } + if (valid.contains(value)) { + return Stream.empty(); + } + return Stream.of(new ValidationResult.ValidationError(pointer, "Invalid value, got " + value + ", expected: " + valid)); + } + + @Override + public String toString() { + return "Enum{" + + "valid=" + valid + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMaximumValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMaximumValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMaximumValidation.java new file mode 100644 index 0000000..2e06140 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMaximumValidation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class ExclusiveMaximumValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("number")) { + return Optional.ofNullable(model.getSchema().get("exclusiveMaximum")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).doubleValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound) { + super(pointer, extractor, bound, JsonValue.ValueType.NUMBER); + } + + @Override + protected boolean isValid(final double val) { + return val < this.bound; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " is strictly more than " + this.bound)); + } + + @Override + public String toString() { + return "ExclusiveMaximum{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMinimumValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMinimumValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMinimumValidation.java new file mode 100644 index 0000000..a649476 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/ExclusiveMinimumValidation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class ExclusiveMinimumValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("number")) { + return Optional.ofNullable(model.getSchema().get("exclusiveMinimum")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).doubleValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound) { + super(pointer, extractor, bound, JsonValue.ValueType.NUMBER); + } + + @Override + protected boolean isValid(final double val) { + return val > this.bound; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " is strictly less than " + this.bound)); + } + + @Override + public String toString() { + return "ExclusiveMinimum{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaxLengthValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaxLengthValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaxLengthValidation.java new file mode 100644 index 0000000..2049dbb --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaxLengthValidation.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class MaxLengthValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("string")) { + return Optional.ofNullable(model.getSchema().get("maxLength")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).intValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound) { + super(pointer, extractor, bound, JsonValue.ValueType.STRING); + } + + @Override + protected double toNumber(final JsonValue value) { + return JsonString.class.cast(value).getString().length(); + } + + @Override + protected boolean isValid(final double val) { + return val <= this.bound; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " length is more than " + this.bound)); + } + + @Override + public String toString() { + return "MaxLength{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaximumValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaximumValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaximumValidation.java new file mode 100644 index 0000000..f0a6160 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MaximumValidation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class MaximumValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("number")) { + return Optional.ofNullable(model.getSchema().get("maximum")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).doubleValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound) { + super(pointer, extractor, bound, JsonValue.ValueType.NUMBER); + } + + @Override + protected boolean isValid(final double val) { + return val <= this.bound; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " is more than " + this.bound)); + } + + @Override + public String toString() { + return "Maximum{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinLengthValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinLengthValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinLengthValidation.java new file mode 100644 index 0000000..54449a3 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinLengthValidation.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class MinLengthValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("string")) { + return Optional.ofNullable(model.getSchema().get("minLength")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).intValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound) { + super(pointer, extractor, bound, JsonValue.ValueType.STRING); + } + + @Override + protected double toNumber(final JsonValue value) { + return JsonString.class.cast(value).getString().length(); + } + + @Override + protected boolean isValid(final double val) { + return val >= this.bound; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " length is less than " + this.bound)); + } + + @Override + public String toString() { + return "MinLength{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinimumValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinimumValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinimumValidation.java new file mode 100644 index 0000000..1207111 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MinimumValidation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class MinimumValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("number")) { + return Optional.ofNullable(model.getSchema().get("minimum")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).doubleValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double bound) { + super(pointer, extractor, bound, JsonValue.ValueType.NUMBER); + } + + @Override + protected boolean isValid(final double val) { + return val >= this.bound; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " is less than " + this.bound)); + } + + @Override + public String toString() { + return "Minimum{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MultipleOfValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MultipleOfValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MultipleOfValidation.java new file mode 100644 index 0000000..951dca8 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/MultipleOfValidation.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class MultipleOfValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("number")) { + return Optional.ofNullable(model.getSchema().get("multipleOf")) + .filter(v -> v.getValueType() == JsonValue.ValueType.NUMBER) + .map(m -> new Impl(model.toPointer(), model::readValue, JsonNumber.class.cast(m).doubleValue())); + } + return Optional.empty(); + } + + private static class Impl extends BaseNumberValidationImpl { + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final double multipleOf) { + super(pointer, extractor, multipleOf, JsonValue.ValueType.NUMBER); + } + + @Override + protected boolean isValid(double val) { + final double divided = val / bound; + return divided == (long) divided; + } + + @Override + protected Stream<ValidationResult.ValidationError> toError(final double val) { + return Stream.of(new ValidationResult.ValidationError(pointer, val + " is not a multiple of " + bound)); + } + + @Override + public String toString() { + return "MultipleOf{" + + "factor=" + bound + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/PatternValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/PatternValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/PatternValidation.java new file mode 100644 index 0000000..4b4d284 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/PatternValidation.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class PatternValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + if (model.getSchema().getString("type", "object").equals("string")) { + return Optional.ofNullable(model.getSchema().get("pattern")) + .filter(val -> val.getValueType() == JsonValue.ValueType.STRING) + .map(pattern -> new Impl(model.toPointer(), model::readValue, JsonString.class.cast(pattern).getString())); + } + return Optional.empty(); + } + + private static class Impl implements Function<JsonValue, Stream<ValidationResult.ValidationError>> { + private final String pointer; + private final Function<JsonObject, JsonValue> extractor; + private final JsRegex jsRegex; + + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final String pattern) { + this.jsRegex = new JsRegex(pattern); + this.pointer = pointer; + this.extractor = extractor; + } + + @Override + public Stream<ValidationResult.ValidationError> apply(final JsonValue obj) { + if (obj == null || obj == JsonValue.NULL) { + return Stream.empty(); + } + final JsonValue value = extractor.apply(obj.asJsonObject()); + if (value == null || value.getValueType() != JsonValue.ValueType.STRING || JsonValue.NULL.equals(value)) { + return Stream.empty(); + } + if (!jsRegex.test(JsonString.class.cast(value).getString())) { + return Stream.of(new ValidationResult.ValidationError(pointer, value + " doesn't match " + jsRegex)); + } + return Stream.empty(); + } + + @Override + public String toString() { + return "Pattern{" + + "regex=" + jsRegex + + ", pointer='" + pointer + '\'' + + '}'; + } + } + + private static class JsRegex implements Predicate<CharSequence> { + + private static final ScriptEngine ENGINE; + + static { + ENGINE = new ScriptEngineManager().getEngineByName("javascript"); + } + + private final String regex; + + private final String indicators; + + private JsRegex(final String regex) { + if (regex.startsWith("/") && regex.length() > 1) { + final int end = regex.lastIndexOf('/'); + if (end < 0) { + this.regex = regex; + this.indicators = ""; + } else { + this.regex = regex.substring(1, end); + this.indicators = regex.substring(end + 1); + } + } else { + this.regex = regex; + this.indicators = ""; + } + } + + @Override + public boolean test(final CharSequence string) { + final Bindings bindings = ENGINE.createBindings(); + bindings.put("text", string); + bindings.put("regex", regex); + bindings.put("indicators", indicators); + try { + return Boolean.class.cast(ENGINE.eval("new RegExp(regex, indicators).test(text)", bindings)); + } catch (final ScriptException e) { + return false; + } + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/RequiredValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/RequiredValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/RequiredValidation.java new file mode 100644 index 0000000..cc547b1 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/RequiredValidation.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toSet; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class RequiredValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + return ofNullable(model.getSchema().get("required")) + .filter(it -> it.getValueType() == JsonValue.ValueType.ARRAY) + .map(JsonValue::asJsonArray) + .filter(arr -> arr.stream().allMatch(it -> it.getValueType() == JsonValue.ValueType.STRING)) + .map(arr -> arr.stream().map(it -> JsonString.class.cast(it).getString()).collect(toSet())) + .map(required -> new Impl(required, model.toPointer(), model::readValue)); + } + + private static class Impl implements Function<JsonValue, Stream<ValidationResult.ValidationError>> { + private final Collection<String> required; + private final String pointer; + private final Function<JsonObject, JsonValue> extractor; + + private Impl(final Collection<String> required, final String pointer, final Function<JsonObject, JsonValue> extractor) { + this.required = required; + this.pointer = pointer; + this.extractor = extractor; + } + + @Override + public Stream<ValidationResult.ValidationError> apply(final JsonValue obj) { + if (obj == null || obj == JsonValue.NULL) { + return toErrors(required.stream()); + } + return toErrors(required.stream().filter(name -> { + final JsonValue jsonValue = extractor.apply(obj.asJsonObject()); + return isNull(jsonValue) || isNull(jsonValue.asJsonObject().get(name)); + })); + } + + private boolean isNull(final JsonValue jsonValue) { + return jsonValue == null || JsonValue.NULL.equals(jsonValue); + } + + private Stream<ValidationResult.ValidationError> toErrors(final Stream<String> fields) { + return fields.map(name -> new ValidationResult.ValidationError(pointer, name + " is required and is not present")); + } + + @Override + public String toString() { + return "Required{" + + "required=" + required + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/TypeValidation.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/TypeValidation.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/TypeValidation.java new file mode 100644 index 0000000..35122aa --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/spi/builtin/TypeValidation.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema.spi.builtin; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.joining; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.apache.johnzon.jsonschema.ValidationResult; +import org.apache.johnzon.jsonschema.spi.ValidationContext; +import org.apache.johnzon.jsonschema.spi.ValidationExtension; + +public class TypeValidation implements ValidationExtension { + @Override + public Optional<Function<JsonValue, Stream<ValidationResult.ValidationError>>> create(final ValidationContext model) { + switch (model.getSchema().getString("type", "object")) { + case "string": + return Optional.of(new Impl(model.toPointer(), model::readValue, JsonValue.ValueType.STRING)); + case "number": + return Optional.of(new Impl(model.toPointer(), model::readValue, JsonValue.ValueType.NUMBER)); + case "array": + return Optional.of(new Impl(model.toPointer(), model::readValue, JsonValue.ValueType.ARRAY)); + case "boolean": + return Optional.of(new Impl(model.toPointer(), model::readValue, JsonValue.ValueType.FALSE, JsonValue.ValueType.TRUE)); + case "object": + default: + return Optional.of(new Impl(model.toPointer(), model::readValue, JsonValue.ValueType.OBJECT)); + } + } + + private static class Impl implements Function<JsonValue, Stream<ValidationResult.ValidationError>> { + private final String pointer; + private final Function<JsonObject, JsonValue> extractor; + private final JsonValue.ValueType[] types; + + private Impl(final String pointer, final Function<JsonObject, JsonValue> extractor, final JsonValue.ValueType... types) { + this.types = types; + this.pointer = pointer; + this.extractor = extractor; + } + + @Override + public Stream<ValidationResult.ValidationError> apply(final JsonValue obj) { + if (obj == null || obj == JsonValue.NULL) { + return Stream.empty(); + } + final JsonValue value = extractor.apply(obj.asJsonObject()); + if (value == null || Stream.of(types).anyMatch(it -> it == value.getValueType()) || JsonValue.ValueType.NULL == value.getValueType()) { + return Stream.empty(); + } + return Stream.of(new ValidationResult.ValidationError( + pointer, + "Expected " + Stream.of(types).map(JsonValue.ValueType::name).collect(joining(", ")) + " but got " + value.getValueType())); + } + + @Override + public String toString() { + return "Type{" + + "type=" + asList(types) + + ", pointer='" + pointer + '\'' + + '}'; + } + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorTest.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorTest.java b/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorTest.java new file mode 100644 index 0000000..a9f7e3c --- /dev/null +++ b/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorTest.java @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.johnzon.jsonschema; + +import static java.util.Collections.emptyMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class JsonSchemaValidatorTest { + private static JsonSchemaValidatorFactory factory; + + private final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(emptyMap()); + + @BeforeClass + public static void init() { + factory = new JsonSchemaValidatorFactory(); + } + + @AfterClass + public static void destroy() { + factory.close(); + } + + @Test + public void rootRequired() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .build()) + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .build()) + .build()) + .add("required", jsonFactory.createArrayBuilder().add("name").build()) + .build()); + + final ValidationResult success = validator.apply(jsonFactory.createObjectBuilder().add("name", "ok").build()); + assertTrue(success.getErrors().toString(), success.isSuccess()); + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder().addNull("name").build()); + assertFalse(failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/", error.getField()); + assertEquals("name is required and is not present", error.getMessage()); + + validator.close(); + } + + @Test + public void rootType() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .build()) + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .build()) + .build()) + .build()); + + { + final ValidationResult success = validator.apply(jsonFactory.createObjectBuilder().add("name", "ok").build()); + assertTrue(success.getErrors().toString(), success.isSuccess()); + } + { + final ValidationResult success = validator.apply(jsonFactory.createObjectBuilder().addNull("name").build()); + assertTrue(success.getErrors().toString(), success.isSuccess()); + } + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder().add("name", 5).build()); + assertFalse(failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/name", error.getField()); + assertEquals("Expected STRING but got NUMBER", error.getMessage()); + + validator.close(); + } + + @Test + public void nestedType() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("person", jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .build()) + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .build()) + .build()) + .build()) + .build()) + .build()); + + final ValidationResult success = validator.apply(jsonFactory.createObjectBuilder() + .add("person", jsonFactory.createObjectBuilder() + .add("name", "ok") + .build()) + .build()); + assertTrue(success.getErrors().toString(), success.isSuccess()); + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder() + .add("person", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder().build()) + .build()) + .build()); + assertFalse(failure.toString(), failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/person/name", error.getField()); + assertEquals("Expected STRING but got OBJECT", error.getMessage()); + + validator.close(); + } + + @Test + public void enumValues() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .add("enum", jsonFactory.createArrayBuilder().add("a").add("b").build()) + .build()) + .build()) + .build()); + + final ValidationResult success = validator.apply(jsonFactory.createObjectBuilder().add("name", "a").build()); + assertTrue(success.getErrors().toString(), success.isSuccess()); + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder().add("name", 5).build()); + assertFalse(failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/name", error.getField()); + assertEquals("Expected STRING but got NUMBER", error.getMessage()); + + validator.close(); + } + + @Test + public void multipleOf() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .add("multipleOf", 5) + .build()) + .build()) + .build()); + + final ValidationResult success = validator.apply(jsonFactory.createObjectBuilder().add("age", 5).build()); + assertTrue(success.getErrors().toString(), success.isSuccess()); + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder().add("age", 6).build()); + assertFalse(failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/age", error.getField()); + assertEquals("6.0 is not a multiple of 5.0", error.getMessage()); + + validator.close(); + } + + @Test + public void minimum() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .add("minimum", 5) + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("age", 5).build()).isSuccess()); + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("age", 6).build()).isSuccess()); + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder().add("age", 2).build()); + assertFalse(failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/age", error.getField()); + assertEquals("2.0 is less than 5.0", error.getMessage()); + + validator.close(); + } + + @Test + public void maximum() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .add("maximum", 5) + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("age", 5).build()).isSuccess()); + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("age", 4).build()).isSuccess()); + + final ValidationResult failure = validator.apply(jsonFactory.createObjectBuilder().add("age", 6).build()); + assertFalse(failure.isSuccess()); + final Collection<ValidationResult.ValidationError> errors = failure.getErrors(); + assertEquals(1, errors.size()); + final ValidationResult.ValidationError error = errors.iterator().next(); + assertEquals("/age", error.getField()); + assertEquals("6.0 is more than 5.0", error.getMessage()); + + validator.close(); + } + + @Test + public void exclusiveMinimum() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .add("exclusiveMinimum", 5) + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("age", 6).build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("age", 5).build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("age", 4).build()).isSuccess()); + validator.close(); + } + + @Test + public void exclusiveMaximum() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("age", jsonFactory.createObjectBuilder() + .add("type", "number") + .add("exclusiveMaximum", 5) + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("age", 4).build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("age", 5).build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("age", 6).build()).isSuccess()); + + validator.close(); + } + + @Test + public void minLength() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .add("minLength", 2) + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("name", "ok").build()).isSuccess()); + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("name", "okk").build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("name", "-").build()).isSuccess()); + + validator.close(); + } + + @Test + public void maxLength() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .add("maxLength", 2) + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("name", "ok").build()).isSuccess()); + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("name", "-").build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("name", "fail").build()).isSuccess()); + + validator.close(); + } + + @Test + public void pattern() { + final JsonSchemaValidator validator = factory.newInstance(jsonFactory.createObjectBuilder() + .add("type", "object") + .add("properties", jsonFactory.createObjectBuilder() + .add("name", jsonFactory.createObjectBuilder() + .add("type", "string") + .add("pattern", "[a-z]") + .build()) + .build()) + .build()); + + assertTrue(validator.apply(jsonFactory.createObjectBuilder().add("name", "ok").build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("name", "-").build()).isSuccess()); + assertFalse(validator.apply(jsonFactory.createObjectBuilder().add("name", "0").build()).isSuccess()); + + validator.close(); + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/947c91b8/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 6f31957..bc46feb 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ <module>johnzon-websocket</module> <module>johnzon-jsonb</module> <module>johnzon-json-extras</module> + <module>johnzon-jsonschema</module> </modules> <dependencies>
