All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.myprysm.vertx.validation.JsonValidation Maven / Gradle / Ivy

/*
 * Copyright 2018 the original author or the original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.myprysm.vertx.validation;

import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static fr.myprysm.vertx.json.JsonHelpers.extractObject;
import static fr.myprysm.vertx.validation.ValidationResult.invalid;
import static fr.myprysm.vertx.validation.ValidationResult.valid;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

/**
 * Base JSON validation class.
 * Provides helpers to test an input {@link JsonObject} and ensure that
 * object structure matches what is expected.
 */
public interface JsonValidation extends Function {
    String ENV_PREFIX = "ENV:";

    /**
     * Validates that field is defining some environment property reference.
     *
     * @param field the name of the field
     * @return validation result combinator
     */
    static JsonValidation isEnv(String field) {
        return isEnv(field, message(field, "is not an environment property reference"));
    }

    /**
     * Validates that field is defining some environment property.
     *
     * @param field   the name of the field
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isEnv(String field, String message) {
        requireNonNull(field);
        return isString(field).and(holds(json -> json.getString(field).startsWith(ENV_PREFIX), message));
    }

    /**
     * Validates that field is not null.
     *
     * @param field the name of the field
     * @return validation result combinator
     */
    static JsonValidation isNotNull(String field) {
        return isNotNull(field, message(field, "is null"));
    }

    /**
     * Validates that field is not null.
     *
     * @param field   the name of the field
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isNotNull(String field, String message) {
        requireNonNull(field);
        return holds(json -> json.getValue(field) != null, message);
    }

    /**
     * Validates that field is null.
     *
     * @param field the name of the field
     * @return validation result combinator
     */
    static JsonValidation isNull(String field) {
        requireNonNull(field);
        return isNull(field, message(field, "is not null"));
    }

    /**
     * Validates that field is null.
     *
     * @param field   the name of the field
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isNull(String field, String message) {
        requireNonNull(field);
        return holds(json -> json.getValue(field) == null, message);
    }

    /**
     * Validates that path exists in object.
     *
     * @param path the path
     * @return validation result combinator
     */
    static JsonValidation hasPath(String path) {
        requireNonNull(path);
        return hasPath(path, message(path, "does not exist"));
    }

    /**
     * Validates that path exists in object.
     *
     * @param path    the path
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation hasPath(String path, String message) {
        requireNonNull(path);
        return holds(json -> extractObject(json, path).isPresent(), message);
    }

    /**
     * Validates that field is an object.
     *
     * @param field the name of the field
     * @return validation result combinator
     */
    static JsonValidation isObject(String field) {
        requireNonNull(field);
        return isObject(field, message(field, "is not an object"));
    }

    /**
     * Validates that field is an object.
     *
     * @param field   the name of the field
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isObject(String field, String message) {
        requireNonNull(field);
        return holds(json -> json.getValue(field) instanceof JsonObject, message);
    }

    /**
     * Validates that field fields are of clazz type.
     *
     * clazz is one of the primitive types or {@link JsonObject} or {@link JsonArray}.
     *
     * @param field the name of the field
     * @param clazz the class to validate
     * @return validation result combinator
     */
    static JsonValidation arrayOf(String field, Class clazz) {
        requireNonNull(field);
        requireNonNull(clazz);
        return arrayOf(field, clazz, message(field, "is not an array of " + clazz.getSimpleName()));
    }

    /**
     * Validates that field fields are of clazz type.
     *
     * clazz is one of the primitive types or {@link JsonObject} or {@link JsonArray}.
     *
     * @param field   the name of the field
     * @param clazz   the class to validate
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation arrayOf(String field, Class clazz, String message) {
        requireNonNull(field);
        requireNonNull(clazz);
        return isArray(field).and(holds(json -> json.getJsonArray(field)
                        .stream()
                        .allMatch(entry -> entry != null && entry.getClass().isAssignableFrom(clazz))
                ,
                message));
    }

    /**
     * Validates that field fields are of clazz type.
     *
     * clazz is one of the primitive types or {@link JsonObject} or {@link JsonArray}.
     *
     * @param field the name of the field
     * @param clazz the class to validate
     * @return validation result combinator
     */
    static JsonValidation mapOf(String field, Class clazz) {
        requireNonNull(field);
        requireNonNull(clazz);
        return mapOf(field, clazz, message(field, "is not a map of " + clazz.getSimpleName()));
    }

    /**
     * Validates that field fields are of clazz type.
     *
     * clazz is one of the primitive types or {@link JsonObject} or {@link JsonArray}.
     *
     * @param field   the name of the field
     * @param clazz   the class to validate
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation mapOf(String field, Class clazz, String message) {
        requireNonNull(field);
        requireNonNull(clazz);
        return isObject(field).and(holds(json -> json.getJsonObject(field)
                        .stream()
                        .allMatch(entry -> entry.getValue() != null && entry.getValue().getClass().isAssignableFrom(clazz))
                ,
                message));
    }

    /**
     * Validates that field is an array.
     *
     * @param field the name of the field
     * @return validation result combinator
     */
    static JsonValidation isArray(String field) {
        requireNonNull(field);
        return isArray(field, message(field, "is not an array"));
    }

    /**
     * Validates that field is an array.
     *
     * @param field   the name of the field
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isArray(String field, String message) {
        requireNonNull(field);
        return holds(json -> json.getValue(field) instanceof JsonArray, message);
    }

    /**
     * Validates that field is an array of maxSize elements.
     *
     * maxSize must be a positive integer.
     * when maxSize is 0, then #isArray(String, int) behaves like {@link #isArray(String)}.
     *
     * @param field   the name of the field
     * @param maxSize the maximum size allowed for the array
     * @return validation result combinator
     */
    static JsonValidation isArray(String field, int maxSize) {
        return isArray(field, maxSize, message(field, "is longer than " + maxSize + " elements"));
    }

    /**
     * Validates that field is an array of maxSize elements.
     *
     * maxSize must be a positive integer.
     * when maxSize is 0, then #isArray(String, int) behaves like {@link #isArray(String)}.
     *
     * @param field   the name of the field
     * @param maxSize the maximum size allowed for the array
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isArray(String field, int maxSize, String message) {
        requireNonNull(field);
        illegalArgument(maxSize < 0);
        return isArray(field)
                .and(holds(
                        json -> maxSize <= 0 || ((JsonArray) json.getValue(field)).size() <= maxSize,
                        message
                ));
    }

    /**
     * Validates that field is a string
     *
     * @param field the name of the field
     * @return validation result combinator
     */
    static JsonValidation isString(String field) {
        return isString(field, message(field, "is not a string"));
    }

    /**
     * Validates that field is a string
     *
     * @param field   the name of the field
     * @param message the custom message for validation
     * @return validation result combinator
     */
    static JsonValidation isString(String field, String message) {
        requireNonNull(field);
        return isNotNull(field)
                .and(holds(json -> String.class.isAssignableFrom(json.getValue(field).getClass()) && isNotBlank(json.getString(field)), message));
    }

    /**
     * Validates that field matches pattern.
     * 

* Pattern is expected as a regular expression. * * @param field the name of the field * @param pattern the pattern to test * @return validation result combinator */ static JsonValidation matches(String field, String pattern) { requireNonNull(field); requireNonNull(pattern); return matches(field, pattern, message(field, "does not match pattern " + pattern)); } /** * Validates that field matches pattern. *

* Pattern is expected as a regular expression. * * @param field the name of the field * @param pattern the pattern to test * @param message the custom message for validation * @return validation result combinator */ static JsonValidation matches(String field, String pattern, String message) { requireNonNull(field); requireNonNull(pattern); return isString(field) .and(holds(json -> Pattern.matches(pattern, json.getString(field)), message)); } /** * Validates that field is a string of enum clazz. * * @param field the name of the field * @param clazz the {@link Enum} class * @param the type of the enumeration * @return validation result combinator */ static > JsonValidation isEnum(String field, Class clazz) { requireNonNull(field); requireNonNull(clazz); return isEnum(field, clazz, message(field, "is not part of enum " + clazz.getSimpleName())); } /** * Validates that field is a string of enum clazz. * * @param field the name of the field * @param clazz the {@link Enum} class * @param message the custom message for validation * @param the type of the enumeration * @return validation result combinator */ static > JsonValidation isEnum(String field, Class clazz, String message) { requireNonNull(field); requireNonNull(clazz); return isString(field).or(arrayOf(field, String.class)) .and(holds(json -> { Object value = json.getValue(field); List enumValues = Arrays.stream(clazz.getEnumConstants()) .map(Enum::name) .collect(Collectors.toList()); if (value instanceof String) { return enumValues.contains(value); } return enumValues.containsAll(((JsonArray) value).getList()); }, message)); } /** * Validates that field is a string of enum clazz. * * @param field the name of the field * @param values the {@link Enum} values * @param the type of the enumeration * @return validation result combinator */ static > JsonValidation isEnumInRange(String field, T... values) { requireNonNull(field); requireNonNull(values); String text = Arrays.stream(values).map(Enum::name).collect(joining(",")); return isEnumInRange(field, message(field, "is not part of enum " + text), values); } /** * Validates that field is a string of enum clazz. * * @param field the name of the field * @param values the {@link Enum} values * @param message the custom message for validation * @param the type of the enumeration * @return validation result combinator */ @SuppressWarnings("unchecked") static > JsonValidation isEnumInRange(String field, String message, T... values) { requireNonNull(field); requireNonNull(values); return isEnum(field, values[0].getDeclaringClass()) .and(holds(json -> { Object value = json.getValue(field); List enumValues = Arrays.stream(values) .map(Enum::name) .collect(Collectors.toList()); if (value instanceof String) { return enumValues.contains(value); } return enumValues.containsAll(((JsonArray) value).getList()); }, message)); } /** * Validates that field is a string and a valid java class. * * @param field the name of the field * @return validation result combinator */ static JsonValidation isClass(String field) { requireNonNull(field); return isClass(field, message(field, "is not a valid java class")); } /** * Validates that field is a string and a valid java class. * * @param field the name of the field * @param message the custom message for validation * @return validation result combinator */ static JsonValidation isClass(String field, String message) { requireNonNull(field); return isString(field) .and(holds(json -> { try { Class.forName(json.getString(field)); return true; } catch (ClassNotFoundException e) { return false; } }, message)); } /** * Validates that field is a boolean. * * @param field the name of the field * @return validation result combinator */ static JsonValidation isBoolean(String field) { return isBoolean(field, message(field, "is not a boolean")); } /** * Validates that field is a boolean. * * @param field the name of the field * @param message the custom message for validation * @return validation result combinator */ static JsonValidation isBoolean(String field, String message) { requireNonNull(field); return isNotNull(field) .and(holds(json -> Boolean.class.isAssignableFrom(json.getValue(field).getClass()), message)); } /** * Validates that field is a boolean of expected value. * * @param field the name of the field * @param value the value the field must have * @return validation result combinator */ static JsonValidation isBoolean(String field, Boolean value) { requireNonNull(field); requireNonNull(value); return isBoolean(field, value, message(field, "is not " + value.toString())); } /** * Validates that field is a boolean of expected value. * * @param field the name of the field * @param value the value the field must have * @param message the custom message for validation * @return validation result combinator */ static JsonValidation isBoolean(String field, Boolean value, String message) { requireNonNull(field); requireNonNull(value); return isBoolean(field) .and(holds(json -> value.equals(json.getBoolean(field)), message)); } /** * Validates that field is a long. * * @param field the name of the field * @return validation result combinator */ static JsonValidation isLong(String field) { requireNonNull(field); return isLong(field, message(field, "is not a long")); } /** * Validates that field is a long. * * @param field the name of the field * @param message the custom message for validation * @return validation result combinator */ static JsonValidation isLong(String field, String message) { requireNonNull(field); return isNotNull(field) .and(holds(json -> { Class clazz = json.getValue(field).getClass(); return Long.class.isAssignableFrom(clazz) || Integer.class.isAssignableFrom(clazz); }, message)); } /** * Validates that field is a long greater than value. * * @param field the name of the field * @param value the minimum value * @return validation result combinator */ static JsonValidation gt(String field, Long value) { requireNonNull(field); requireNonNull(value); return gt(field, value, message(field, "is not greater than " + value)); } /** * Validates that field is a long greater than value. * * @param field the name of the field * @param value the minimum value * @param message the custom message for validation * @return validation result combinator */ static JsonValidation gt(String field, Long value, String message) { requireNonNull(field); requireNonNull(value); return isLong(field) .and(holds(json -> Long.compare(json.getLong(field), value) > 0, message)); } /** * Validates that field is a long greater or equal to value. * * @param field the name of the field * @param value the minimum value * @return validation result combinator */ static JsonValidation gte(String field, Long value) { requireNonNull(field); requireNonNull(value); return gte(field, value, message(field, "is not greater or equal to " + value)); } /** * Validates that field is a long greater or equal to value. * * @param field the name of the field * @param value the minimum value * @param message the custom message for validation * @return validation result combinator */ static JsonValidation gte(String field, Long value, String message) { requireNonNull(field); requireNonNull(value); return isLong(field) .and(holds(json -> Long.compare(json.getLong(field), value) >= 0, message)); } /** * Validates that field is a long lesser than value. * * @param field the name of the field * @param value the maximum value * @return validation result combinator */ static JsonValidation lt(String field, Long value) { requireNonNull(field); requireNonNull(value); return lt(field, value, message(field, "is not lesser than " + value)); } /** * Validates that field is a long lesser than value. * * @param field the name of the field * @param value the maximum value * @param message the custom message for validation * @return validation result combinator */ static JsonValidation lt(String field, Long value, String message) { requireNonNull(field); requireNonNull(value); return isLong(field) .and(holds(json -> Long.compare(json.getLong(field), value) < 0, message)); } /** * Validates that field is a long lesser or equal to value. * * @param field the name of the field * @param value the maximum value * @return validation result combinator */ static JsonValidation lte(String field, Long value) { requireNonNull(field); requireNonNull(value); return lte(field, value, message(field, "is not lesser or equal to " + value)); } /** * Validates that field is a long lesser or equal to value. * * @param field the name of the field * @param value the maximum value * @param message the custom message for validation * @return validation result combinator */ static JsonValidation lte(String field, Long value, String message) { requireNonNull(field); requireNonNull(value); return isLong(field) .and(holds(json -> Long.compare(json.getLong(field), value) <= 0, message)); } /** * Validates the nested field by applying the provided (sub) validation. * * @param field the nested field to validate * @param subValidation the sub validation * @return validation result combinator */ static JsonValidation nested(String field, JsonValidation subValidation) { requireNonNull(field); requireNonNull(subValidation); return nested(field, () -> subValidation); } /** * Validates the nested field by applying the provided (sub) validation lazily. * * @param field the nested field to validate * @param supplier the validation supplier * @return validation result combinator */ static JsonValidation nested(String field, Supplier supplier) { requireNonNull(field); requireNonNull(supplier); AtomicReference nestedMessage = new AtomicReference<>(); return isObject(field).and(holds(json -> { ValidationResult result = supplier.get().apply(json.getJsonObject(field)); if (!result.isValid()) { nestedMessage.set(result.getReason().get()); } return result.isValid(); }, () -> message(field, "is not valid. Nested error is: " + nestedMessage.get()))); } /** * Combines a predicate and a message into a JsonValidation * * @param p the predicate * @param message the message * @return the validation */ static JsonValidation holds(Predicate p, String message) { return holds(p, () -> message); } /** * Combines a predicate and a message into a JsonValidation *

* Message is supplied only when an error occurs * * @param p the predicate * @param messageSupplier the message supplier * @return the validation */ static JsonValidation holds(Predicate p, Supplier messageSupplier) { return json -> p.test(json) ? valid() : invalid(messageSupplier.get()); } /** * Returns a preformatted text like "Field 'field' message". * * @param field the name of the field * @param message the message to append to the text * @return the message. */ static String message(String field, String message) { return "Field '" + field + "' " + message; } /** * Throws an {@link IllegalArgumentException} when the input is true * * @param inputTest the test input */ static void illegalArgument(boolean inputTest) { if (inputTest) { throw new IllegalArgumentException(); } } /** * Combines two validations. * When the first validation is KO then the other validation is applied and its result is returned. * * @param other the other validation to apply * @return validation result combinator */ default JsonValidation or(JsonValidation other) { return or(() -> other); } /** * Combines two validations. *

* Second validation is applied lazily. * When the first validation is KO then the other validation is applied and its result is returned. * * @param other the other validation to apply * @return validation result combinator */ default JsonValidation or(Supplier other) { return json -> { final ValidationResult result = this.apply(json); return result.isValid() ? result : other.get().apply(json); }; } /** * Combines two validations. * When the first validation is OK then the other validation is applied and its result is returned. * * @param other the other validation to apply * @return validation result combinator */ default JsonValidation and(JsonValidation other) { return and(() -> other); } /** * Combines two validations. *

* Second validation is applied lazily. * When the first validation is OK then the other validation is applied and its result is returned. * * @param other the other validation to apply * @return validation result combinator */ default JsonValidation and(Supplier other) { return json -> { final ValidationResult result = this.apply(json); return result.isValid() ? other.get().apply(json) : result; }; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy