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

org.openrewrite.Validated Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 the original author or 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 *

* https://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.openrewrite; import org.jspecify.annotations.Nullable; import org.openrewrite.internal.StringUtils; import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; import java.util.stream.StreamSupport; import static java.util.stream.StreamSupport.stream; /** * A container object which may or may not contain a valid value. * If a value is valid, {@link #isValid()} returns {@code true}. * If the value is invalid, the object is considered invalid and * {@link #isValid()} returns {@code false}. * * @param the type of value being validated */ public interface Validated extends Iterable> { boolean isValid(); default boolean isInvalid() { return !isValid(); } default List> failures() { List> list = new ArrayList<>(); for (Validated v : this) { if (v.isInvalid()) { list.add((Invalid) v); } } return list; } @SuppressWarnings("unused") static Secret validSecret(String property, String value) { return new Secret(property, value); } static None none() { return new None<>(); } static Valid valid(String property, @Nullable T value) { return new Valid<>(property, value); } /** * Validate that the Predicate will evaluate to 'true' on the supplied value. * When the Predicate evaluates to 'false' the error message will be of the form: *

* "[property] was '[value]' but it [message]" * * @param property The property name to test * @param message The failure message if the test doesn't pass * @param value The value of the property * @param test The test predicate * @param The property value type. * @return A validation result */ static Validated test(String property, String message, @Nullable T value, Predicate test) { return test.test(value) ? valid(property, value) : invalid(property, value, message.replace("{}", value == null ? "null" : value.toString())); } /** * Validate that the Predicate will evaluate to 'true' on the supplied value. * Will return a {@link None} if the value is valid. *

* This allows validation of a value that is not the intended return value. *

* When the Predicate evaluates to 'false' the error message will be of the form: *

* "[property] was '[value]' but it [message]" * * @param property The property name to test * @param message The failure message if the test doesn't pass * @param value The value of the property * @param test The test predicate * @param The property value type. * @return A validation result */ static Validated testNone(String property, String message, @Nullable V value, Predicate test) { return test.test(value) ? new None<>() : invalid(property, value, message.replace("{}", value == null ? "null" : value.toString())); } static Validated required(String property, @Nullable T value) { return value != null ? valid(property, value) : missing(property, null, "is required"); } static Validated notBlank(String property, @Nullable String value) { return test(property, "must not be blank", value, s -> value != null && !StringUtils.isBlank(value)); } static Missing missing(String property, @Nullable T value, String message) { return new Missing<>(property, value, message); } static Invalid invalid(String property, @Nullable Object value, String message) { return invalid(property, value, message, null); } static Invalid invalid(String property, @Nullable Object value, String message, @Nullable Throwable exception) { return new Invalid<>(property, value, message, exception); } @SuppressWarnings("unchecked") default Validated and(Validated validated) { if (validated instanceof None) { return this; } return new Both<>(this, (Validated) validated); } @SuppressWarnings("unchecked") default Validated or(Validated validated) { if (validated instanceof None) { return this; } return new Either<>(this, (Validated) validated); } @Nullable T getValue(); /** * Indicates that no validation has occurred. None is considered "valid", effectively a no-op validation. */ class None implements Validated { @Override public boolean isValid() { return true; } @Override public Iterator> iterator() { return Collections.emptyIterator(); } @Override public T getValue() { throw new IllegalStateException("Value does not exist"); } @Override public Validated or(Validated validated) { if (validated instanceof None) { return this; } //noinspection unchecked return (Validated) validated; } @Override public Validated and(Validated validated) { if (validated instanceof None) { return this; } //noinspection unchecked return (Validated) validated; } } /** * A specialization {@link Valid} that won't print the secret in plain text if the validation is serialized. */ class Secret extends Valid { public Secret(String property, String value) { super(property, value); } @Override public String toString() { return "Secret{" + "property='" + property + '\'' + '}'; } } /** * A valid property value. */ class Valid implements Validated { protected final String property; @Nullable private final T value; public Valid(String property, @Nullable T value) { this.property = property; this.value = value; } @Override public boolean isValid() { return true; } @Override public Iterator> iterator() { return Stream.of((Validated) this).iterator(); } public String getProperty() { return property; } @Override public @Nullable T getValue() { return value; } @Override public String toString() { return "Valid{" + "property='" + property + '\'' + ", value='" + value + '\'' + '}'; } } class Invalid implements Validated { private final String property; @Nullable private final Object value; private final String message; @Nullable private final Throwable exception; public Invalid(String property, @Nullable Object value, String message, @Nullable Throwable exception) { this.property = property; this.value = value; this.message = message; this.exception = exception; } @Override public boolean isValid() { return false; } @Override public @Nullable T getValue() { throw new ValidationException(this); } @Override public Iterator> iterator() { return Stream.of((Validated) this).iterator(); } public String getMessage() { return message; } public String getProperty() { return property; } public @Nullable Object getInvalidValue() { return value; } public @Nullable Throwable getException() { return exception; } @Override public String toString() { return getClass().getSimpleName() + "{" + "property='" + property + '\'' + ", value='" + value + '\'' + ", message='" + message + '\'' + '}'; } } class Missing extends Invalid { public Missing(String property, @Nullable T value, String message) { super(property, value, message, null); } } class Either implements Validated { private final Validated left; private final Validated right; public Either(Validated left, Validated right) { this.left = left; this.right = right; } @Override public boolean isValid() { return left.isValid() || right.isValid(); } public Optional> findAny() { return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(), Spliterator.CONCURRENT), false) .filter(Validated::isValid) .findAny(); } @Override public T getValue() { return findAny() .map(Validated::getValue) .orElseThrow(() -> new IllegalStateException("Value does not exist")); } @Override public Iterator> iterator() { //If only one side is valid, this short circuits the invalid path. if (left.isValid() && right.isInvalid()) { return stream(left.spliterator(), false).iterator(); } else if (left.isInvalid() && right.isValid()) { return stream(right.spliterator(), false).iterator(); } else { //If both are valid/invalid, concat all validations. return Stream.concat( stream(left.spliterator(), false), stream(right.spliterator(), false) ).iterator(); } } } class Both implements Validated { protected final Validated left; protected final Validated right; public Both(Validated left, Validated right) { this.left = left; this.right = right; } @Override public boolean isValid() { return left.isValid() && right.isValid(); } @Override public T getValue() { return right.getValue(); } @Override public Iterator> iterator() { return Stream.concat( stream(left.spliterator(), false), stream(right.spliterator(), false) ).iterator(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy