diff --git a/dependencies.md b/dependencies.md index 5eb1cfa7c1..cb14dd4906 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -1139,14 +1139,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -1743,7 +1743,7 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:40 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -1764,7 +1764,7 @@ This report was generated on **Fri Mar 06 18:16:01 WET 2026** using -# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -2853,14 +2853,14 @@ This report was generated on **Fri Mar 06 18:16:01 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -3939,14 +3939,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 13.0. @@ -3993,14 +3993,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:39 WET 2026** using +This report was generated on **Tue Mar 10 16:36:36 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:validation-jvm-runtime:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine:validation-jvm-runtime:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4833,14 +4833,14 @@ This report was generated on **Mon Mar 09 16:52:39 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -5423,14 +5423,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:40 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -5941,14 +5941,14 @@ This report was generated on **Mon Mar 09 16:52:40 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -6628,14 +6628,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7257,14 +7257,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7929,14 +7929,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -8719,14 +8719,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:42 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8996,14 +8996,14 @@ This report was generated on **Mon Mar 09 16:52:42 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:41 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -9346,6 +9346,6 @@ This report was generated on **Mon Mar 09 16:52:41 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Mar 09 16:52:40 WET 2026** using +This report was generated on **Tue Mar 10 16:36:37 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/docs/_examples b/docs/_examples index 5ba3d2a518..7084c5f46d 160000 --- a/docs/_examples +++ b/docs/_examples @@ -1 +1 @@ -Subproject commit 5ba3d2a518adb53f4e58b7858c26ba21f2a0df16 +Subproject commit 7084c5f46dbffd9bf741c54e96743b0fefc6d568 diff --git a/docs/content/docs/validation/01-getting-started/adding-to-build.md b/docs/content/docs/validation/01-getting-started/adding-to-build.md index 273b6c0164..9c480b76ed 100644 --- a/docs/content/docs/validation/01-getting-started/adding-to-build.md +++ b/docs/content/docs/validation/01-getting-started/adding-to-build.md @@ -82,7 +82,7 @@ Add the Validation plugin to the build. ```kotlin plugins { module - id("io.spine.validation") version "2.0.0-SNAPSHOT.401" + id("io.spine.validation") version "2.0.0-SNAPSHOT.402" } ``` diff --git a/jvm-runtime/src/main/java/io/spine/validation/Alternative.java b/jvm-runtime/src/main/java/io/spine/validation/Alternative.java deleted file mode 100644 index dfe67e89b8..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/Alternative.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldDeclaration; -import io.spine.type.MessageType; - -import java.util.Collection; -import java.util.regex.Pattern; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; - -/** - * A combination of required fields found in the message value. - */ -@Immutable -public final class Alternative { - - /** - * The pattern to remove whitespace from the option field value. - */ - private static final Pattern WHITESPACE = Pattern.compile("\\s+"); - - /** - * Splits Protobuf field names separated with a logical disjunction (OR) literal {@literal |}. - */ - private static final Splitter orSplitter = Splitter.on('|'); - - /** - * Splits Protobuf field names separated with a logical conjunction (AND) literal {@literal &}. - */ - private static final Splitter andSplitter = Splitter.on('&'); - - private final ImmutableSet fields; - - private Alternative(ImmutableSet fields) { - this.fields = fields; - } - - /** - * Parses field combinations from the given raw input. - * - *

Fields may be combined via `{@code |}` ("or") or `{@code &}` ("and") operators. The "and" - * operator always has a priority. Brackets are not supported. - * - * @param notation - * the field expression - * @param type - * the type which declares the fields - * @return a set of parsed alternatives - */ - public static ImmutableSet parse(String notation, MessageType type) { - ImmutableSet.Builder alternatives = ImmutableSet.builder(); - var whiteSpaceRemoved = WHITESPACE.matcher(notation) - .replaceAll(""); - var parts = orSplitter.split(whiteSpaceRemoved); - for (var part : parts) { - var fieldNames = andSplitter.splitToList(part); - alternatives.add(ofCombination(fieldNames, type)); - } - return alternatives.build(); - } - - private static Alternative ofCombination(Collection fieldNames, MessageType type) { - var fields = fieldNames.stream() - .map(type::field) - .collect(toImmutableSet()); - return new Alternative(fields); - } - - /** - * Obtains fields joined in this combination. - */ - public ImmutableSet fields() { - return fields; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/ComparableNumber.java b/jvm-runtime/src/main/java/io/spine/validation/ComparableNumber.java deleted file mode 100644 index 7b72e6cd57..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/ComparableNumber.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.base.Objects; -import com.google.errorprone.annotations.Immutable; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A number that can be compared to another number. - * - *

Note that for values that are outside {@code Long} range, or when - * the precision beyond that of {@code Double} is required, instances of this class - * yield incorrect comparison results. - */ -@Immutable -@SuppressWarnings("ComparableType") // Can be compared to any number. -public final class ComparableNumber extends Number implements Comparable { - - private static final long serialVersionUID = 0L; - @SuppressWarnings("Immutable") // effectively - private final Number value; - - /** Creates a new instance from the specified number. */ - public ComparableNumber(Number value) { - super(); - this.value = checkNotNull(value); - } - - /** Converts this number to its textual representation. */ - public NumberText toText() { - return new NumberText(value); - } - - /** Returns the actual wrapped number. */ - public Number value() { - return value; - } - - @Override - public int compareTo(Number anotherNumber) { - checkNotNull(anotherNumber); - - var thisLong = longValue(); - var thatLong = anotherNumber.longValue(); - if (thisLong == thatLong) { - return Double.compare(doubleValue(), anotherNumber.doubleValue()); - } - return Long.compare(thisLong, thatLong); - } - - @Override - public int intValue() { - return value.intValue(); - } - - @Override - public long longValue() { - return value.longValue(); - } - - @Override - public float floatValue() { - return value.floatValue(); - } - - @Override - public double doubleValue() { - return value.doubleValue(); - } - - /** - * Checks if this number is a whole number, i.e. an {@code int} or a {@code long}. - */ - public boolean isInteger() { - return value instanceof Integer || value instanceof Long; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - var number = (ComparableNumber) o; - return Objects.equal(value, number.value); - } - - @Override - public int hashCode() { - return Objects.hashCode(value); - } - - @Override - public String toString() { - return value.toString(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/Constraint.java b/jvm-runtime/src/main/java/io/spine/validation/Constraint.java deleted file mode 100644 index bd20aa1d10..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/Constraint.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.type.MessageType; - -/** - * A validation rule attributed to a message type. - * - *

A {@code Constraint} may cover the values of one or more fields of a message. - */ -@Immutable -public interface Constraint { - - /** - * The associated message type. - */ - MessageType targetType(); - - /** - * Produces an error message template for the given field validation context. - * - *

Please note, the full support of {@link TemplateString} is not implemented yet - * for the runtime validation. This method was created to keep compatibility with - * the updated {@link ConstraintViolation} structure, which now expects a template string - * for error messages. The inheritors are not supposed to use or override this method. - * - * @param field - * the validated field - */ - default TemplateString errorMessage(FieldContext field) { - var formatted = formattedErrorMessage(field); - return TemplateString.newBuilder() - .setWithPlaceholders(formatted) - .build(); - } - - /** - * Produces an error message for the given field validation context. - * - *

Implementations may choose to ignore the field context or to embed its parts into - * the error message. - * - * @param field - * the validated field - */ - String formattedErrorMessage(FieldContext field); - - /** - * Accepts the given {@link ConstraintTranslator}. - * - * @implNote {@code Constraint} and {@code ConstraintTranslator} implement the visitor pattern. - * Implementations should call the appropriate method of {@code ConstraintTranslator}. - */ - void accept(ConstraintTranslator visitor); -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/ConstraintCache.java b/jvm-runtime/src/main/java/io/spine/validation/ConstraintCache.java deleted file mode 100644 index c4b1fbbca9..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/ConstraintCache.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.base.Objects; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import io.spine.code.proto.FieldContext; -import io.spine.type.MessageType; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.illegalStateWithCauseOf; - -/** - * Cache of constraints per {@link MessageType} in a {@link FieldContext}. - * - * @implNote The cache consists of a {@code 1000} entries, which is, most likely, not all - * the combinations of {@link MessageType}s and {@link FieldContext}s. - * However, as any kind of caching is a tradeoff between speed and the allocated memory, - * it's assumed that {@code 1000} entries is just enough. - */ -final class ConstraintCache { - - private static final int CACHE_SIZE = 1000; - - private static final LoadingCache allConstraints = CacheBuilder - .newBuilder() - .maximumSize(CACHE_SIZE) - .build(new ConstraintLoader()); - private static final LoadingCache customConstraints = CacheBuilder - .newBuilder() - .maximumSize(CACHE_SIZE) - .build(new CustomConstraintLoader()); - - /** - * Prevents the utility class instantiation. - */ - private ConstraintCache() { - } - - /** - * Obtains the constraints for the given type and field context from cache. - * - *

If there is no cache entry for these params, loads the value into the cache and returns - * it. - */ - static Constraints forType(MessageType type, FieldContext context) { - checkNotNull(type); - checkNotNull(context); - - return fromCache(allConstraints, type, context); - } - - /** - * Obtains non-standard constraints for the given type and field context from cache. - * - *

If there is no cache entry for these params, loads the value into the cache and returns - * it. - */ - static Constraints customForType(MessageType type, FieldContext context) { - checkNotNull(type); - checkNotNull(context); - - return fromCache(customConstraints, type, context); - } - - private static Constraints - fromCache(LoadingCache constraints, MessageType type, FieldContext context) { - var key = new Key(type, context); - try { - return constraints.get(key); - } catch (@SuppressWarnings("OverlyBroadCatchBlock") /* `get(..)` Can throw checked - and unchecked Exceptions and Errors. */ Throwable e) { - throw illegalStateWithCauseOf(e); - } - } - - /** - * The constraints cache key. - */ - private static final class Key { - - private final MessageType type; - private final FieldContext fieldContext; - - private Key(MessageType type, FieldContext context) { - this.type = checkNotNull(type); - this.fieldContext = checkNotNull(context); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Key)) { - return false; - } - var key = (Key) o; - return Objects.equal(type, key.type) && - Objects.equal(fieldContext, key.fieldContext); - } - - @Override - public int hashCode() { - return Objects.hashCode(type, fieldContext); - } - } - - /** - * Loads a cache of all constraints per type per field context. - */ - private static final class ConstraintLoader extends CacheLoader { - - @Override - public Constraints load(Key key) { - return Constraints.loadFor(key.type, key.fieldContext); - } - } - - /** - * Loads a cache of non-standard constraints per type per field context. - */ - private static final class CustomConstraintLoader extends CacheLoader { - - @Override - public Constraints load(Key key) { - return Constraints.loadCustomFor(key.type, key.fieldContext); - } - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/ConstraintFor.java b/jvm-runtime/src/main/java/io/spine/validation/ConstraintFor.java deleted file mode 100644 index 5169f91cbd..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/ConstraintFor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.DescriptorProtos.DescriptorProto; -import io.spine.code.proto.MessageOption; -import io.spine.option.OptionsProto; - -import java.util.Optional; - -/** - * An external constraint for a field. - * - *

Contains information about constraint of another field, described by the option value. - */ -@Immutable -final class ConstraintFor extends MessageOption { - - ConstraintFor() { - super(OptionsProto.constraintFor); - } - - /** - * Obtains the value of the option based on its {@linkplain DescriptorProto descriptor}. - */ - Optional valueFrom(DescriptorProto message) { - var options = message.getOptions(); - return options.hasExtension(extension()) - ? Optional.of(options.getExtension(extension())) - : Optional.empty(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/ConstraintTranslator.java b/jvm-runtime/src/main/java/io/spine/validation/ConstraintTranslator.java deleted file mode 100644 index 9f49164c59..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/ConstraintTranslator.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import io.spine.validation.option.DistinctConstraint; -import io.spine.validation.option.GoesConstraint; -import io.spine.validation.option.IsRequiredConstraint; -import io.spine.validation.option.PatternConstraint; -import io.spine.validation.option.RangedConstraint; -import io.spine.validation.option.RequiredConstraint; -import io.spine.validation.option.RequiredFieldConstraint; -import io.spine.validation.option.ValidateConstraint; - -/** - * Translates validation constraints into an output form. - * - *

The format of the output is implementation-dependant. For example, given message value, - * a translator may perform validation and output any found {@linkplain ConstraintViolation - * violations}. Another example would be printing validation code in a target language (e.g. Java). - * - *

A instance of {@code ConstraintTranslator} should only be used for a single type of messages. - * A translator need not be thread-safe. An instance of translator is, most likely, a mutable - * object. If any resources must be closed when finishing the translation job, {@link #translate()} - * is the right place to do so. - * - * @param - * the type of the translation result - */ -public interface ConstraintTranslator { - - /** - * Translates the given {@link RangedConstraint}. - * - * @param constraint - * the constraint of a number field - */ - void visitRange(RangedConstraint constraint); - - /** - * Translates the given {@link RequiredConstraint}. - * - * @param constraint - * the constraint of a field - */ - void visitRequired(RequiredConstraint constraint); - - /** - * Translates the given {@link PatternConstraint}. - * - * @param constraint - * the constraint of a string field - */ - void visitPattern(PatternConstraint constraint); - - /** - * Translates the given {@link DistinctConstraint}. - * - * @param constraint - * the constraint of a collection field - */ - void visitDistinct(DistinctConstraint constraint); - - /** - * Translates the given {@link GoesConstraint}. - * - * @param constraint - * the constraint of a field - */ - void visitGoesWith(GoesConstraint constraint); - - /** - * Translates the given {@link ValidateConstraint}. - * - * @param constraint - * the constraint of a message field - */ - void visitValidate(ValidateConstraint constraint); - - /** - * Translates the given {@link RequiredFieldConstraint}. - * - * @param constraint - * the constraint of a field combination - */ - void visitRequiredField(RequiredFieldConstraint constraint); - - /** - * Translates the given {@link IsRequiredConstraint}. - * - * @param constraint - * the constraint of a oneof group - */ - void visitRequiredOneof(IsRequiredConstraint constraint); - - /** - * Translates the given {@link CustomConstraint}. - * - * @param constraint - * the self-validating constraint - */ - void visitCustom(CustomConstraint constraint); - - /** - * Finalizes the translation for a message type. - */ - T translate(); -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/Constraints.java b/jvm-runtime/src/main/java/io/spine/validation/Constraints.java deleted file mode 100644 index dff387baff..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/Constraints.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.OneofDeclaration; -import io.spine.type.MessageType; -import io.spine.validation.option.IsRequired; -import io.spine.validation.option.Require; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.spine.validation.ConstraintCache.customForType; -import static io.spine.validation.ConstraintCache.forType; -import static io.spine.validation.FieldConstraints.customFactoriesExist; - -/** - * Validation constraints of a single Protobuf message type. - */ -@Immutable -public final class Constraints { - - private final ImmutableList constraints; - - private Constraints(ImmutableList constraints) { - this.constraints = constraints; - } - - /** - * Assembles constraints from the given message type. - */ - public static Constraints of(MessageType type) { - return of(type, FieldContext.empty()); - } - - /** - * Assembles constraints from the given message type in the given field context. - * - *

The field context is not empty if the constraints must consider messages values of a field - * rather than independent messages. - */ - public static Constraints of(MessageType type, FieldContext context) { - checkNotNull(type); - checkNotNull(context); - return forType(type, context); - } - - /** - * Assembles constraints for the given params. - * - *

Use {@link #onlyCustom(MessageType, FieldContext)} over this method, as it relies on - * caching. - */ - static Constraints loadFor(MessageType type, FieldContext context) { - ImmutableList.Builder constraintBuilder = ImmutableList.builder(); - type.fields() - .stream() - .map(context::forChild) - .flatMap(FieldConstraints::of) - .forEach(constraintBuilder::add); - addRequiredField(type, constraintBuilder); - scanIsRequired(type, constraintBuilder); - return new Constraints(constraintBuilder.build()); - } - - private static void addRequiredField(MessageType type, - ImmutableList.Builder constraintBuilder) { - var require = new Require(); - if (require.valuePresent(type.descriptor())) { - var constraint = require.constraintFor(type); - constraintBuilder.add(constraint); - } - } - - private static void scanIsRequired(MessageType type, - ImmutableList.Builder builder) { - var option = new IsRequired(); - type.descriptor() - .getOneofs() - .stream() - .filter(option::valuePresent) - .map(descriptor -> new OneofDeclaration(descriptor, type)) - .map(option::constraintFor) - .forEach(builder::add); - } - - /** - * Assembles non-standard constraints from the given message type in the given field context. - */ - static Constraints onlyCustom(MessageType type, FieldContext context) { - checkNotNull(type); - checkNotNull(context); - return customForType(type, context); - } - - /** - * Assembles non-standard constraints for the given params. - * - *

Use {@link #onlyCustom(MessageType, FieldContext)} over this method, as it relies on - * caching. - */ - static Constraints loadCustomFor(MessageType type, FieldContext context) { - ImmutableList constraints = - customFactoriesExist() - ? type.fields() - .stream() - .map(field -> context.forChild(field.descriptor())) - .flatMap(FieldConstraints::customConstraintsFor) - .collect(toImmutableList()) - : ImmutableList.of(); - return new Constraints(constraints); - } - - /** - * Feeds these constraints to the given {@link ConstraintTranslator} and obtains the result of - * translation. - * - * @param constraintTranslator - * the {@code ConstraintTranslator} which reduces the constrains to - * a value of {@code T} - * @param - * type of the translation result - * @return the translation result - */ - public T runThrough(ConstraintTranslator constraintTranslator) { - constraints.forEach(c -> c.accept(constraintTranslator)); - return constraintTranslator.translate(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/CustomConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/CustomConstraint.java deleted file mode 100644 index 673df5749b..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/CustomConstraint.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.Immutable; -import io.spine.annotation.SPI; - -/** - * A custom runtime constraint defined by a library other than {@code spine-base}. - * - *

Custom constraints can only be used at runtime. Compile-time validation should invoke runtime - * constraints after the compile-time constraints are checked. - */ -@SPI -@Immutable -public interface CustomConstraint extends Constraint { - - ImmutableList validate(MessageValue containingMessage); - - @Override - default void accept(ConstraintTranslator visitor) { - visitor.visitCustom(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/ExceptionFactory.java b/jvm-runtime/src/main/java/io/spine/validation/ExceptionFactory.java deleted file mode 100644 index c1403063d7..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/ExceptionFactory.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.collect.ImmutableList; -import com.google.protobuf.Message; -import com.google.protobuf.ProtocolMessageEnum; -import com.google.protobuf.Value; -import io.spine.annotation.Internal; -import io.spine.base.Error; -import io.spine.protobuf.AnyPacker; -import io.spine.type.MessageClass; -import io.spine.validation.diags.ViolationText; - -import java.util.Map; - -import static java.lang.String.format; - -/** - * A helper class for building exceptions used to report invalid {@code Message}s, - * which have fields that violate validation constraint(s). - * - * @param - * type of {@code Exception} to build - * @param - * type of the {@code Message} - * @param - * type of the {@linkplain io.spine.type.MessageClass} of {@code |M|}. - * @param - * type of error code to use for error reporting; must be a Protobuf enum value - */ -@Internal -@SuppressWarnings({"unused", /* Part of the public API. Exposed for `server`. */ - "AbstractClassNeverImplemented"}) -public abstract class ExceptionFactory, - R extends ProtocolMessageEnum> { - - private final ImmutableList constraintViolations; - private final M message; - - /** - * Creates an {@code ExceptionFactory} instance for a given message and - * constraint violations. - * - * @param message - * an invalid event message - * @param violations - * constraint violations for the event message - */ - protected ExceptionFactory(M message, Iterable violations) { - this.constraintViolations = ImmutableList.copyOf(violations); - this.message = message; - } - - /** - * Obtains a {@code MessageClass} for an invalid {@code Message}. - */ - protected abstract C getMessageClass(); - - /** - * Obtains an error code to use for error reporting. - */ - protected abstract R getErrorCode(); - - /** - * Obtains an error text to use for error reporting. - * - *

This text will also be used as a base for an exception message to generate. - */ - protected abstract String getErrorText(); - - /** - * Obtains the {@code Message}-specific type attributes for error reporting. - */ - protected abstract Map getMessageTypeAttribute(Message message); - - /** - * Defines the way to create an instance of exception basing on the source {@code Message}, - * exception text, and a generated {@code Error}. - */ - protected abstract E createException(String exceptionMsg, M message, Error error); - - private String formatExceptionMessage() { - return format("%s. Message class: `%s`. %s", - getErrorText(), getMessageClass(), violationsText()); - } - - private Error createError() { - var validationError = error(); - var errorCode = getErrorCode(); - var errorType = errorCode.getDescriptorForType() - .getFullName(); - var errorText = errorText(); - - var error = Error.newBuilder() - .setType(errorType) - .setCode(errorCode.getNumber()) - .setDetails(AnyPacker.pack(validationError)) - .setMessage(errorText) - .putAllAttributes(getMessageTypeAttribute(message)); - return error.build(); - } - - private ValidationError error() { - return ValidationError.newBuilder() - .addAllConstraintViolation(constraintViolations) - .build(); - } - - private String errorText() { - var errorTextTemplate = getErrorText(); - var violationsText = violationsText(); - return format("%s %s", errorTextTemplate, violationsText); - } - - private String violationsText() { - return ViolationText.ofAll(constraintViolations); - } - - /** - * Creates an exception instance for an invalid message which has fields that - * violate validation constraint(s). - */ - public E newException() { - return createException(formatExceptionMessage(), message, createError()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/FieldConstraints.java b/jvm-runtime/src/main/java/io/spine/validation/FieldConstraints.java deleted file mode 100644 index c688ebd47e..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/FieldConstraints.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.collect.ImmutableSet; -import io.spine.code.proto.FieldContext; -import io.spine.validation.option.FieldValidatingOption; -import io.spine.validation.option.StandardOptionFactory; -import io.spine.validation.option.ValidatingOptionFactory; -import io.spine.validation.option.ValidatingOptionsLoader; - -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Stream; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableSet.toImmutableSet; - -/** - * A factory of field validation {@link Constraint}s. - */ -final class FieldConstraints { - - private static final ImmutableSet allFactories = - ValidatingOptionsLoader.INSTANCE.implementations(); - private static final ImmutableSet customFactories = - allFactories.stream() - .filter(factory -> !(factory instanceof StandardOptionFactory)) - .collect(toImmutableSet()); - - /** - * Prevents the utility class instantiation. - */ - private FieldConstraints() { - } - - /** - * Assembles {@link Constraint}s for a given field. - * - * @param field - * field to validate - * @return validation constraints - */ - static Stream of(FieldContext field) { - return findConstraints(field, allFactories); - } - - static boolean customFactoriesExist() { - return !customFactories.isEmpty(); - } - - static Stream customConstraintsFor(FieldContext field) { - return findConstraints(field, customFactories); - } - - // Assembles many options and option factories for all field types. - private static Stream - findConstraints(FieldContext field, ImmutableSet factories) { - checkNotNull(field); - var declaration = field.targetDeclaration(); - var type = declaration.javaType(); - return switch (type) { - case INT -> constraintsFrom(factories, ValidatingOptionFactory::forInt, field); - case LONG -> constraintsFrom(factories, ValidatingOptionFactory::forLong, field); - case FLOAT -> constraintsFrom(factories, ValidatingOptionFactory::forFloat, field); - case DOUBLE -> constraintsFrom(factories, ValidatingOptionFactory::forDouble, field); - case BOOLEAN -> constraintsFrom(factories, ValidatingOptionFactory::forBoolean, field); - case STRING -> constraintsFrom(factories, ValidatingOptionFactory::forString, field); - case BYTE_STRING -> - constraintsFrom(factories, ValidatingOptionFactory::forByteString, field); - case ENUM -> constraintsFrom(factories, ValidatingOptionFactory::forEnum, field); - case MESSAGE -> constraintsFrom(factories, ValidatingOptionFactory::forMessage, field); - }; - } - - private static Stream - constraintsFrom(ImmutableSet factories, - Function>> selector, - FieldContext field) { - return factories.stream() - .map(selector) - .flatMap(Set::stream) - .filter(option -> option.shouldValidate(field)) - .map(option -> option.constraintFor(field)); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/FieldValue.java b/jvm-runtime/src/main/java/io/spine/validation/FieldValue.java deleted file mode 100644 index 595a15c66f..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/FieldValue.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.EnumValueDescriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; -import com.google.protobuf.ProtocolMessageEnum; -import io.spine.annotation.Internal; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.protobuf.Messages; -import io.spine.protobuf.TypeConverter; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -/** - * A field value to validate. - * - *

The exact type of the value is unknown since it is set - * by a user who applies a generated validating builder. - * - *

Map fields are considered in a special way, and only values are validated. - * Keys don't require validation since they are of primitive types. - * - * @see - * Protobuf Maps - */ -@Immutable -@Internal -public final class FieldValue { - - /** - * Actual field values. - * - *

Since a field can be, among other things, a repeated field or a map, the values are stored - * in a list. - * - *

For singular fields, a list contains a single value. - * For repeated fields, a list contains all values. - * For a map fields, a list contains a list of values, since the map values are being validated, - * not the keys. - */ - @SuppressWarnings("Immutable") - private final ImmutableList values; - private final FieldContext context; - private final FieldDeclaration declaration; - - private FieldValue(Collection values, FieldContext context, FieldDeclaration declaration) { - this.values = ImmutableList.copyOf(values); - this.context = context; - this.declaration = declaration; - } - - /** - * Creates a new instance from the value. - * - * @param rawValue - * the value obtained via a validating builder - * @param context - * the context of the field - * @return a new instance - */ - static FieldValue of(Object rawValue, FieldContext context) { - checkNotNull(rawValue); - checkNotNull(context); - var value = rawValue instanceof ProtocolMessageEnum - ? ((ProtocolMessageEnum) rawValue).getValueDescriptor() - : rawValue; - var fieldDescriptor = context.target(); - var declaration = new FieldDeclaration(fieldDescriptor); - - var result = resolveType(declaration, context, value); - return result; - } - - /** - * Returns a properly typed {@code FieldValue}. - * - *

To do so, performs a series of {@code instanceof} calls and casts, since there are no - * common ancestors between all the possible value types ({@code Map} for Protobuf {@code map} - * fields, {@code List} for {@code repeated} fields, and {@code T} for plain values). - * - * @return a properly typed {@code FieldValue} instance. - */ - @SuppressWarnings("ChainOfInstanceofChecks") - private static FieldValue resolveType(FieldDeclaration field, FieldContext context, - Object value) { - if (value instanceof List) { - var values = (List) value; - return new FieldValue(values, context, field); - } else if (value instanceof Map) { - var map = (Map) value; - return new FieldValue(map.values(), context, field); - } else { - return new FieldValue(ImmutableList.of(value), context, field); - } - } - - public FieldDescriptor descriptor() { - return context.target(); - } - - /** - * Obtains the {@link JavaType} of the value. - * - *

For a map, returns the type of the values. - * - * @return {@link JavaType} of the field elements - */ - public JavaType javaType() { - if (!declaration.isMap()) { - return declaration.javaType(); - } - var result = declaration.valueDeclaration() - .javaType(); - return result; - } - - /** - * Converts the value to a list. - * - * @return the value as a list - * @deprecated Use {@link #values()} instead. - */ - @Deprecated - public final ImmutableList asList() { - return values; - } - - /** - * Creates a stream of all the values of this field. - * - *

If the field is singular, obtains the only value. If the field is {@code repeated}, - * obtains all the values of the list. If the field is a {@code map}, obtains all the map values - * (but not the keys). - */ - public final Stream values() { - return values.stream(); - } - - /** - * Creates a stream of non-default values of this field. - * - *

Behaves similarly to {@link #values()} but also filters out default values. - * - *

{@code 0} number values, {@code false} boolean values, {@code 0}-number enum instances, - * empty char and byte strings, and empty messages are considered default values. - */ - public final Stream nonDefault() { - return values().filter(val -> !isDefault(val)); - } - - /** - * Obtains the single value of this field. - * - *

If the field is a {@linkplain FieldDeclaration#isCollection() collection}, obtains - * the first element. If the first element is not available, throws - * an {@code IllegalStateException}. - * - * @return a single value of this field - */ - public Object singleValue() { - checkState(!values.isEmpty(), - "Unable to get the first element of an empty collection for the field `%s`.", - declaration()); - return values.get(0); - } - - /** Returns {@code true} if this field is default, {@code false} otherwise. */ - public boolean isDefault() { - return values.isEmpty() || allDefault(); - } - - private boolean allDefault() { - return values.stream() - .allMatch(FieldValue::isDefault); - } - - private static boolean isDefault(Object singleValue) { - if (singleValue instanceof EnumValueDescriptor) { - return ((EnumValueDescriptor) singleValue).getNumber() == 0; - } - var thisAsMessage = TypeConverter.toMessage(singleValue); - return Messages.isDefault(thisAsMessage); - } - - /** Returns the declaration of the value. */ - public FieldDeclaration declaration() { - return declaration; - } - - /** Returns the context of the value. */ - public FieldContext context() { - return context; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/MessageValue.java b/jvm-runtime/src/main/java/io/spine/validation/MessageValue.java deleted file mode 100644 index c1148724e3..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/MessageValue.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.OneofDescriptor; -import com.google.protobuf.Message; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.code.proto.OneofDeclaration; -import io.spine.type.MessageType; -import org.jspecify.annotations.Nullable; - -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A value of a {@link Message} to validate. - * - * @implNote For the performance reasons the values of the passed {@code Message} fields - * are read either via - * {@linkplain Message#getField(com.google.protobuf.Descriptors.FieldDescriptor) reflection} - * or - * {@linkplain FieldAwareMessage#readValue(com.google.protobuf.Descriptors.FieldDescriptor) - * directly} if an instance of {@link FieldAwareMessage} is passed. - * Also, for the same reason the contents of non-{@code oneof} fields are cached once read. - */ -@Immutable -public final class MessageValue { - - /** - * The message which is validated. - */ - private final Message message; - - /** - * Refers to a {@code Message} which may optimize reading of its fields. - * - *

If the {@linkplain MessageValue#MessageValue(Message, FieldContext) passed} message isn't - * an instance of {@link FieldAwareMessage}, this field is {@code null}. - */ - private final @Nullable FieldAwareMessage asFieldAware; - - /** - * The descriptor of type of the validated message. - */ - private final Descriptor descriptor; - - /** - * The field context of the validated message. - */ - private final FieldContext context; - - private MessageValue(Message message, FieldContext context) { - this.message = message; - this.descriptor = message.getDescriptorForType(); - this.context = context; - if (message instanceof FieldAwareMessage) { - asFieldAware = (FieldAwareMessage) message; - } else { - asFieldAware = null; - } - } - - /** - * Creates a value of a message field. - * - * @param messageContext - * the context of the field presented by the message - * @param message - * the message itself - * @return a new instance - */ - public static MessageValue nestedIn(FieldContext messageContext, Message message) { - checkNotNull(message); - checkNotNull(messageContext); - return new MessageValue(message, messageContext); - } - - /** - * Creates a value of a top-level message. - * - * @param message - * the message that is not a part of another message - * @return a new instance - */ - public static MessageValue atTopLevel(Message message) { - checkNotNull(message); - return new MessageValue(message, FieldContext.empty()); - } - - public MessageType declaration() { - return new MessageType(descriptor); - } - - /** - * Obtains the value of the field with the specified name. - * - * @param fieldName - * the name of the field to obtain - * @return a value of the field - * or {@code Optional.empty()} if the message doesn't contain the field - */ - public Optional valueOf(String fieldName) { - var field = descriptor.findFieldByName(fieldName); - return valueOf(field); - } - - /** - * Obtains the value of the field with the specified field descriptor. - * - * @param fieldDescriptor - * the field descriptor of the field to obtain - * @return a value of the field or {@code Optional.empty()} if - * the message doesn't contain the field - */ - public Optional valueOf(FieldDescriptor fieldDescriptor) { - return valueOfNullable(fieldDescriptor); - } - - /** - * Obtains the value of the field with the specified field declaration. - * - * @param field - * the declaration of the field to obtain - * @return a value of the field - */ - public FieldValue valueOf(FieldDeclaration field) { - checkNotNull(field); - return valueOfField(field.descriptor()); - } - - /** - * Obtains the value of a populated {@code oneof} field. - * - * @param oneof - * the {@code oneof} descriptor - * @return a value of the populated field - * or {@code Optional.empty()} if the field was not populated - * @throws IllegalArgumentException - * if the if the message doesn't declare this oneof - */ - public Optional valueOf(OneofDescriptor oneof) { - checkArgument(descriptor.getOneofs() - .contains(oneof)); - var field = message.getOneofFieldDescriptor(oneof); - return valueOfNullable(field); - } - - /** - * Obtains the value of a populated {@code oneof} field. - * - * @param oneof - * the {@code oneof} declaration - * @return a value of the populated field - * or {@code Optional.empty()} if the field was not populated - * @throws IllegalArgumentException - * if the if the message doesn't declare this oneof - */ - public Optional valueOf(OneofDeclaration oneof) { - return valueOf(oneof.descriptor()); - } - - /** Returns the context of the message. */ - @SuppressWarnings("unused") - FieldContext context() { - return context; - } - - private Optional valueOfNullable(@Nullable FieldDescriptor field) { - if (field == null) { - return Optional.empty(); - } - var fieldValue = valueOfField(field); - return Optional.of(fieldValue); - } - - private FieldValue valueOfField(FieldDescriptor field) { - var fieldContext = context.forChild(field); - var rawValue = readValue(field); - @SuppressWarnings("Immutable") // field values are immutable - var value = FieldValue.of(rawValue, fieldContext); - return value; - } - - private Object readValue(FieldDescriptor field) { - return asFieldAware == null ? message.getField(field) : asFieldAware.readValue(field); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/NumberText.java b/jvm-runtime/src/main/java/io/spine/validation/NumberText.java deleted file mode 100644 index 8e5de410d9..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/NumberText.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.base.Objects; -import com.google.common.base.Splitter; -import com.google.errorprone.annotations.Immutable; -import io.spine.annotation.Internal; - -import java.util.Collection; -import java.util.List; - -import static io.spine.util.Exceptions.newIllegalStateException; -import static io.spine.util.Preconditions2.checkNotEmptyOrBlank; - -/** - * A number that is described with a {@code String} of characters. - */ -@Immutable -@Internal -public final class NumberText { - - private static final String DECIMAL_DELIMITER = "."; - private static final Splitter DECIMAL_SPLIT = Splitter.on(DECIMAL_DELIMITER); - - private final String text; - @SuppressWarnings("Immutable") // effectively - private final Number value; - - /** - * Creates a new instance which represents to the given number. - */ - public NumberText(Number number) { - this.text = String.valueOf(number); - this.value = number; - } - - /** - * Creates a new instance, the value being the specified {@code String} resolved to an - * appropriate type. - * - * @param text - * a string representation of a number - */ - public NumberText(String text) { - this.text = checkNotEmptyOrBlank(text).trim(); - this.value = parseNumber(this.text); - } - - /** - * Determines whether this instance of a number is of the same {@code Number} subtype as - * the specified one. - * - *

Example: - *

-     *     {@code
-     *     NumberText zeroWithDecimal = new NumberText("0.0");
-     *     NumberText plainZero = new NumberText("0");
-     *
-     *     zeroWithDecimal.isOfSameType(plainZero); // false
-     *     }
-     *  
- * the above code is false, since {@code zeroWithDecimal} describes a number that has a decimal - * part, be it a {@code Double} or a {@code Float}, while {@code plainZero} describes an - * arbitrary whole number. - * - * @return whether this instance of a number is of the same {@code Number} subtype as - * the specified one. - */ - public boolean isOfSameType(NumberText anotherNumber) { - var classOfThisNumber = value.getClass(); - var classOfAnotherNumber = anotherNumber.value.getClass(); - return classOfThisNumber.equals(classOfAnotherNumber); - } - - /** - * Converts current number {@code value} to a {@code ComparableNumber}. - */ - public ComparableNumber toNumber() { - return new ComparableNumber(this.value); - } - - private static Number parseNumber(String text) { - var wholeAndDecimal = DECIMAL_SPLIT.splitToList(text); - hasOnlyWholeAndDecimal(wholeAndDecimal); - if (hasDecimalPart(wholeAndDecimal)) { - return Double.parseDouble(text); - } - return Long.parseLong(text); - } - - private static boolean hasDecimalPart(List wholeAndDecimal) { - var hasOnlyWhole = wholeAndDecimal.size() <= 1; - return !hasOnlyWhole && !wholeAndDecimal.get(1) - .isEmpty(); - } - - private static void hasOnlyWholeAndDecimal(Collection wholeAndDecimal) - throws IllegalStateException { - if (wholeAndDecimal.size() > 2) { - var malformedNumber = String.join("", wholeAndDecimal); - throw newIllegalStateException("Found malformed number: %s.", malformedNumber); - } - } - - @Override - public String toString() { - return text; - } - - @Override - public int hashCode() { - return Objects.hashCode(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - var text = (NumberText) o; - return Objects.equal(this.value, text.value); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/RequiredFieldCheck.java b/jvm-runtime/src/main/java/io/spine/validation/RequiredFieldCheck.java deleted file mode 100644 index 81e2e6e572..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/RequiredFieldCheck.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation; - -import com.google.common.collect.ImmutableSet; -import io.spine.base.FieldPath; - -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.newIllegalStateException; -import static io.spine.util.Preconditions2.checkNotEmptyOrBlank; - -/** - * Method object for validating a value. - */ -final class RequiredFieldCheck { - - private static final String RULE_VALUE = "rule"; - private static final String ERROR_MESSAGE = - "Message must have all the required fields set according to the rule: `${" + RULE_VALUE + "}`."; - - private final MessageValue message; - private final ImmutableSet alternatives; - private final String optionValue; - - RequiredFieldCheck(String optionValue, - ImmutableSet alternatives, - MessageValue value) { - this.message = checkNotNull(value); - this.optionValue = checkNotEmptyOrBlank(optionValue.trim()); - this.alternatives = checkNotNull(alternatives); - } - - Optional perform() { - var matches = alternatives.stream() - .anyMatch(this::allPresent); - var message = TemplateString.newBuilder() - .setWithPlaceholders(ERROR_MESSAGE) - .putPlaceholderValue(RULE_VALUE, optionValue) - .build(); - return matches - ? Optional.empty() - : Optional.of(ConstraintViolation.newBuilder() - .setMessage(message) - .setFieldPath(fieldPath()) - .setTypeName(typeName()) - .build() - ); - } - - private boolean allPresent(Alternative alternative) { - for (var declaration : alternative.fields()) { - var fieldName = declaration.name().value(); - var field = message.valueOf(fieldName); - var value = field.orElseThrow( - () -> fieldNotFound(message, fieldName) - ); - if (value.isDefault()) { - return false; - } - } - return true; - } - - private static IllegalStateException - fieldNotFound(MessageValue containingMessage, String fieldName) { - return newIllegalStateException( - "Message `%s` declares a constraint with a field `%s`, which does not exist.", - containingMessage.declaration(), - fieldName - ); - } - - private FieldPath fieldPath() { - return message.context() - .fieldPath(); - } - - private String typeName() { - return message.declaration() - .name() - .value(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/Validate.java b/jvm-runtime/src/main/java/io/spine/validation/Validate.java index 350fea27c7..8a8f917c4d 100644 --- a/jvm-runtime/src/main/java/io/spine/validation/Validate.java +++ b/jvm-runtime/src/main/java/io/spine/validation/Validate.java @@ -27,27 +27,16 @@ package io.spine.validation; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.Any; import com.google.protobuf.Message; -import io.spine.annotation.Internal; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.protobuf.Diff; import io.spine.type.KnownTypes; -import io.spine.type.MessageType; import io.spine.type.TypeUrl; -import io.spine.validation.option.SetOnce; -import io.spine.validation.option.ValidatingOptionFactory; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.spine.protobuf.AnyPacker.unpack; -import static io.spine.validation.RuntimeErrorPlaceholder.FIELD_PATH; -import static io.spine.validation.RuntimeErrorPlaceholder.PARENT_TYPE; /** * This class provides general validation routines. @@ -58,11 +47,6 @@ ) public final class Validate { - private static final String SET_ONCE_ERROR_MESSAGE = - "Attempted to change the value of the field " + - "`${" + PARENT_TYPE + "}.${" + FIELD_PATH + "}` which has " + - "`(set_once) = true` and already has a non-default value."; - /** Prevents instantiation of this utility class. */ private Validate() { } @@ -85,13 +69,17 @@ public static M check(M message) throws ValidationException } /** - * Validates the given message according to its definition and returns - * the constraint violations, if any. + * Validates the given message according to its definition and configured validators. + * + *

If the message is {@link Any}, it is unpacked before validation. * * @return violations of the validation rules or an empty list if the message is valid + * @see ValidatableMessage + * @see MessageValidator */ @SuppressWarnings("ChainOfInstanceofChecks") // A necessity for covering more cases. public static List violationsOf(Message message) { + checkNotNull(message); var msg = message; if (message instanceof Any packed) { if (KnownTypes.instance().contains(TypeUrl.ofEnclosed(packed))) { @@ -106,180 +94,8 @@ public static List violationsOf(Message message) { var error = validatable.validate(); return error.map(ValidationError::getConstraintViolationList) .orElse(ImmutableList.of()); - } - return validateAtRuntime(message); - } - - private static List validateAtRuntime(Message message) { - return validateAtRuntime(message, FieldContext.empty()); - } - - /** - * Validates the given message ignoring the generated validation code. - * - *

Use {@link #violationsOf(Message)} over this method. It is declared {@code public} only - * to be accessible in the generated code. - * - * @param message - * the message to validate - * @param context - * the validation field context - * @return violations of the validation rules or an empty list if the message is valid - * @apiNote This method is used by the generated code, and as such needs to - * be {@code public}. - */ - @Internal - @SuppressWarnings("WeakerAccess") // see apiNote. - public static List - validateAtRuntime(Message message, FieldContext context) { - var error = Constraints.of(MessageType.of(message), context) - .runThrough(new RuntimeMessageValidator(message, context)); - var violations = error.map(ValidationError::getConstraintViolationList) - .orElse(ImmutableList.of()); - return violations; - } - - /** - * Validates the given message according to custom validation constraints. - * - *

If there are user-defined {@link ValidatingOptionFactory} in - * the classpath, they are used to create validating options and assemble constraints. If there - * are no such factories, this method always returns an empty list. - * - * @param message - * the message to validate - * @return a list of violations; an empty list if the message is valid - */ - public static List violationsOfCustomConstraints(Message message) { - checkNotNull(message); - var error = Constraints.onlyCustom(MessageType.of(message), FieldContext.empty()) - .runThrough(new RuntimeMessageValidator(message)); - var violations = error.map(ValidationError::getConstraintViolationList) - .orElse(ImmutableList.of()); - return violations; - } - - /** - * Checks that when transitioning a message state from {@code previous} to {@code current}, - * the {@code set_once} constrains are met and throws a {@link ValidationException} if - * the value transition is not valid. - * - * @param previous - * the previous state of the message - * @param current - * the new state of the message - * @param - * the type of the message - * @throws ValidationException - * if the value transition is not valid - * @deprecated the {@code set_once} constraint is enforced by the {@link Message} builder now. - * Just remove usages of this method without providing any replacement. - */ - @Deprecated - public static void checkValidChange(M previous, M current) { - checkNotNull(previous); - checkNotNull(current); - var setOnceViolations = validateChange(previous, current); - if (!setOnceViolations.isEmpty()) { - throw new ValidationException(setOnceViolations); - } - } - - /** - * Checks that when transitioning a message state from {@code previous} to {@code current}, - * the {@code set_once} constrains are met. - * - * @param previous - * the previous state of the message - * @param current - * the new state of the message - * @param - * the type of the message - * @return a set of constraint violations, if the transaction is invalid, - * an empty set otherwise - * @deprecated the {@code set_once} constraint is enforced by the {@link Message} builder now. - * Just remove usages of this method without providing any replacement. - */ - @Deprecated - @SuppressWarnings("WeakerAccess") // part of public API. - public static - ImmutableSet validateChange(M previous, M current) { - checkNotNull(previous); - checkNotNull(current); - - var diff = Diff.between(previous, current); - var violations = current.getDescriptorForType().getFields() - .stream() - .map(FieldDeclaration::new) - .filter(Validate::isNonOverridable) - .filter(diff::contains) - .filter(field -> { - var fieldValue = previous.getField(field.descriptor()); - return !field.isDefault(fieldValue); - }) - .map(Validate::violatedSetOnce) - .collect(toImmutableSet()); - return violations; - } - - /** - * Checks if the given field, once set, may not be changed. - * - *

This property is defined by the {@code (set_once)} option. If the option is set to - * {@code true} on a non-{@code repeated} and non-{@code map} field, this field is - * non-overridable. - * - *

Logs if the option is set but the field is {@code repeated} or a {@code map}. - * - * @param field - * the field to check - * @return {@code true} if the field is neither {@code repeated} nor {@code map} and is - * {@code (set_once)} - */ - private static boolean isNonOverridable(FieldDeclaration field) { - checkNotNull(field); - - var marked = markedSetOnce(field); - if (marked) { - var setOnceInapplicable = field.isCollection(); - if (setOnceInapplicable) { - onSetOnceMisuse(field); - return false; - } else { - return true; - } } else { - return false; + return ValidatorRegistry.validate(msg); } } - - private static boolean markedSetOnce(FieldDeclaration declaration) { - var setOnceDeclaration = SetOnce.from(declaration.descriptor()); - boolean setOnceValue = setOnceDeclaration.orElse(false); - return setOnceValue; - } - - private static void onSetOnceMisuse(FieldDeclaration field) { - var fieldName = field.name(); - System.err.printf( - "Error found in `%s`. " + - "Repeated and map fields cannot be marked as `(set_once) = true`.%n", - fieldName); - } - - private static ConstraintViolation violatedSetOnce(FieldDeclaration declaration) { - var declaringTypeName = declaration.declaringType().name().value(); - var fieldName = declaration.name().value(); - var message = TemplateString.newBuilder() - .setWithPlaceholders(SET_ONCE_ERROR_MESSAGE) - .putPlaceholderValue(PARENT_TYPE.toString(), declaringTypeName) - .putPlaceholderValue(FIELD_PATH.toString(), fieldName) - .build(); - var violation = ConstraintViolation.newBuilder() - .setMessage(message) - .setFieldPath(declaration.name().asPath()) - .setTypeName(declaration.declaringType().name().value()) - .build(); - return violation; - } } diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/AlwaysRequired.java b/jvm-runtime/src/main/java/io/spine/validation/option/AlwaysRequired.java deleted file mode 100644 index 725766dc7e..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/AlwaysRequired.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; - -/** - * A special case of {@code Required} option that assumes that the option is present regardless - * of the actual field declaration. - */ -@Immutable -final class AlwaysRequired extends Required { - - /** - * Creates a new instance of this option. - */ - AlwaysRequired() { - super(); - } - - /** - * {@inheritDoc} - * - *

For {@code AlwaysRequired}, validation happens every time. - */ - @Override - public boolean shouldValidate(FieldContext context) { - checkUsage(context.targetDeclaration()); - return true; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Distinct.java b/jvm-runtime/src/main/java/io/spine/validation/option/Distinct.java deleted file mode 100644 index 6beabedee2..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Distinct.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -/** - * An option that can be applied to {@code repeated} and {@code map} Protobuf fields to specify that - * values represented by that field should not contain duplicates. - */ -@Immutable -public final class Distinct extends FieldValidatingOption { - - private Distinct() { - super(OptionsProto.distinct); - } - - /** - * Returns a new instance of this option. - */ - public static Distinct create() { - return new Distinct(); - } - - @Override - public Constraint constraintFor(FieldContext fieldValue) { - return new DistinctConstraint(optionValue(fieldValue), fieldValue.targetDeclaration()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/DistinctConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/DistinctConstraint.java deleted file mode 100644 index 86d0c37a9e..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/DistinctConstraint.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.validation.ConstraintTranslator; - -import static java.lang.String.format; - -/** - * A repeated field constraint that requires values to be distinct. - */ -@Immutable -public final class DistinctConstraint extends FieldConstraint { - - DistinctConstraint(Boolean optionValue, FieldDeclaration field) { - super(optionValue, field); - } - - @Override - public String formattedErrorMessage(FieldContext field) { - return format("`%s` must not contain duplicates.", field.targetDeclaration()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitDistinct(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/FieldConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/FieldConstraint.java deleted file mode 100644 index 5ac9ffd99a..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/FieldConstraint.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.errorprone.annotations.ImmutableTypeParameter; -import io.spine.code.proto.FieldDeclaration; -import io.spine.type.MessageType; -import io.spine.validation.Constraint; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A rule that limits a set of values that a Protobuf field can have. - * - * @param - * a type of value that describes the constraints - */ -@Immutable -public abstract class FieldConstraint<@ImmutableTypeParameter V> implements Constraint { - - private final V optionValue; - private final FieldDeclaration field; - - /** - * Creates a new instance of this constraint. - * - * @param optionValue - * a value that describes the field constraints - * @param field - * the field which declares the constraint - */ - protected FieldConstraint(V optionValue, FieldDeclaration field) { - this.optionValue = checkNotNull(optionValue); - this.field = checkNotNull(field); - } - - /** Returns a value that describes the constraint.*/ - public final V optionValue() { - return optionValue; - } - - public final FieldDeclaration field() { - return field; - } - - @Override - public MessageType targetType() { - return field.declaringType(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/FieldValidatingOption.java b/jvm-runtime/src/main/java/io/spine/validation/option/FieldValidatingOption.java deleted file mode 100644 index 8f9fcabc5b..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/FieldValidatingOption.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.errorprone.annotations.ImmutableTypeParameter; -import com.google.protobuf.DescriptorProtos.FieldOptions; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.GeneratedMessage.GeneratedExtension; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldOption; - -import java.util.Optional; - -import static java.lang.String.format; - -/** - * An option that validates a field. - * - *

Validating options impose constraint on fields that they are applied to. - * - * @param - * type of value held by this option - */ -@Immutable -public abstract class FieldValidatingOption<@ImmutableTypeParameter T> - extends FieldOption - implements ValidatingOption { - - /** Specifies the extension that corresponds to this option. */ - protected FieldValidatingOption(GeneratedExtension optionExtension) { - super(optionExtension); - } - - /** - * Returns a value of the option. - * - * @apiNote Should only be called by subclasses in circumstances that assume presence of - * the option. For all other cases please refer to - * {@link #valueFrom(FieldContext)}. - */ - protected T optionValue(FieldContext field) throws IllegalStateException { - var option = valueFrom(field); - return option.orElseThrow(() -> { - var descriptor = extension().getDescriptor(); - - var fieldName = field.targetDeclaration() - .name() - .value(); - var containingTypeName = descriptor.getContainingType() - .getName(); - return couldNotGetOptionValueFrom(fieldName, containingTypeName); - }); - } - - private IllegalStateException couldNotGetOptionValueFrom(String fieldName, - String containingTypeName) { - var optionName = extension().getDescriptor().getName(); - var message = format("Could not get value of option %s from field %s in message %s.", - optionName, - fieldName, - containingTypeName); - return new IllegalStateException(message); - } - - /** - * Takes the value of the option from the given descriptor, given the specified context. - * - * @param context - * context of the field - * @return an {@code Optional} with an option value if such exists, otherwise an empty - * {@code Optional} - * @apiNote Use this in favour of {@link - * FieldOption#optionsFrom(com.google.protobuf.Descriptors.FieldDescriptor) - * optionsFrom(FieldDescriptor)} when {@code FieldContext} matters. - */ - public Optional valueFrom(FieldContext context) { - return valueFrom(context.target()); - } - - /** - * Returns {@code true} if this option exists for the specified field, {@code false} otherwise. - * - * @param context - * the value to be validated - */ - public boolean shouldValidate(FieldContext context) { - return valueFrom(context).isPresent(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Goes.java b/jvm-runtime/src/main/java/io/spine/validation/option/Goes.java deleted file mode 100644 index 43e4dac785..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Goes.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.GoesOption; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -import static java.lang.String.format; - -/** - * An option that defines field bond to another field within the message. - */ -@Immutable -public final class Goes - extends FieldValidatingOption { - - private Goes() { - super(OptionsProto.goes); - } - - public static Goes create() { - return new Goes(); - } - - @Override - public Constraint constraintFor(FieldContext field) { - return new GoesConstraint(field.targetDeclaration(), optionValue(field)); - } - - @Override - public boolean shouldValidate(FieldContext field) { - return super.shouldValidate(field) - && canBeRequired(field) - && canPairedBeRequired(field); - } - - private static boolean canBeRequired(FieldContext context) { - var field = context.targetDeclaration(); - var warning = format( - "Field `%s` cannot be checked for presence. `(goes).with` is obsolete.", - field - ); - return checkType(field, warning); - } - - private boolean canPairedBeRequired(FieldContext context) { - var option = optionValue(context); - var pairedFieldName = option.getWith().trim(); - if (pairedFieldName.isEmpty()) { - return false; - } - var field = context.targetDeclaration(); - var messageType = field.declaringType(); - var pairedField = messageType.field(pairedFieldName); - var warningMessage = format( - "Field `%s` paired with `%s` cannot be checked for presence. " + - "`(goes).with` at `%s` is obsolete.", - pairedField, field, field - ); - return checkType(pairedField, warningMessage); - } - - @SuppressWarnings("UseOfSystemOutOrSystemErr") /* We're migrating off runtime validation. AND - we do not want the dependency of Validation Runtime on Spine Logging. - So we use `System.err` for warnings. */ - private static boolean checkType(FieldDeclaration field, String warningMessage) { - var type = field.javaType(); - if (field.isCollection() || Required.CAN_BE_REQUIRED.contains(type)) { - return true; - } else { - System.err.println(warningMessage); - return false; - } - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/GoesConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/GoesConstraint.java deleted file mode 100644 index e36bccdab5..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/GoesConstraint.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.GoesOption; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.diags.ViolationText; - -/** - * A constraint which checks whether a field is set only if the specific related field is also set. - */ -@Immutable -public final class GoesConstraint extends FieldConstraint { - - /** - * Creates a constraint for the supplied {@code message} with a specified {@code goes} option. - */ - GoesConstraint(FieldDeclaration declaringField, GoesOption option) { - super(option, declaringField); - } - - @Override - @SuppressWarnings("deprecation") /* Old validation won't migrate to the new error messages. */ - public String formattedErrorMessage(FieldContext field) { - var option = optionValue(); - return ViolationText.errorMessage(option, option.getMsgFormat()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitGoesWith(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/IfMissing.java b/jvm-runtime/src/main/java/io/spine/validation/option/IfMissing.java deleted file mode 100644 index 494b2df188..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/IfMissing.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.FieldDescriptor; -import io.spine.code.proto.FieldOption; -import io.spine.option.IfMissingOption; -import io.spine.option.OptionsProto; - -/** - * A field option that defines custom error message if a field is {@code required} but missing. - */ -@Immutable -public final class IfMissing extends FieldOption { - - /** Creates a new instance of this option. */ - public IfMissing() { - super(OptionsProto.ifMissing); - } - - /** - * Returns the option value from the specified field or a default value, if the field does not - * have its own option value. - */ - public IfMissingOption valueOrDefault(FieldDescriptor field) { - var option = valueFrom(field); - return option.orElse(IfMissingOption.getDefaultInstance()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/IsRequired.java b/jvm-runtime/src/main/java/io/spine/validation/option/IsRequired.java deleted file mode 100644 index 93d073fc63..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/IsRequired.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.OneofDescriptor; -import io.spine.code.proto.OneofDeclaration; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -import java.util.Optional; - -/** - * A {@code oneof} validation option which constrains the target {@code oneof} group to be set. - * - *

If the value of the option is {@code true}, one of the fields in the group must be set. - */ -@Immutable -public class IsRequired implements ValidatingOption { - - @Override - public Constraint constraintFor(OneofDeclaration field) { - return new IsRequiredConstraint(field); - } - - @Override - public Optional valueFrom(OneofDescriptor descriptor) { - boolean value = descriptor.getOptions() - .getExtension(OptionsProto.isRequired); - return value ? Optional.of(true) : Optional.empty(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/IsRequiredConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/IsRequiredConstraint.java deleted file mode 100644 index 2b74a8e729..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/IsRequiredConstraint.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldName; -import io.spine.code.proto.OneofDeclaration; -import io.spine.type.MessageType; -import io.spine.validation.Constraint; -import io.spine.validation.ConstraintTranslator; - -import static java.lang.String.format; - -/** - * A {@code oneof} group constraint which signifies that one of the fields must be set. - */ -@Immutable -public final class IsRequiredConstraint implements Constraint { - - private final OneofDeclaration declaration; - - IsRequiredConstraint(OneofDeclaration declaration) { - this.declaration = declaration; - } - - @Override - public MessageType targetType() { - return declaration.declaringType(); - } - - @Override - public String formattedErrorMessage(FieldContext field) { - return format("One of fields in group `%s` must be set.", declaration.name()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitRequiredOneof(this); - } - - /** - * Obtains the name of the {@code oneof} group. - */ - public FieldName oneofName() { - return declaration.name(); - } - - /** - * Obtains the {@code oneof} declaration. - */ - public OneofDeclaration declaration() { - return declaration; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Max.java b/jvm-runtime/src/main/java/io/spine/validation/option/Max.java deleted file mode 100644 index 3fe177e183..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Max.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.MaxOption; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -/** - * An option that defines a maximum value for a numeric field. - */ -@Immutable -final class Max extends FieldValidatingOption { - - private Max() { - super(OptionsProto.max); - } - - /** Returns a new instance of this option. */ - static Max create() { - return new Max(); - } - - @Override - public Constraint constraintFor(FieldContext fieldValue) { - return new MaxConstraint(optionValue(fieldValue), fieldValue.targetDeclaration()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/MaxConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/MaxConstraint.java deleted file mode 100644 index 826195227d..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/MaxConstraint.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.Range; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.MaxOption; -import io.spine.validation.ComparableNumber; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.NumberText; -import io.spine.validation.diags.ViolationText; - -import static java.lang.String.format; - -/** - * A constraint, which checks whether a numeric field value exceeds a max value, when applied. - */ -@Immutable -public final class MaxConstraint extends RangedConstraint { - - MaxConstraint(MaxOption optionValue, FieldDeclaration field) { - super(optionValue, maxRange(optionValue), field); - } - - private static Range maxRange(MaxOption option) { - var inclusive = !option.getExclusive(); - var maxValue = new NumberText(option.getValue()); - return inclusive - ? Range.atMost(maxValue.toNumber()) - : Range.lessThan(maxValue.toNumber()); - } - - @Override - @SuppressWarnings("deprecation") /* Old validation won't migrate to the new error messages. */ - protected String compileErrorMessage(Range range) { - var max = optionValue(); - var template = ViolationText.errorMessage(max, max.getMsgFormat()); - return format(template, orEqualTo(range.upperBoundType()), range.upperEndpoint()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitRange(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Min.java b/jvm-runtime/src/main/java/io/spine/validation/option/Min.java deleted file mode 100644 index 95445ae58c..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Min.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.MinOption; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -/** - * An option that defines a minimum value for a numeric field. - */ -@Immutable -final class Min - extends FieldValidatingOption { - - private Min() { - super(OptionsProto.min); - } - - /** Creates a new instance of this option. */ - static Min create() { - return new Min(); - } - - @Override - public Constraint constraintFor(FieldContext fieldValue) { - return new MinConstraint(optionValue(fieldValue), fieldValue.targetDeclaration()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/MinConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/MinConstraint.java deleted file mode 100644 index 5335ca6af2..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/MinConstraint.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.Range; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.MinOption; -import io.spine.validation.ComparableNumber; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.NumberText; -import io.spine.validation.diags.ViolationText; - -import static java.lang.String.format; - -/** - * A constraint that, when applied to a numeric field, checks whether the value of that field is - * greater than (or equal to, if specified by the value of the respective option) a min value. - */ -@Immutable -public final class MinConstraint extends RangedConstraint { - - MinConstraint(MinOption optionValue, FieldDeclaration field) { - super(optionValue, minRange(optionValue), field); - } - - private static Range minRange(MinOption option) { - var inclusive = !option.getExclusive(); - var minValue = new NumberText(option.getValue()); - return inclusive - ? Range.atLeast(minValue.toNumber()) - : Range.greaterThan(minValue.toNumber()); - } - - @Override - @SuppressWarnings("deprecation") /* Old validation won't migrate to the new error messages. */ - protected String compileErrorMessage(Range range) { - var min = optionValue(); - var template = ViolationText.errorMessage(min, min.getMsgFormat()); - return format(template, orEqualTo(range.lowerBoundType()), range.lowerEndpoint()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitRange(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Pattern.java b/jvm-runtime/src/main/java/io/spine/validation/option/Pattern.java deleted file mode 100644 index ccb1bfec96..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Pattern.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.OptionsProto; -import io.spine.option.PatternOption; -import io.spine.validation.Constraint; - -/** - * An option defining a pattern that a field value has to match. - */ -@Immutable -final class Pattern extends FieldValidatingOption { - - private Pattern() { - super(OptionsProto.pattern); - } - - /** Returns a new instance of this option. */ - public static Pattern create() { - return new Pattern(); - } - - @Override - public Constraint constraintFor(FieldContext fieldValue) { - return new PatternConstraint(optionValue(fieldValue), fieldValue.targetDeclaration()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/PatternConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/PatternConstraint.java deleted file mode 100644 index 79b1a68cde..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/PatternConstraint.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.PatternOption; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.diags.ViolationText; - -import static java.util.regex.Pattern.CASE_INSENSITIVE; -import static java.util.regex.Pattern.DOTALL; -import static java.util.regex.Pattern.MULTILINE; -import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; - -/** - * A constraint, which when applied to a string field, checks whether that field matches the - * specified pattern. - */ -@Immutable -public final class PatternConstraint extends FieldConstraint { - - PatternConstraint(PatternOption optionValue, FieldDeclaration field) { - super(optionValue, field); - } - - @Override - @SuppressWarnings("deprecation") /* Old validation won't migrate to the new error messages. */ - public String formattedErrorMessage(FieldContext field) { - var option = optionValue(); - return ViolationText.errorMessage(option, option.getMsgFormat()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitPattern(this); - } - - /** - * Obtains the regular expression as a string. - */ - public String regex() { - return optionValue().getRegex(); - } - - /** - * Checks if the pattern allows a partial match. - * - *

If {@code true}, the whole string value does not have to match the regex, but only its - * substring. - */ - public boolean allowsPartialMatch() { - var option = optionValue(); - var modifier = option.getModifier(); - return modifier.getPartialMatch(); - } - - /** - * Obtains the pattern modifiers as a bit mask for the {@link Pattern} flags. - * - *

If no modifiers are specified, returns {@code 0}. - */ - public int flagsMask() { - var result = 0; - var option = optionValue(); - var modifier = option.getModifier(); - if (modifier.getDotAll()) { - result |= DOTALL; - } - if (modifier.getUnicode()) { - result |= UNICODE_CHARACTER_CLASS; - } - if (modifier.getCaseInsensitive()) { - result |= CASE_INSENSITIVE; - } - if (modifier.getMultiline()) { - result |= MULTILINE; - } - return result; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Range.java b/jvm-runtime/src/main/java/io/spine/validation/option/Range.java deleted file mode 100644 index f788517a30..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Range.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.OptionsProto; -import io.spine.option.RangeOption; -import io.spine.validation.Constraint; - -/** - * A validating option that limits a numeric field to be in the specified range. - */ -@Immutable -final class Range extends FieldValidatingOption { - - private Range() { - super(OptionsProto.range); - } - - /** Creates a new instance of this option. */ - static Range create() { - return new Range(); - } - - @Override - public Constraint constraintFor(FieldContext field) { - return new RangeConstraint(optionValue(field), field.targetDeclaration()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/RangeConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/RangeConstraint.java deleted file mode 100644 index e0460a8e8a..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/RangeConstraint.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Range; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.RangeOption; -import io.spine.validation.ComparableNumber; -import io.spine.validation.ConstraintTranslator; - -import static java.lang.String.format; - -/** - * A constraint that checks whether a value fits the ranged described by expressions such as - * {@code int32 value = 5 [(range).value = "[3..5)]}, describing a value that is at least 3 - * and less than 5. - */ -@Immutable -public final class RangeConstraint extends RangedConstraint { - - RangeConstraint(RangeOption option, FieldDeclaration field) { - super(option, rangeFromOption(option, field), field); - } - - @VisibleForTesting - static Range rangeFromOption(RangeOption option, FieldDeclaration field) { - var value = option.getValue(); - return !value.isEmpty() - ? RangeDecl.compile(value, field) - : Range.all(); - } - - @Override - protected String compileErrorMessage(Range range) { - return format("The value of the field `%s` is out of range. Must be %s%s and %s%s.", - field(), - forLowerBound(range), range.lowerEndpoint(), - forUpperBound(range), range.upperEndpoint()); - } - - private static String forLowerBound(Range range) { - return format("greater than %s", orEqualTo(range.lowerBoundType())); - } - - private static String forUpperBound(Range range) { - return format("less than %s", orEqualTo(range.upperBoundType())); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitRange(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/RangeDecl.java b/jvm-runtime/src/main/java/io/spine/validation/option/RangeDecl.java deleted file mode 100644 index fd8cbb49a9..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/RangeDecl.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.Range; -import io.spine.code.proto.FieldDeclaration; -import io.spine.validation.ComparableNumber; -import io.spine.validation.NumberText; - -import java.util.regex.Pattern; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.Range.closed; -import static com.google.common.collect.Range.closedOpen; -import static com.google.common.collect.Range.open; -import static com.google.common.collect.Range.openClosed; - -/** - * Transforms a string value defined in a field declaration into a {@link Range} of - * {@link ComparableNumber}s. - */ -final class RangeDecl { - - /** - * The regular expression for parsing number ranges. - * - *

Defines four groups: - *

    - *
  1. The opening bracket (a {@code [} or a {@code (}). - *
  2. The lower numerical bound. - *
  3. The higher numerical bound. - *
  4. The closing bracket (a {@code ]} or a {@code )}). - *
- * - *

All the groups as well as a {@code ..} divider between the numerical bounds must be - * matched. Extra spaces among the groups and the divider are allowed. - * - *

Examples of a valid number range: - *

    - *
  • {@code [0..1]} - *
  • {@code ( -17.3 .. +146.0 ]} - *
  • {@code [+1..+100)} - *
- * - *

Examples of an invalid number range: - *

    - *
  • {@code 1..5} - missing brackets. - *
  • {@code [0 - 1]} - wrong divider. - *
  • {@code [0 . . 1]} - divider cannot be split with spaces. - *
  • {@code ( .. 0)} - missing lower bound. - *
- */ - private static final Pattern NUMBER_RANGE = Pattern.compile( - "([\\[(])\\s*([+\\-]?[\\d.]+)\\s*\\.\\.\\s*([+\\-]?[\\d.]+)\\s*([])])" - ); - - private final boolean minInclusive; - private final ComparableNumber min; - private final ComparableNumber max; - private final boolean maxInclusive; - - /** - * Transforms the passed range declaration into a range of numbers. - */ - static Range compile(String rangeOptionValue, FieldDeclaration field) { - var decl = new RangeDecl(rangeOptionValue, field); - return decl.toRange(); - } - - private RangeDecl(String rangeOptionValue, FieldDeclaration field) { - var rangeMatcher = NUMBER_RANGE.matcher(rangeOptionValue.trim()); - checkState(rangeMatcher.matches(), - "Malformed range `%s` on the field `%s`. " + - "Must have a form of `[a..b]` " + - "where `a` and `b` are valid literals of the type `%s`. " + - "Please see the documentation of the `(range)` option for more details.", - rangeOptionValue, field, field.javaTypeName()); - this.minInclusive = "[".equals(rangeMatcher.group(1)); - this.min = new NumberText(rangeMatcher.group(2)).toNumber(); - this.max = new NumberText(rangeMatcher.group(3)).toNumber(); - this.maxInclusive = "]".equals(rangeMatcher.group(4)); - } - - private Range toRange() { - if (minInclusive) { - return maxInclusive - ? closed(min, max) - : closedOpen(min, max); - } else { - return maxInclusive - ? openClosed(min, max) - : open(min, max); - } - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/RangedConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/RangedConstraint.java deleted file mode 100644 index 00cc78626c..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/RangedConstraint.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.BoundType; -import com.google.common.collect.Range; -import com.google.errorprone.annotations.Immutable; -import com.google.errorprone.annotations.ImmutableTypeParameter; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.validation.ComparableNumber; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.BoundType.CLOSED; -import static io.spine.util.Exceptions.newIllegalArgumentException; - -/** - * A constraint that puts a numeric field value into a range. - * - *

A field which violates this constraint of its value is out of the range. - * - * @param - * value of the option - */ -@Immutable -public abstract class RangedConstraint<@ImmutableTypeParameter T> extends FieldConstraint { - - private static final String OR_EQUAL_TO = "or equal to "; - - private final Range range; - - RangedConstraint(T optionValue, Range range, FieldDeclaration field) { - super(optionValue, field); - verifyType(field, range); - this.range = range; - } - - @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private static void verifyType(FieldDeclaration field, Range range) { - var fieldType = field.javaType(); - switch (fieldType) { - case INT: // Fallthrough intended. - case LONG: { - if (range.hasLowerBound()) { - checkInteger(range.lowerEndpoint(), field); - } - if (range.hasUpperBound()) { - checkInteger(range.upperEndpoint(), field); - } - return; - } - case FLOAT: // Fallthrough intended. - case DOUBLE: - return; - default: - throw newIllegalArgumentException("Field `%s` cannot have a number bound.", field); - } - } - - private static void checkInteger(ComparableNumber number, FieldDeclaration field) { - checkState(number.isInteger(), - "An integer bound expected for field `%s`, but got `%s`.", field, number); - } - - public final Range range() { - return range; - } - - @Override - public String formattedErrorMessage(FieldContext field) { - return compileErrorMessage(range); - } - - protected abstract String compileErrorMessage(Range range); - - static String orEqualTo(BoundType type) { - return type == CLOSED ? OR_EQUAL_TO : ""; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Require.java b/jvm-runtime/src/main/java/io/spine/validation/option/Require.java deleted file mode 100644 index 7ca9df831a..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Require.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.Descriptor; -import io.spine.option.OptionsProto; -import io.spine.type.MessageType; -import io.spine.validation.Constraint; - -import java.util.Optional; - -/** - * A message option that defines a combination of required fields for the message. - * - *

The fields are separated with a {@code |} symbol and combined with a {@code &} symbol. - * - *

Example: - *

- *     {@code
- *     message PersonName {
- *         option (require).fields = "given_name|honorific_prefix & family_name";
- *         string honorific_prefix = 1;
- *         string given_name = 2;
- *         string middle_name = 3;
- *     }
- *     }
- * 
- *

The {@code PersonName} message is valid against the {@code RequiredField} either - * if it has a non-default family name, or both the honorific prefix and family name. - */ -@Immutable -public final class Require implements ValidatingOption { - - @Override - @SuppressWarnings({"ImpossibleNullComparison", "ConstantValue"}) /* - Keep `result == null` check for the backward compatibility. - */ - public Optional valueFrom(Descriptor message) { - var result = message.getOptions() - .getExtension(OptionsProto.requiredField); - return result == null || result.isEmpty() - ? Optional.empty() - : Optional.of(result); - } - - @Override - public Constraint constraintFor(MessageType messageType) { - var expression = valueFrom(messageType.descriptor()).orElse(""); - return new RequiredFieldConstraint(expression, messageType); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Required.java b/jvm-runtime/src/main/java/io/spine/validation/option/Required.java deleted file mode 100644 index 47d5b56e7c..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Required.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; -import io.spine.base.CommandMessage; -import io.spine.base.EntityState; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -import static com.google.protobuf.Descriptors.FieldDescriptor.JavaType.BYTE_STRING; -import static com.google.protobuf.Descriptors.FieldDescriptor.JavaType.ENUM; -import static com.google.protobuf.Descriptors.FieldDescriptor.JavaType.MESSAGE; -import static com.google.protobuf.Descriptors.FieldDescriptor.JavaType.STRING; - -/** - * An option that makes a field {@code required}. - * - *

If a {@code required} field is missing, an error is produced. - */ -@Immutable -public class Required extends FieldValidatingOption { - - static final ImmutableSet CAN_BE_REQUIRED = ImmutableSet.of( - MESSAGE, ENUM, STRING, BYTE_STRING - ); - - /** - * Creates a new instance of this option. - */ - Required() { - super(OptionsProto.required); - } - - /** - * Creates a new instance. - * - *

If the specified parameter is {@code true}, a returned option always assumes a field to - * be {@code required}, regardless of the field value. - * If the specified parameter is {@code false}, a returned option checks the actual value. - * - * @param strict - * specifies if a field is assumed to be a required one regardless of the actual - * Protobuf option value - * @return a new instance of the {@code Required} option - */ - public static Required create(boolean strict) { - return strict - ? new AlwaysRequired() - : new Required(); - } - - private boolean notAssumingRequired(FieldContext context) { - var defaultValue = context.targetDeclaration().isId(); - return valueFrom(context.target()) - .orElse(defaultValue); - } - - @Override - public boolean shouldValidate(FieldContext context) { - return notAssumingRequired(context); - } - - /** - * Produces warnings if the {@code required} option was applied incorrectly. - * - *

Examples of incorrect application include attempting to apply the option to a numeric - * field. - * - * @param field - * a value that the option is applied to - */ - @SuppressWarnings("UseOfSystemOutOrSystemErr") /* We're migrating off runtime validation - AND we do not want dependency of Validation Runtime on Spine Logging. - So we use `System.err` for the warnings. */ - void checkUsage(FieldDeclaration field) { - var type = field.javaType(); - if (!CAN_BE_REQUIRED.contains(type) && field.isNotCollection()) { - var isTheFirstField = field.descriptor().getIndex() == 0; - if (isTheFirstField) { - // The first field declared in a message type could be assumed as required - // because by convention it is an ID field of the message. - // If so, do not log the warning message for this field because ID fields - // could be of any reasonable type. - var messageClass = field.declaringType().javaClass(); - if (CommandMessage.class.isAssignableFrom(messageClass) - || EntityState.class.isAssignableFrom(messageClass)) { - return; - } - } - var typeName = field.descriptor().getType().name(); - System.err.printf( - "The field `%s.%s` has the type %s and" + - " should not be declared as `(required)`.%n", - field.declaringType().name(), field.name(), typeName); - } - } - - @Override - public Constraint constraintFor(FieldContext context) { - checkUsage(context.targetDeclaration()); - var value = notAssumingRequired(context); - return new RequiredConstraint(value, context.targetDeclaration()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/RequiredConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/RequiredConstraint.java deleted file mode 100644 index fec6ed8daa..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/RequiredConstraint.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.diags.ViolationText; - -/** - * A constraint that, when applied to a field, checks whether the field is set to a non-default - * value. - */ -@Immutable -public final class RequiredConstraint extends FieldConstraint { - - RequiredConstraint(boolean required, - FieldDeclaration declaration) { - super(required, declaration); - } - - @Override - @SuppressWarnings("deprecation") /* Old validation won't migrate to the new error messages. */ - public String formattedErrorMessage(FieldContext field) { - var ifMissing = new IfMissing(); - var option = ifMissing.valueOrDefault(field.target()); - return ViolationText.errorMessage(option, option.getMsgFormat()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitRequired(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/RequiredFieldConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/RequiredFieldConstraint.java deleted file mode 100644 index 5c5d01dcf0..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/RequiredFieldConstraint.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.type.MessageType; -import io.spine.validation.Alternative; -import io.spine.validation.Constraint; -import io.spine.validation.ConstraintTranslator; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.lang.String.format; - -/** - * A constraint that, when applied to a message, checks whether the specified combination of fields - * has non-default values. - */ -@Immutable -public final class RequiredFieldConstraint implements Constraint { - - private final String optionValue; - private final MessageType messageType; - private final ImmutableSet alternatives; - - RequiredFieldConstraint(String optionValue, MessageType messageType) { - this.optionValue = checkNotNull(optionValue); - this.messageType = checkNotNull(messageType); - checkNotNull(optionValue); - this.alternatives = Alternative.parse(optionValue, messageType); - } - - @Override - public MessageType targetType() { - return messageType; - } - - @Override - public String formattedErrorMessage(FieldContext field) { - return format("Field named `%s` is not found.", field.targetDeclaration()); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitRequiredField(this); - } - - /** - * Obtains the raw option value. - */ - public String optionValue() { - return optionValue; - } - - /** - * Obtains the set of alternative conjunctive field expressions. - * - * @see Alternative - */ - public ImmutableSet alternatives() { - return alternatives; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/SetOnce.java b/jvm-runtime/src/main/java/io/spine/validation/option/SetOnce.java deleted file mode 100644 index 01b8710b6b..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/SetOnce.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Descriptors.FieldDescriptor; -import io.spine.code.proto.FieldOption; -import io.spine.option.OptionsProto; - -import java.util.Optional; - -/** - * An option that indicates that a field value cannot be changed. - */ -@Immutable -public final class SetOnce extends FieldOption { - - /** - * Specifies the extension that corresponds to this option. - */ - private SetOnce() { - super(OptionsProto.setOnce); - } - - /** Obtains a value of the {@code set_once} option from the given field. */ - public static Optional from(FieldDescriptor field){ - return new SetOnce().valueFrom(field); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/StandardOptionFactory.java b/jvm-runtime/src/main/java/io/spine/validation/option/StandardOptionFactory.java deleted file mode 100644 index 3b1ee82109..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/StandardOptionFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.annotation.Internal; - -/** - * A {@link ValidatingOptionFactory} of known options. - * - *

The options produced by a {@code StandardOptionFactory} are supported - * by the code validation generation. - * - *

If code generation takes place, checking constraints produced by these options is obsolete. - */ -@Internal -@Immutable -public interface StandardOptionFactory extends ValidatingOptionFactory { -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/Valid.java b/jvm-runtime/src/main/java/io/spine/validation/option/Valid.java deleted file mode 100644 index ce70c4c1df..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/Valid.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; - -/** - * An option that indicates that the message field should be validated according to its constraints. - */ -@Immutable -public final class Valid extends FieldValidatingOption { - - /** Creates a new instance of this option. */ - public Valid() { - super(OptionsProto.validate); - } - - @Override - public Constraint constraintFor(FieldContext field) { - boolean shouldValidate = optionValue(field); - return new ValidateConstraint(shouldValidate, field.targetDeclaration()); - } - - @Override - public boolean shouldValidate(FieldContext context) { - return valueFrom(context).orElse(false); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/ValidateConstraint.java b/jvm-runtime/src/main/java/io/spine/validation/option/ValidateConstraint.java deleted file mode 100644 index 2e57144a00..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/ValidateConstraint.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.validation.ConstraintTranslator; - -/** - * A constraint that, when applied to a message field, signifies that the message should have valid - * properties. - */ -public final class ValidateConstraint extends FieldConstraint { - - ValidateConstraint(Boolean optionValue, FieldDeclaration field) { - super(optionValue, field); - } - - @Override - public String formattedErrorMessage(FieldContext field) { - // This option does not have an error message anymore. - return ""; - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitValidate(this); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOption.java b/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOption.java deleted file mode 100644 index a25aac16ff..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOption.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.errorprone.annotations.Immutable; -import com.google.errorprone.annotations.ImmutableTypeParameter; -import com.google.protobuf.Descriptors.GenericDescriptor; -import io.spine.code.proto.Option; -import io.spine.validation.Constraint; - -/** - * The interface common to all standard validation options. - * - * @param - * data type that this option holds, e.g., - * {@linkplain io.spine.option.OptionsProto#required required option} would - * hold a {@code Boolean} - * @param - * reflective container which describes the entity this option is applied to - * @param - * kind of entity that this option is applied to - */ -@Immutable -interface ValidatingOption<@ImmutableTypeParameter T, - @ImmutableTypeParameter C, - @ImmutableTypeParameter K extends GenericDescriptor> - extends Option { - - /** - * Creates a {@link Constraint} for the given field based on the value of the option. - */ - Constraint constraintFor(C field); -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionFactory.java b/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionFactory.java deleted file mode 100644 index 4db59aeb8f..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionFactory.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import io.spine.annotation.SPI; -import io.spine.validation.CustomConstraint; - -import java.util.ServiceLoader; -import java.util.Set; - -/** - * A factory of validation options for message fields. - * - *

This interface has no abstract methods. All the overridable methods are optional for - * implementation. The default implementation retrieves empty sets. - * - *

This interface is designed as a Service Provider Interface. The implementations are - * {@linkplain ValidatingOptionsLoader loaded} via the {@link ServiceLoader} mechanism. - * - * @see FieldValidatingOption - * @see CustomConstraint - */ -@SPI -@Immutable -public interface ValidatingOptionFactory { - - /** - * Obtains additional options for {@code bool} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forBoolean() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for {@code bytes} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forByteString() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for {@code double} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forDouble() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for enum fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forEnum() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for {@code float} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forFloat() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for {@code int64}, {@code sint64}, {@code uint64}, - * {@code fixed64}, and {@code sfixed64} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forInt() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for {@code int32}, {@code sint32}, {@code uint32}, - * {@code fixed32}, and {@code sfixed32} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forLong() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for message fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forMessage() { - return ImmutableSet.of(); - } - - /** - * Obtains additional options for {@code string} fields validation. - * - * @return the set of additional options - * @implSpec By default, obtains an empty set. - */ - default Set> forString() { - return ImmutableSet.of(); - } - - /** - * Obtains all the options declared by this factory. - * - * @return the set of all additional options - */ - default Set> all() { - return ImmutableSet - .>builder() - .addAll(forBoolean()) - .addAll(forByteString()) - .addAll(forDouble()) - .addAll(forEnum()) - .addAll(forFloat()) - .addAll(forInt()) - .addAll(forLong()) - .addAll(forMessage()) - .addAll(forString()) - .build(); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionsLoader.java b/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionsLoader.java deleted file mode 100644 index 66d91a0038..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionsLoader.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option; - -import com.google.common.collect.ImmutableSet; -import com.google.protobuf.Extension; -import com.google.protobuf.ExtensionRegistry; -import io.spine.annotation.Internal; -import io.spine.code.proto.AbstractOption; - -import java.util.ServiceLoader; - -import static java.util.ServiceLoader.load; - -/** - * Loads the implementations of {@link ValidatingOptionFactory} using a {@link ServiceLoader}. - * - *

Caches the loaded results and never reloads the services. - */ -@Internal -public enum ValidatingOptionsLoader { - - INSTANCE; - - private final ImmutableSet implementations; - - ValidatingOptionsLoader() { - var loader = load(ValidatingOptionFactory.class); - this.implementations = ImmutableSet.copyOf(loader); - } - - @SuppressWarnings("unused") - public static void registerCustomOptions(ExtensionRegistry target) { - var implementations = INSTANCE.implementations(); - implementations.stream() - .flatMap(factory -> factory.all().stream()) - .map(AbstractOption::extension) - .filter(extension -> isExtensionRegistered(target, extension)) - .forEach(target::add); - } - - @SuppressWarnings("deprecation" /* We keep using the deprecated `findMutableExtensionByName()` - call because we use it only for detecting extension registration. - We do not use the mutability of the extension. */ - ) - private static boolean isExtensionRegistered(ExtensionRegistry registry, - Extension extension) { - var name = extension.getDescriptor().getFullName(); - var mutableAbsent = registry.findMutableExtensionByName(name) == null; - var immutableAbsent = registry.findImmutableExtensionByName(name) == null; - return mutableAbsent && immutableAbsent; - } - - /** - * Obtains all the implementations of {@link ValidatingOptionFactory} available - * at current runtime. - * - *

Uses a {@link ServiceLoader} to scan for the SPI implementations. - * - * @return a stream of all available {@link ValidatingOptionFactory} implementations - * @implNote The implementations are actually loaded when the enum instance is created. - * This method only accesses the loaded services. - */ - public ImmutableSet implementations() { - return implementations; - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/package-info.java b/jvm-runtime/src/main/java/io/spine/validation/option/package-info.java deleted file mode 100644 index 2814c49492..0000000000 --- a/jvm-runtime/src/main/java/io/spine/validation/option/package-info.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * This package contains the options and the option factories used for message validation. - */ - -@CheckReturnValue -@NullMarked -package io.spine.validation.option; - -import com.google.errorprone.annotations.CheckReturnValue; - -import org.jspecify.annotations.NullMarked; diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt index d9b6398464..d4ee9b8983 100644 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt +++ b/jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt @@ -127,6 +127,8 @@ import io.spine.annotation.SPI * Every implementation of [MessageValidator] must have a public, no-args constructor. * * @param M the type of Protobuf [Message][Message] being validated. + * + * @see ValidatorRegistry */ @SPI public interface MessageValidator { diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/NumberConversion.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/NumberConversion.kt deleted file mode 100644 index d529737743..0000000000 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/NumberConversion.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -/** - * Allows determining safe variants of number conversions without losing precision. - * - * Mimics the actual automatic conversions that are applied to primitive number types. - */ -internal object NumberConversion { - - private val checkers: List> = listOf( - ByteChecker(), ShortChecker(), IntegerChecker(), LongChecker(), - FloatChecker(), DoubleChecker() - ) - - /** - * Determines if the supplied [number] can be safely converted to the type of - * [anotherNumber] without loss of precision. - */ - @JvmStatic - fun check(number: Number, anotherNumber: Number): Boolean { - val unwrappedNumber = unwrap(number) - val unwrappedAnotherNumber = unwrap(anotherNumber) - for (caster in checkers) { - if (caster.supports(unwrappedNumber)) { - return caster.isConvertible(unwrappedAnotherNumber) - } - } - return false - } - - /** - * Unwraps the actual value from the [ComparableNumber]. - */ - private fun unwrap(number: Number): Number = - if (number is ComparableNumber) number.value() else number -} - -/** - * Allows determining which types a number of type [T] can be converted to. - * - * @param T The type of the number. - * @property casterType The class of the number. - * @property convertibleTypes The list of types that the number can be safely converted to. - */ -private sealed class ConversionChecker( - val casterType: Class, - val convertibleTypes: List> -) { - /** - * Determines if the supplied [number] can be safely converted to the type of the caster. - */ - fun isConvertible(number: Number): Boolean { - val numberClass = number.javaClass - return convertibleTypes.any { it == numberClass } - } - - /** - * Determines if the supplied [number] is supported by the caster. - */ - fun supports(number: Number): Boolean = casterType.isInstance(number) -} - -private class ByteChecker : ConversionChecker( - Byte::class.javaObjectType, - listOf(Byte::class.javaObjectType) -) - -private class ShortChecker : ConversionChecker( - Short::class.javaObjectType, - listOf( - Byte::class.javaObjectType, - Short::class.javaObjectType - ) -) - -private class IntegerChecker : ConversionChecker( - Int::class.javaObjectType, - listOf( - Byte::class.javaObjectType, - Short::class.javaObjectType, - Int::class.javaObjectType - ) -) - -private class LongChecker : ConversionChecker( - Long::class.javaObjectType, - listOf( - Byte::class.javaObjectType, - Short::class.javaObjectType, - Int::class.javaObjectType, - Long::class.javaObjectType - ) -) - -private class FloatChecker : ConversionChecker( - Float::class.javaObjectType, - listOf(Float::class.javaObjectType) -) - -private class DoubleChecker : ConversionChecker( - Double::class.javaObjectType, - listOf( - Float::class.javaObjectType, - Double::class.javaObjectType - ) -) diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/RuntimeMessageValidator.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/RuntimeMessageValidator.kt deleted file mode 100644 index 8339f0e14d..0000000000 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/RuntimeMessageValidator.kt +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import com.google.common.collect.Range -import com.google.protobuf.Descriptors -import com.google.protobuf.Message -import io.spine.base.Field.named -import io.spine.code.proto.FieldContext -import io.spine.code.proto.FieldDeclaration -import io.spine.code.proto.FieldName -import io.spine.protobuf.TypeConverter.toAny -import io.spine.protobuf.ensureUnpacked -import io.spine.type.MessageType -import io.spine.validation.MessageValue.atTopLevel -import io.spine.validation.MessageValue.nestedIn -import io.spine.validation.option.DistinctConstraint -import io.spine.validation.option.GoesConstraint -import io.spine.validation.option.IsRequiredConstraint -import io.spine.validation.option.PatternConstraint -import io.spine.validation.option.RangedConstraint -import io.spine.validation.option.RequiredConstraint -import io.spine.validation.option.RequiredFieldConstraint -import io.spine.validation.option.ValidateConstraint -import java.util.* -import java.util.regex.Pattern -import java.util.stream.Collectors.toList - -/** - * Validates a given message according to the constraints. - * - * The output result of this [ConstraintTranslator] is a [ValidationError]. - * - * The class uses [Optional] to keep its compatibility with the remaining Java code. - */ -@Suppress("TooManyFunctions") // Covers almost all runtime validation. -internal class RuntimeMessageValidator -private constructor( - private val validatedMessage: MessageValue -) : ConstraintTranslator> { - - private val violations = mutableListOf() - - /** - * Creates a new validator for the [top-level][MessageValue.atTopLevel] [message]. - */ - constructor(message: Message) : this(atTopLevel(message)) - - /** - * Creates a new validator for the [message] with the specific field [context]. - */ - constructor(message: Message, context: FieldContext) : this(nestedIn(context, message)) - - override fun visitRange(constraint: RangedConstraint<*>) { - val value = validatedMessage.valueOf(constraint.field()) - val range = constraint.range() - checkTypeConsistency(range, value) - value.values() - .map { any -> Number::class.java.cast(any) } - .map { number -> ComparableNumber(number) } - .filter(range.negate()) - .map { comparableNumber -> violation(constraint, value, comparableNumber.value()) } - .forEach { e: ConstraintViolation -> violations.add(e) } - } - - override fun visitRequired(constraint: RequiredConstraint) { - if (constraint.optionValue()) { - val fieldValue = validatedMessage.valueOf(constraint.field()) - if (fieldValue.isDefault) { - violations.add(violation(constraint, fieldValue)) - } - } - } - - override fun visitPattern(constraint: PatternConstraint) { - val fieldValue = validatedMessage.valueOf(constraint.field()) - val regex = constraint.regex() - val flags = constraint.flagsMask() - val compiledPattern = Pattern.compile(regex, flags) - val partialMatch = constraint.allowsPartialMatch() - fieldValue.nonDefault() - .filter { value: Any -> - val matcher = compiledPattern.matcher(value as String) - if (partialMatch) { - !matcher.find() - } else { - !matcher.matches() - } - } - .map { value: Any -> - val violation = violation(constraint, fieldValue, value) - val withRegex = violation.message.toBuilder() - .withRegex(regex) - .build() - violation.copy { - message = withRegex - } - } - .forEach { e: ConstraintViolation -> violations.add(e) } - } - - override fun visitDistinct(constraint: DistinctConstraint) { - val fieldValue = validatedMessage.valueOf(constraint.field()) - val duplicates = findDuplicates(fieldValue) - val distinctViolations = duplicates.map { duplicate -> - violation(constraint, fieldValue, duplicate) - } - violations.addAll(distinctViolations) - } - - override fun visitGoesWith(constraint: GoesConstraint) { - val field = constraint.field() - val value = validatedMessage.valueOf(field) - val declaration = withField(validatedMessage, constraint) - val withFieldName = constraint.optionValue().with - check(declaration.isPresent) { - "The field `$withFieldName` specified in the `(goes).with` option is not found." - } - val withField = declaration.get() - if (!value.isDefault && fieldValueNotSet(withField)) { - val violation = violation(constraint, value) - val template = violation.message.toBuilder() - .withField(field) - .withCompanion(withField) - violations.add( - violation.copy { - message = template.build() - } - ) - } - } - - override fun visitValidate(constraint: ValidateConstraint) { - val fieldValue = validatedMessage.valueOf(constraint.field()) - if (!fieldValue.isDefault) { - val childViolations = fieldValue.values() - .map { any -> (any as Message).ensureUnpacked() } - .map { msg -> childViolations(fieldValue.context(), msg) } - .flatMap { violations -> violations.stream() } - .collect(toList()) - violations.addAll(childViolations) - } - } - - override fun visitRequiredField(constraint: RequiredFieldConstraint) { - val check = RequiredFieldCheck( - constraint.optionValue(), - constraint.alternatives(), - validatedMessage - ) - val violation = check.perform() - violation.ifPresent { e -> violations.add(e) } - } - - override fun visitRequiredOneof(constraint: IsRequiredConstraint) { - val fieldValue = validatedMessage.valueOf(constraint.declaration()) - val noneSet = fieldValue.isEmpty - if (noneSet) { - val oneofName = constraint.oneofName() - val oneofField = named(oneofName.value()) - val targetType = constraint.targetType() - violations.add( - constraintViolation { - message = constraint.errorMessage(validatedMessage.context()) - fieldPath = oneofField.path() - typeName = targetType.name().value - } - ) - } - } - - override fun visitCustom(constraint: CustomConstraint) { - val violations = constraint.validate(validatedMessage) - this.violations.addAll(violations) - } - - /** - * Obtains the resulting [ValidationError] or an [Optional.empty] if - * the message value is valid. - */ - override fun translate(): Optional = - if (violations.isEmpty()) { - Optional.empty() - } else { - Optional.of( - validationError { - constraintViolation += violations - } - ) - } - - private fun fieldValueNotSet(field: FieldDeclaration): Boolean = - validatedMessage - .valueOf(field.descriptor()) - .map { it.isDefault } - .orElse(false) - - private companion object { - - fun childViolations(field: FieldContext, message: Message): List { - val messageValue = nestedIn(field, message.ensureUnpacked()) - val childInterpreter = RuntimeMessageValidator(messageValue) - return Constraints.of(MessageType.of(message), field) - .runThrough(childInterpreter) - .map { error: ValidationError -> error.constraintViolationList } - .orElse(emptyList()) - } - } -} - -private fun findDuplicates(fieldValue: FieldValue): Set<*> = - fieldValue.values().toList() - .groupingBy { it } - .eachCount() - .filter { (_, count) -> count > 1 } - .keys - -private fun withField(message: MessageValue, goes: GoesConstraint): Optional { - val withField = FieldName.of(goes.optionValue().with) - for (field in message.declaration().fields()) { - if (withField == field.name()) { - return Optional.of(field) - } - } - return Optional.empty() -} - -private fun violation(constraint: Constraint, value: FieldValue): ConstraintViolation = - violation(constraint, value, null) - -private fun violation( - constraint: Constraint, - value: FieldValue, - violatingValue: Any? -): ConstraintViolation { - val context = value.context() - val fieldPath = context.fieldPath() - val typeName = constraint.targetType().name() - val violation = ConstraintViolation.newBuilder() - .setMessage(constraint.errorMessage(context)) - .setFieldPath(fieldPath) - .setTypeName(typeName.value()) - if (violatingValue != null) { - violation.setFieldValue(toFieldValue(violatingValue)) - } - return violation.build() -} - -/** - * Converts the `violatingValue` to a wrapped [Any]. - * - * If the violation is caused by an enum, unwraps the enum value from the descriptor before - * doing the conversion. - */ -private fun toFieldValue(violatingValue: Any): com.google.protobuf.Any = - if (violatingValue is Descriptors.EnumValueDescriptor) { - toAny(violatingValue.toProto()) - } else { - toAny(violatingValue) - } - -private fun checkTypeConsistency(range: Range, value: FieldValue) { - if (range.hasLowerBound() && range.hasUpperBound()) { - val upper = range.upperEndpoint().toText() - val lower = range.lowerEndpoint().toText() - check(upper.isOfSameType(lower)) { - "Boundaries have inconsistent types: lower is `$lower`, upper is `$upper`." - } - checkBoundaryAndValue(upper, value) - } else { - checkSingleBoundary(range, value) - } -} - -private fun checkSingleBoundary(range: Range, value: FieldValue) { - val singleBoundary = - if (range.hasLowerBound()) { - range.lowerEndpoint().toText() - } else { - range.upperEndpoint().toText() - } - checkBoundaryAndValue(singleBoundary, value) -} - -private fun checkBoundaryAndValue(boundary: NumberText, value: FieldValue) { - val boundaryNumber = boundary.toNumber() - val valueNumber = value.singleValue() as Number - check(NumberConversion.check(boundaryNumber, valueNumber)) { - "Boundary values must have types consistent with the values they bind: " + - "boundary is $boundary`, value is `$valueNumber`." - } -} diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/Validator.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/Validator.kt deleted file mode 100644 index 853cb7b0c7..0000000000 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/Validator.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2026, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import com.google.protobuf.Message -import kotlin.reflect.KClass - -/** - * Marks the class as a message validator. - * - * Applying this annotation to an implementation of [MessageValidator] - * makes the class visible to the validation library. - * - * @deprecated Use [MessageValidator] directly. The message type will be - * obtained via reflection. - */ -@Deprecated("Use `MessageValidator` directly.") -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -public annotation class Validator( - - /** - * The class of the validated message. - */ - val value: KClass -) diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/ValidatorRegistry.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/ValidatorRegistry.kt index 524c45372d..a7460bd348 100644 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/ValidatorRegistry.kt +++ b/jvm-runtime/src/main/kotlin/io/spine/validation/ValidatorRegistry.kt @@ -52,6 +52,8 @@ import com.google.protobuf.Any as ProtoAny * * The registry also automatically loads validators from the classpath using * the [ServiceLoader] mechanism. + * + * @see MessageValidator */ @ThreadSafe public object ValidatorRegistry { diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/option/NonPrimitiveOptionFactory.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/option/NonPrimitiveOptionFactory.kt deleted file mode 100644 index 1f576f1cc4..0000000000 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/option/NonPrimitiveOptionFactory.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option - -import com.google.auto.service.AutoService -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Sets -import com.google.errorprone.annotations.Immutable -import io.spine.annotation.Internal - -/** - * A factory of standard validating options for non-primitive types. - */ -@AutoService(ValidatingOptionFactory::class) -@Internal -@Immutable -public class NonPrimitiveOptionFactory : StandardOptionFactory { - - override fun forString(): MutableSet> { - return Sets.union(stringOptions, collectionOptions) - } - - override fun forByteString(): Set> = collectionOptions - - override fun forEnum(): Set> = collectionOptions - - override fun forMessage(): Set> = - Sets.union>(messageOptions, collectionOptions) - - private companion object { - - private val stringOptions by lazy { - ImmutableSet.of>( - Pattern.create() - ) - } - - private val collectionOptions by lazy { - ImmutableSet.of>( - Required.create(false), - Goes.create(), - Distinct.create() - ) - } - - private val messageOptions by lazy { - ImmutableSet.of>( - Valid() - ) - } - } -} diff --git a/jvm-runtime/src/main/kotlin/io/spine/validation/option/NumberOptionFactory.kt b/jvm-runtime/src/main/kotlin/io/spine/validation/option/NumberOptionFactory.kt deleted file mode 100644 index fa1caeb615..0000000000 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/option/NumberOptionFactory.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option - -import com.google.auto.service.AutoService -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Sets -import com.google.errorprone.annotations.Immutable -import io.spine.annotation.Internal - -/** - * An implementation of [ValidatingOptionFactory] which adds validation options for numbers. - * - * Creates `(max)`, `(min)`, and `(range)` options for number fields. - */ -@AutoService(ValidatingOptionFactory::class) -@Internal -@Immutable -public class NumberOptionFactory : StandardOptionFactory { - - override fun forInt(): MutableSet> = - Sets.union>(numberOptions, collectionOptions) - - override fun forLong(): MutableSet> = - Sets.union>(numberOptions, collectionOptions) - - override fun forFloat(): MutableSet> = - Sets.union>(numberOptions, collectionOptions) - - override fun forDouble(): MutableSet> = - Sets.union>(numberOptions, collectionOptions) - - private companion object { - - private val numberOptions by lazy { - ImmutableSet.of>( - Max.create(), - Min.create(), - Range.create() - ) - } - - private val collectionOptions by lazy { - ImmutableSet.of>( - Required.create(false), - Goes.create(), - Distinct.create()) - } - } -} diff --git a/jvm-runtime/src/test/kotlin/io/spine/validation/ComparableNumberSpec.kt b/jvm-runtime/src/test/kotlin/io/spine/validation/ComparableNumberSpec.kt deleted file mode 100644 index b123a285b7..0000000000 --- a/jvm-runtime/src/test/kotlin/io/spine/validation/ComparableNumberSpec.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import com.google.common.testing.EqualsTester -import com.google.common.testing.NullPointerTester -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test - -@DisplayName("`ComparableNumber` should") -internal class ComparableNumberSpec { - - @Test - fun `not accept nulls`() { - val tester = NullPointerTester() - tester.testAllPublicConstructors(ComparableNumber::class.java) - tester.testAllPublicInstanceMethods(ComparableNumber(42)) - } - - @Nested inner class - `have a consistent equality relationship` { - - @Test - fun `between instances`() { - val longMaxValue = Long.MAX_VALUE.toString() - val doubleMinValue = Double.MIN_VALUE.toString() - EqualsTester() - .addEqualityGroup(NumberText(1L).toNumber(), NumberText("1").toNumber()) - .addEqualityGroup( - NumberText(longMaxValue).toNumber(), - NumberText(Long.MAX_VALUE).toNumber() - ) - .addEqualityGroup( - NumberText(doubleMinValue).toNumber(), - NumberText(Double.MIN_VALUE).toNumber() - ) - .testEquals() - } - - @Test - fun `between instances and primitives`() { - val doubleValue = Double.MAX_VALUE - val intValue = Int.MAX_VALUE - val floatValue = Float.MAX_VALUE - val longValue = Long.MAX_VALUE - - EqualsTester() - .addEqualityGroup(doubleValue, ComparableNumber(doubleValue).toDouble()) - .addEqualityGroup(intValue, ComparableNumber(intValue).toInt()) - .addEqualityGroup(floatValue, ComparableNumber(floatValue).toFloat()) - .addEqualityGroup(longValue, ComparableNumber(longValue).toLong()) - .testEquals() - } - } -} diff --git a/jvm-runtime/src/test/kotlin/io/spine/validation/MessageValueSpec.kt b/jvm-runtime/src/test/kotlin/io/spine/validation/MessageValueSpec.kt deleted file mode 100644 index 12427eca8c..0000000000 --- a/jvm-runtime/src/test/kotlin/io/spine/validation/MessageValueSpec.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import com.google.common.testing.NullPointerTester -import com.google.protobuf.Descriptors.OneofDescriptor -import com.google.protobuf.StringValue -import com.google.protobuf.Value -import com.google.protobuf.value -import io.kotest.matchers.optional.shouldBePresent -import io.kotest.matchers.shouldBe -import io.spine.code.proto.FieldContext -import io.spine.testing.ClassTest -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -@DisplayName("`MessageValue` should") -internal class MessageValueSpec : ClassTest(MessageValue::class.java) { - - override fun configure(tester: NullPointerTester) { - tester.setDefault(FieldContext::class.java, FieldContext.empty()) - } - - @Nested inner class - `obtain 'oneof' value` { - - @Test - fun `using the valid descriptor`() { - val expected = false - val message = value { boolValue = expected } - val value = MessageValue.atTopLevel(message) - assertOneofValue(value, expected) - } - - @Test - fun `throwing 'IAE' if a oneof is not declared in a message`() { - val message = StringValue.getDefaultInstance() - val value = MessageValue.atTopLevel(message) - assertThrows { - value.valueOf(VALUE_ONEOF) - } - } - - private fun assertOneofValue(message: MessageValue, expectedValue: Any) { - val optionalValue = message.valueOf(VALUE_ONEOF) - optionalValue.shouldBePresent() - val value = optionalValue.get() - value.singleValue() shouldBe expectedValue - } - } - - companion object { - private val VALUE_ONEOF: OneofDescriptor = Value.getDescriptor().oneofs[0] - } -} diff --git a/jvm-runtime/src/test/kotlin/io/spine/validation/NumberConversionSpec.kt b/jvm-runtime/src/test/kotlin/io/spine/validation/NumberConversionSpec.kt deleted file mode 100644 index a62ab7bb7b..0000000000 --- a/jvm-runtime/src/test/kotlin/io/spine/validation/NumberConversionSpec.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import io.spine.testing.UtilityClassTest -import io.spine.validation.NumberConversion.check -import java.math.BigDecimal -import java.util.stream.Stream -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource - -@DisplayName("`NumberConversion` utility should") -internal class NumberConversionSpec : - UtilityClassTest(NumberConversion::class.java) { - - @Nested inner class - `tell that it is possible to convert to` { - - @Test - fun byte() = - assertTrue(check("1".toByte(), "2".toByte())) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#shorts") - fun short(shortNumber: Number) = - assertTrue(check("1".toShort(), shortNumber)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#integers") - fun integer(integerNumber: Number) = - assertTrue(check(1, integerNumber)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#longs") - fun long(longNumber: Number) = - assertTrue(check(1L, longNumber)) - - @Test - fun float() = - assertTrue(check(1.0f, 3.14f)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#doubles") - fun double(doubleNumber: Number) = - assertTrue(check(1.0, doubleNumber)) - } - - @Nested inner class - `tell that it is not possible to convert` { - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#nonBytes") - fun `non-'byte' to 'byte'`(nonByte: Number) = - assertFalse(check("1".toByte(), nonByte)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#nonShorts") - fun `non-'short' to 'short'`(nonShort: Number) { - assertFalse(check("1".toShort(), nonShort)) - } - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#nonIntegers") - fun `non-'integer' to 'integer'`(nonInteger: Number) = - assertFalse(check(1, nonInteger)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#nonLongs") - fun `non-'long' to 'long'`(nonLong: Number) = - assertFalse(check(1L, nonLong)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#nonFloats") - fun `non-'float' to 'float'`(nonFloat: Number) = - assertFalse(check(1.0f, nonFloat)) - - @ParameterizedTest - @MethodSource("$PACKAGE.NumberConversionSpec#nonDoubles") - fun `non-'double' to 'double'`(nonDouble: Number) { - assertFalse(check(1.0, nonDouble)) - } - } - - @Test - fun `tell that 'ComparableNumber' instances are automatically unwrapped`() { - val number = ComparableNumber(3) - assertTrue(check(number, number)) - } - - @Test - fun `tell that 'BigDecimal's are not supported`() = - assertFalse(check(BigDecimal.valueOf(1), 1L)) - - @Suppress("unused") /* Serves as a source for argument values. */ - companion object { - - const val PACKAGE = "io.spine.validation" - - @JvmStatic - fun nonBytes(): Stream = Stream.concat(Stream.of("1".toShort()), nonShorts()) - - @JvmStatic - fun nonShorts(): Stream = Stream.concat(Stream.of(2), nonIntegers()) - - @JvmStatic - fun nonIntegers(): Stream = Stream.concat(Stream.of(3L), nonLongs()) - - @JvmStatic - fun nonLongs(): Stream = Stream.of(4.0, 5.1f) - - @JvmStatic - fun nonFloats(): Stream = Stream.concat(nonDoubles(), Stream.of(4.0)) - - @JvmStatic - fun nonDoubles(): Stream = Stream.of("1".toByte(), "1".toShort(), 2, 3L) - - @JvmStatic - fun shorts(): Stream = Stream.of("1".toByte(), "2".toShort()) - - @JvmStatic - fun integers(): Stream = Stream.concat(shorts(), Stream.of(2)) - - @JvmStatic - fun longs(): Stream = Stream.concat(integers(), Stream.of(3L)) - - @JvmStatic - fun doubles(): Stream = Stream.of(3.14f, 8.19) - } -} diff --git a/jvm-runtime/src/test/kotlin/io/spine/validation/NumberTextSpec.kt b/jvm-runtime/src/test/kotlin/io/spine/validation/NumberTextSpec.kt deleted file mode 100644 index 23ea38c798..0000000000 --- a/jvm-runtime/src/test/kotlin/io/spine/validation/NumberTextSpec.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import com.google.common.testing.EqualsTester -import io.kotest.matchers.shouldBe -import java.util.stream.Stream -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource - -@DisplayName("`NumberText` numbers should") -internal class NumberTextSpec { - - @Test - fun `have a correct equality relationship`() { - EqualsTester() - .addEqualityGroup(NumberText("0.0"), NumberText("0.0")) - .addEqualityGroup(NumberText("0.1"), NumberText("0.10")) - .testEquals() - } - - @Test - fun `recognize that two numbers have different types`() { - val plain = NumberText("1") - val withDecimal = NumberText("1.0") - - plain.isOfSameType(withDecimal) shouldBe false - } - - @Test - fun `recognize that two numbers are of the same type`() { - val fitsIntoInt = NumberText("4") - val maxInt = NumberText(Int.MAX_VALUE.toString()) - - fitsIntoInt.isOfSameType(maxInt) shouldBe true - } - - @Test - fun `compare values`() { - val smaller = NumberText("0.1") - val larger = NumberText("15") - val comparison = smaller.toNumber().compareTo(larger.toNumber()) - - (comparison < 0) shouldBe true - } - - @Test - fun `store numbers that do not fit into 'int'`() { - val longMax = NumberText(Long.MAX_VALUE.toString()) - val lessThanLongMax = NumberText((Long.MAX_VALUE - 1).toString()) - - longMax.toNumber().compareTo(lessThanLongMax.toNumber()) shouldBe 1 - } - - @ParameterizedTest - @MethodSource("textNumbers") - fun `stringify values`(input: Number, expected: String) { - val text = NumberText(input) - text.toString() shouldBe expected - } - - @ParameterizedTest - @MethodSource("malformedNumbers") - fun `throw on malformed numbers`(malformed: String) { - assertThrows { - NumberText(malformed) - } - } - - @Test - fun `throw on a number with too many decimal separators`() { - assertThrows { - NumberText("1.0.0") - } - } - - @Suppress("unused") // invoked via `@MethodSource`. - companion object { - - @JvmStatic - fun textNumbers(): Stream = Stream.of( - Arguments.of(0.0, "0.0"), - Arguments.of(0, "0"), - Arguments.of(-1.0, "-1.0"), - Arguments.of(-1, "-1"), - Arguments.of(-1.23456789, "-1.23456789"), - Arguments.of(-3L, "-3"), - Arguments.of(-2.23456f, "-2.23456") - ) - - @JvmStatic - fun malformedNumbers(): Stream = Stream.of( - Arguments.of("1,0,0"), - /* Even though the expressions below technically evaluate to a number, - they are not allowed because we do not want to build and support - a calculator on Protobuf options. */ - Arguments.of("1,0"), - Arguments.of("2!"), - Arguments.of("2/2"), - Arguments.of("2+2"), - Arguments.of("2-2") - ) - } -} diff --git a/jvm-runtime/src/test/kotlin/io/spine/validation/ValidatingOptionFactorySpec.kt b/jvm-runtime/src/test/kotlin/io/spine/validation/ValidatingOptionFactorySpec.kt deleted file mode 100644 index 53a323d0a4..0000000000 --- a/jvm-runtime/src/test/kotlin/io/spine/validation/ValidatingOptionFactorySpec.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import io.kotest.matchers.collections.shouldBeEmpty -import io.kotest.matchers.shouldBe -import io.spine.validation.option.ValidatingOptionFactory -import java.util.function.Function -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -@DisplayName("`ValidatingOptionFactory` should") -internal class ValidatingOptionFactorySpec { - - /** - * Verifies that [ValidatingOptionFactory] does not force the classes that - * implement this interface to provide implementations for the methods. - * - * The interface has all the methods declared as `default` providing the implementations. - * - * This test checks that the requirement is met by providing the simplest possible - * implementation via an anonymous object. If this test compiles, we're good to go. - */ - @Test - fun `have no abstract methods`() { - val factory = object : ValidatingOptionFactory {} - factory.forBoolean().size shouldBe 0 - } - - @Test - fun `provide empty sets of options for all types by default`() { - val options = object : ValidatingOptionFactory {} - fun assetEmpty(selector: Function>) = - selector.apply(options).shouldBeEmpty() - - assetEmpty { it.forBoolean() } - assetEmpty { it.forByteString() } - assetEmpty { it.forDouble() } - assetEmpty { it.forEnum() } - assetEmpty { it.forFloat() } - assetEmpty { it.forInt() } - assetEmpty { it.forLong() } - assetEmpty { it.forMessage() } - assetEmpty { it.forString() } - } -} diff --git a/jvm-runtime/src/test/kotlin/io/spine/validation/ValidatingOptionLoaderSpec.kt b/jvm-runtime/src/test/kotlin/io/spine/validation/ValidatingOptionLoaderSpec.kt deleted file mode 100644 index a70aff8903..0000000000 --- a/jvm-runtime/src/test/kotlin/io/spine/validation/ValidatingOptionLoaderSpec.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.spine.validation.option.NonPrimitiveOptionFactory -import io.spine.validation.option.NumberOptionFactory -import io.spine.validation.option.ValidatingOptionsLoader -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -@DisplayName("`ValidatingOptionsLoader` should") -internal class ValidatingOptionLoaderSpec { - - @Test - fun `load common options`() { - val loadedClasses = ValidatingOptionsLoader.INSTANCE.implementations() - .map { it::class.java } - .toSet() - - loadedClasses.shouldContainExactlyInAnyOrder( - NumberOptionFactory::class.java, - NonPrimitiveOptionFactory::class.java - ) - } -} diff --git a/pom.xml b/pom.xml index afc054ebd4..9fd4c18b88 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine.tools validation -2.0.0-SNAPSHOT.401 +2.0.0-SNAPSHOT.402 2015 diff --git a/tests/runtime/src/test/kotlin/io/spine/validation/FieldValueSpec.kt b/tests/runtime/src/test/kotlin/io/spine/validation/FieldValueSpec.kt deleted file mode 100644 index 16ee93022b..0000000000 --- a/tests/runtime/src/test/kotlin/io/spine/validation/FieldValueSpec.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation - -import com.google.protobuf.Descriptors.FieldDescriptor.JavaType.STRING -import com.google.protobuf.Descriptors.FieldDescriptor.JavaType.MESSAGE -import com.google.protobuf.Syntax -import com.google.protobuf.Timestamp -import io.kotest.matchers.shouldBe -import io.spine.base.Identifier.newUuid -import io.spine.code.proto.FieldContext -import io.spine.test.validate.field.Stub -import kotlin.streams.toList -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test - -@DisplayName("`FieldValue` should") -internal class FieldValueSpec { - - @Nested inner class - convert { - - @Test - fun `a map to values`() { - val map = buildMap { - put(newUuid(), newUuid()) - put(newUuid(), newUuid()) - } - val fieldValue = FieldValue.of(map, mapContext()) - assertConversion(map.values, fieldValue) - } - - @Test - fun `a repeated field`() { - val repeated = listOf(newUuid(), newUuid()) - val fieldValue = FieldValue.of(repeated, repeatedContext()) - assertConversion(repeated, fieldValue) - } - - @Test - fun `a scalar field`() { - val scalar = newUuid() - val fieldValue = FieldValue.of(scalar, scalarContext()) - assertConversion(listOf(scalar), fieldValue) - } - } - - @Nested inner class - `determine 'JavaType' for` { - - @Test - fun `a map field`() { - val mapValue = FieldValue.of(mapOf(), mapContext()) - mapValue.javaType() shouldBe MESSAGE - } - - @Test - fun `a repeated field`() { - val repeatedValue = FieldValue.of(listOf(), repeatedContext()) - repeatedValue.javaType() shouldBe STRING - } - } - - @Test - fun `handle 'Enum' value`() { - val rawValue = Syntax.SYNTAX_PROTO3 - val enumValue = FieldValue.of(rawValue, scalarContext()) - val expectedValues = listOf(rawValue.valueDescriptor) - assertConversion(expectedValues, enumValue) - } - - @Nested inner class - `check if the value is default for` { - - @Test - fun `repeated fields`() { - assertDefault(FieldValue.of(listOf("", "", ""), repeatedContext())) - assertNotDefault( - FieldValue.of( - listOf("", "abc", ""), - repeatedContext() - ) - ) - } - - @Test - fun `map fields`() { - assertDefault(FieldValue.of(mapOf("aaaa" to ""), mapContext())) - assertNotDefault( - FieldValue.of( - mapOf( - "" to "", - "aaaa" to "aaa", - " " to "" - ), - mapContext() - ) - ) - } - - @Test - fun `string fields`() { - assertDefault(FieldValue.of("", scalarContext())) - assertNotDefault(FieldValue.of(" ", scalarContext())) - } - - private fun assertDefault(value: FieldValue) { - value.isDefault shouldBe true - } - - private fun assertNotDefault(value: FieldValue) { - value.isDefault shouldBe false - } - } - - private fun assertConversion(expectedValues: Collection, fieldValue: FieldValue) { - fieldValue.values().toList() shouldBe expectedValues - } -} - -private val descriptor = Stub.getDescriptor() - -fun mapContext(): FieldContext { - val mapField = descriptor.findFieldByName("map") - return FieldContext.create(mapField) -} - -fun repeatedContext(): FieldContext { - val repeatedField = descriptor.findFieldByName("repeated") - return FieldContext.create(repeatedField) -} - -fun scalarContext(): FieldContext { - val scalarField = descriptor.findFieldByName("scalar") - return FieldContext.create(scalarField) -} diff --git a/tests/runtime/src/test/kotlin/io/spine/validation/ValidateUtilitySpec.kt b/tests/runtime/src/test/kotlin/io/spine/validation/ValidateUtilitySpec.kt index 860a7f88ef..af3451069e 100644 --- a/tests/runtime/src/test/kotlin/io/spine/validation/ValidateUtilitySpec.kt +++ b/tests/runtime/src/test/kotlin/io/spine/validation/ValidateUtilitySpec.kt @@ -28,15 +28,10 @@ package io.spine.validation import com.google.common.testing.NullPointerTester import com.google.protobuf.Message -import io.kotest.matchers.collections.shouldBeEmpty -import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.spine.base.Time import io.spine.code.proto.FieldContext -import io.spine.test.validate.RequiredStringValue import io.spine.testing.UtilityClassTest -import io.spine.validation.Validate.violationsOf -import io.spine.validation.Validate.violationsOfCustomConstraints import io.spine.validation.diags.ViolationText import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -50,16 +45,6 @@ internal class ValidateUtilitySpec : UtilityClassTest(Validate::class. .setDefault(FieldContext::class.java, FieldContext.empty()) } - @Test - fun `run custom validation and obtain no violations if there are no custom constraints`() { - val message = RequiredStringValue.getDefaultInstance() - val violations = violationsOf(message) - val customViolations = violationsOfCustomConstraints(message) - - violations shouldHaveSize 1 - customViolations.shouldBeEmpty() - } - @Test fun `format message from constraint violation`() { val template = templateString { diff --git a/tests/runtime/src/test/kotlin/io/spine/validation/option/FieldValidatingOptionSpec.kt b/tests/runtime/src/test/kotlin/io/spine/validation/option/FieldValidatingOptionSpec.kt deleted file mode 100644 index e4b87e8d88..0000000000 --- a/tests/runtime/src/test/kotlin/io/spine/validation/option/FieldValidatingOptionSpec.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option - -import com.google.common.collect.ImmutableList -import com.google.errorprone.annotations.Immutable -import io.kotest.matchers.optional.shouldBeEmpty -import io.kotest.matchers.optional.shouldBePresent -import io.kotest.matchers.shouldBe -import io.spine.code.proto.FieldContext -import io.spine.test.validate.option.ATestMessageWithConstraint -import io.spine.test.validate.option.TestFieldOptionProto -import io.spine.test.validate.option.aTestMessage -import io.spine.test.validate.option.noValidationTestMessage -import io.spine.testing.TestValues.randomString -import io.spine.validation.Constraint -import io.spine.validation.ConstraintViolation -import io.spine.validation.CustomConstraint -import io.spine.validation.FieldValue -import io.spine.validation.MessageValue -import io.spine.validation.constraintViolation -import org.junit.jupiter.api.Assertions.fail -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -@DisplayName("`FieldValidatingOption` should") -internal class FieldValidatingOptionSpec { - - @Test - fun `return empty value if option is not present in external or field constraints`() { - val msg = ATestMessageWithConstraint.getDefaultInstance() - val value = MessageValue.atTopLevel(msg) - val fieldValue = valueField(value) - val maxLength = MaxLength() - - maxLength.valueFrom(fieldValue.context()).shouldBeEmpty() - } - - @Test - fun `return value if option is present in field option`() { - val msg = aTestMessage { - value = randomString() - } - val value = MessageValue.atTopLevel(msg) - val fieldValue = valueField(value) - val maxLength = MaxLength() - - maxLength.valueFrom(fieldValue.context()).shouldBePresent() - } - - @Test - fun `throw 'IllegalStateException' if a specified option is not a field option`() { - val msg = ATestMessageWithConstraint.getDefaultInstance() - val value = MessageValue.atTopLevel(msg) - val fieldValue = valueField(value) - val maxLength = MaxLength() - - assertThrows { - maxLength.optionValue(fieldValue.context()) - } - } - - @Test - fun `not validate field if option is not present in external or field constraints`() { - val msg = noValidationTestMessage { - value = randomString() - } - val value = MessageValue.atTopLevel(msg) - val fieldValue = valueField(value) - val maxLength = MaxLength() - - maxLength.shouldValidate(fieldValue.context()) shouldBe false - } - - @Test - fun `validate field if option is present in field option`() { - val msg = aTestMessage { - value = randomString() - } - val value = MessageValue.atTopLevel(msg) - val fieldValue = valueField(value) - val maxLength = MaxLength() - - maxLength.shouldValidate(fieldValue.context()) shouldBe true - } - - private fun valueField(value: MessageValue): FieldValue { - return value.valueOf("value").orElseGet { fail() } - } -} - -/** - * Creates a new instance of this constraint. - * - * @param optionValue A value that describes the field constraints. - * @param field The constrained field. - */ -@Immutable -private class MaxLengthConstraint( - optionValue: Int, - field: FieldContext -) : FieldConstraint(optionValue, field.targetDeclaration()), CustomConstraint { - - override fun formattedErrorMessage(field: FieldContext): String { - return "Value of `${field.targetDeclaration()}` must not be longer than `${optionValue()}`." - } - - override fun validate(containingMessage: MessageValue): ImmutableList { - val value = containingMessage.valueOf(field()) - val maxLength = optionValue() - val context = value.context() - val violation = constraintViolation { - fieldPath = context.fieldPath() - typeName = containingMessage.declaration().name().value() - message = errorMessage(context) - } - return value.nonDefault() - .filter { it.toString().length > maxLength } - .map { violation } - .collect(ImmutableList.toImmutableList()) - } -} - -@Immutable -private class MaxLength : FieldValidatingOption(TestFieldOptionProto.maxLength) { - override fun constraintFor(field: FieldContext): Constraint { - return MaxLengthConstraint(optionValue(field), field) - } -} diff --git a/tests/runtime/src/test/kotlin/io/spine/validation/option/RangeConstraintSpec.kt b/tests/runtime/src/test/kotlin/io/spine/validation/option/RangeConstraintSpec.kt deleted file mode 100644 index be9adb3473..0000000000 --- a/tests/runtime/src/test/kotlin/io/spine/validation/option/RangeConstraintSpec.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.validation.option - -import com.google.common.collect.BoundType -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Sets -import io.kotest.matchers.shouldBe -import io.spine.code.proto.FieldDeclaration -import io.spine.option.rangeOption -import io.spine.test.type.Url -import io.spine.validation.option.RangeConstraint.rangeFromOption -import java.util.stream.Stream -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource - -@DisplayName("Range constraint should") -internal class RangeConstraintSpec { - - @ParameterizedTest - @MethodSource("validRanges") - fun `be able to parse valid range strings`(range: String, expected: BoundType) { - val option = toOption(range) - val result = rangeFromOption(option, fieldDecl()) - result.upperBoundType() shouldBe expected - } - - @ParameterizedTest - @MethodSource("badRanges") - fun `throw on incorrectly defined ranges`(badRange: String) { - // Exceptions would be `NumberFormatException` or `IllegalStateException`. - val option = toOption(badRange) - assertThrows { - rangeFromOption(option, fieldDecl()) - } - } - - @ParameterizedTest - @MethodSource("emptyRanges") - fun `throw on empty ranges`(emptyRange: String) { - val option = toOption(emptyRange) - assertThrows { - rangeFromOption(option, fieldDecl()) - } - } - - @Suppress("unused") /* Methods used via `@MethodSource`. */ - companion object { - - @JvmStatic - fun validRanges(): Stream = Stream.of( - Arguments.of("[1..2]", BoundType.CLOSED), - Arguments.of("(1..2)", BoundType.OPEN), - Arguments.of("[1..2)", BoundType.OPEN), - Arguments.of("(1..2]", BoundType.CLOSED) - ) - - @JvmStatic - fun badRanges(): ImmutableSet = argumentsFrom( - "{3..5]", - "[3..5}", - "{3..5}", - "(3..5", - "3..5)", - "((3..5]", - "(3..5]]", - "(3,5..5)", - "(3 5..5", - "[3..5 5]", - "[3..5,5]", - "[3;5]", - "[3...5]" - ) - - @JvmStatic - fun emptyRanges(): Set { - val right = 0 - val left = right + 1 - val leftGreaterThanRight = - rangeCombinationsFor( - left, - right, - ImmutableSet.of('[', '('), - ImmutableSet.of(']', ')') - ) - val closedWithSameNumber = Arguments.arguments("(0..0)") - return Sets.union(leftGreaterThanRight, ImmutableSet.of(closedWithSameNumber)) - } - } -} - -private fun rangeCombinationsFor( - left: Number, - right: Number, - leftBoundary: ImmutableSet, - rightBoundary: ImmutableSet -): ImmutableSet { - val lefts = leftBoundary.stream() - .map { boundary: Char -> "$boundary$left.." } - .collect(ImmutableSet.toImmutableSet()) - val rights = rightBoundary.stream() - .map { boundary: Char -> right.toString() + boundary } - .collect(ImmutableSet.toImmutableSet()) - val result = Sets.cartesianProduct(lefts, rights).stream() - .flatMap { product: List -> - Stream.of(product[0] + product[1]) - } - .map { arguments: String -> Arguments.of(arguments) } - .collect(ImmutableSet.toImmutableSet()) - return result -} - -private fun argumentsFrom(vararg elements: Any): ImmutableSet { - val builder = ImmutableSet.builder() - for (element in elements) { - builder.add(Arguments.of(element)) - } - return builder.build() -} - -private fun fieldDecl(): FieldDeclaration = FieldDeclaration( - Url.getDescriptor().fields[0] -) - -private fun toOption(range: String) = rangeOption { - value = range -} diff --git a/tests/validating/src/test/kotlin/io/spine/test/options/CustomOptionsITest.kt b/tests/validating/src/test/kotlin/io/spine/test/options/CustomOptionsITest.kt deleted file mode 100644 index a54d6e0e02..0000000000 --- a/tests/validating/src/test/kotlin/io/spine/test/options/CustomOptionsITest.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.test.options - -import com.google.common.truth.extensions.proto.ProtoTruth.assertThat -import com.google.protobuf.ByteString -import io.kotest.matchers.optional.shouldBeEmpty -import io.kotest.matchers.optional.shouldBePresent -import io.spine.base.fieldPath -import io.spine.test.tools.validate.ByteMatrix -import io.spine.tools.validate.rule.BytesAllRequiredFactory -import io.spine.validation.constraintViolation -import io.spine.validation.option.ValidatingOptionsLoader -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -@DisplayName("Custom validation options should") -internal class CustomOptionsITest { - - @Test - fun `be discovered`() { - val implementations = ValidatingOptionsLoader.INSTANCE.implementations() - val classes = implementations.map { it::class.java } - classes.contains(BytesAllRequiredFactory::class.java) - } - - @Test - @Disabled("https://github.com/SpineEventEngine/mc-java/issues/119") - fun `be applied to validated messages`() { - val matrix = ByteMatrix.newBuilder() - .addValue(ByteString.copyFrom(byteArrayOf(42))) - .addValue(ByteString.EMPTY) - .buildPartial() - - val error = matrix.validate() - error.shouldBePresent() - - val violations = error.get().constraintViolationList - val expected = constraintViolation { fieldPath = fieldPath { fieldName.add("value") } } - assertThat(violations) - .comparingExpectedFieldsOnly() - .containsExactly(expected) - } - - @Test - fun `be applied to valid messages and pass`() { - val matrix = ByteMatrix.newBuilder() - .addValue(ByteString.copyFrom(byteArrayOf(42))) - .build() - matrix.validate().shouldBeEmpty() - } -} diff --git a/tests/validating/src/test/kotlin/io/spine/test/options/ValidatingOptionFactoryITest.kt b/tests/validating/src/test/kotlin/io/spine/test/options/CustomOptionsLoadingITest.kt similarity index 91% rename from tests/validating/src/test/kotlin/io/spine/test/options/ValidatingOptionFactoryITest.kt rename to tests/validating/src/test/kotlin/io/spine/test/options/CustomOptionsLoadingITest.kt index 6243155814..160eb35be6 100644 --- a/tests/validating/src/test/kotlin/io/spine/test/options/ValidatingOptionFactoryITest.kt +++ b/tests/validating/src/test/kotlin/io/spine/test/options/CustomOptionsLoadingITest.kt @@ -34,20 +34,20 @@ import io.spine.option.OptionsProvider import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -@DisplayName("`ValidatingOptionFactory` should") -internal class ValidatingOptionFactoryITest { +@DisplayName("Options should be registered with `ExtensionRegistry` so that it") +internal class CustomOptionsLoadingITest { private val registry by lazy { OptionsProvider.registryWithAllOptions() } @Test - fun `contain standard options`() { + fun `contains standard options`() { assertContains(OptionsProto.required) } @Test - fun `contain custom options`() { + fun `contains custom options`() { assertContains(BytesDirectionOptionProto.direction) } diff --git a/tests/validating/src/testFixtures/java/io/spine/test/options/BytesOptions.java b/tests/validating/src/testFixtures/java/io/spine/test/options/BytesOptions.java deleted file mode 100644 index d805907fea..0000000000 --- a/tests/validating/src/testFixtures/java/io/spine/test/options/BytesOptions.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.test.options; - -import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import io.spine.validation.option.FieldValidatingOption; -import io.spine.validation.option.ValidatingOptionFactory; - -import java.util.Set; - -@AutoService(ValidatingOptionFactory.class) -@Immutable -public final class BytesOptions implements ValidatingOptionFactory { - - @Override - public Set> forByteString() { - return ImmutableSet.of(new Direction()); - } -} diff --git a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionsProvider.java b/tests/validating/src/testFixtures/java/io/spine/test/options/BytesOptionsProvider.java similarity index 81% rename from jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionsProvider.java rename to tests/validating/src/testFixtures/java/io/spine/test/options/BytesOptionsProvider.java index e46ef23553..76c2ead74c 100644 --- a/jvm-runtime/src/main/java/io/spine/validation/option/ValidatingOptionsProvider.java +++ b/tests/validating/src/testFixtures/java/io/spine/test/options/BytesOptionsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2025, TeamDev. All rights reserved. + * Copyright 2026, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,21 +24,20 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.spine.validation.option; +package io.spine.test.options; import com.google.auto.service.AutoService; import com.google.protobuf.ExtensionRegistry; import io.spine.option.OptionsProvider; -@SuppressWarnings("unused") // loaded by AutoService +/** + * Registers extensions for {@link BytesDirectionOptionProto}. + */ @AutoService(OptionsProvider.class) -public final class ValidatingOptionsProvider implements OptionsProvider { - - public ValidatingOptionsProvider() { - } +public class BytesOptionsProvider implements OptionsProvider { @Override public void registerIn(ExtensionRegistry registry) { - ValidatingOptionsLoader.registerCustomOptions(registry); + BytesDirectionOptionProto.registerAllExtensions(registry); } } diff --git a/tests/validating/src/testFixtures/java/io/spine/test/options/Direction.java b/tests/validating/src/testFixtures/java/io/spine/test/options/Direction.java deleted file mode 100644 index 14c22ab2fe..0000000000 --- a/tests/validating/src/testFixtures/java/io/spine/test/options/Direction.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.test.options; - -import io.spine.code.proto.FieldContext; -import io.spine.validation.Constraint; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.option.FieldConstraint; -import io.spine.validation.option.FieldValidatingOption; - -/** - * A custom validation option for {@code bytes}. - * - *

This option is used for testing the custom options loading. The constraint produced by this - * option cannot be violated. - */ -public final class Direction extends FieldValidatingOption { - - Direction() { - super(BytesDirectionOptionProto.direction); - } - - @Override - public Constraint constraintFor(FieldContext field) { - var declaration = field.targetDeclaration(); - var optionValue = optionValue(field); - return new FieldConstraint<>(optionValue, declaration) { - @Override - public String formattedErrorMessage(FieldContext field) { - return ""; - } - - @Override - public void accept(ConstraintTranslator visitor) { - // NoOp. - } - }; - } -} diff --git a/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/AllRequired.java b/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/AllRequired.java deleted file mode 100644 index defb65d1e1..0000000000 --- a/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/AllRequired.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.tools.validate.rule; - -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.option.OptionsProto; -import io.spine.validation.Constraint; -import io.spine.validation.option.FieldValidatingOption; - -/** - * A field validating option which creates {@link AllRequiredConstraint}s. - * - *

The option reuses the {@link OptionsProto#required} extension. - */ -@Immutable -public final class AllRequired extends FieldValidatingOption { - - AllRequired() { - super(OptionsProto.required); - } - - @Override - public boolean shouldValidate(FieldContext context) { - return context.targetDeclaration().isCollection() && super.shouldValidate(context); - } - - @Override - public Constraint constraintFor(FieldContext field) { - return new AllRequiredConstraint(optionValue(field), field.targetDeclaration()); - } -} diff --git a/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/AllRequiredConstraint.java b/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/AllRequiredConstraint.java deleted file mode 100644 index 1a1807ddcb..0000000000 --- a/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/AllRequiredConstraint.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.tools.validate.rule; - -import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.Immutable; -import io.spine.code.proto.FieldContext; -import io.spine.code.proto.FieldDeclaration; -import io.spine.validation.ConstraintTranslator; -import io.spine.validation.ConstraintViolation; -import io.spine.validation.CustomConstraint; -import io.spine.validation.MessageValue; -import io.spine.validation.option.FieldConstraint; - -import static java.lang.String.format; - -/** - * A field constraint for collection fields which makes all the field elements to be non-default. - */ -@Immutable -public final class AllRequiredConstraint - extends FieldConstraint - implements CustomConstraint { - - AllRequiredConstraint(Boolean optionValue, FieldDeclaration field) { - super(optionValue, field); - } - - @Override - public String formattedErrorMessage(FieldContext field) { - return format("Field `%s` cannot contain default values.", field.targetDeclaration()); - } - - @Override - public ImmutableList validate(MessageValue containingMessage) { - var value = containingMessage.valueOf(field()); - var count = value.values().count(); - var countOfNonDefault = value.nonDefault().count(); - var context = value.context(); - return count > countOfNonDefault - ? ImmutableList.of( - ConstraintViolation.newBuilder() - .setMessage(errorMessage(context)) - .setTypeName(containingMessage.declaration().name().value()) - .setFieldPath(context.fieldPath()) - .build()) - : ImmutableList.of(); - } - - @Override - public void accept(ConstraintTranslator visitor) { - visitor.visitCustom(this); - } -} diff --git a/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/BytesAllRequiredFactory.java b/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/BytesAllRequiredFactory.java deleted file mode 100644 index 9eb489874e..0000000000 --- a/tests/validating/src/testFixtures/java/io/spine/tools/validate/rule/BytesAllRequiredFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.tools.validate.rule; - -import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import io.spine.validation.option.FieldValidatingOption; -import io.spine.validation.option.ValidatingOptionFactory; - -import java.util.Set; - -/** - * A {@link ValidatingOptionFactory} which adds the {@link AllRequired} option - * for {@code bytes} fields. - */ -@Immutable -@AutoService(ValidatingOptionFactory.class) -public final class BytesAllRequiredFactory implements ValidatingOptionFactory { - - private static final ImmutableSet> OPTIONS = - ImmutableSet.of(new AllRequired()); - - @Override - public Set> forByteString() { - return OPTIONS; - } -} diff --git a/tests/validating/src/testFixtures/proto/spine/test/options/bytes_direction_option.proto b/tests/validating/src/testFixtures/proto/spine/test/options/bytes_direction_option.proto index adf9c6abb7..fe002cdb79 100644 --- a/tests/validating/src/testFixtures/proto/spine/test/options/bytes_direction_option.proto +++ b/tests/validating/src/testFixtures/proto/spine/test/options/bytes_direction_option.proto @@ -46,5 +46,5 @@ enum BytesDirection { extend google.protobuf.FieldOptions { - BytesDirection direction = 73827; + BytesDirection direction = 78888; } diff --git a/version.gradle.kts b/version.gradle.kts index 8d4217a3c5..53c8ff9dd9 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -29,4 +29,4 @@ * * For Spine-based dependencies please see [io.spine.dependency.local.Spine]. */ -val validationVersion by extra("2.0.0-SNAPSHOT.401") +val validationVersion by extra("2.0.0-SNAPSHOT.402")