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;
};
}
}