From d1a320098025d17f0a9b5e238a7589ef9762635e Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 20 Jan 2026 19:52:41 +0000 Subject: [PATCH 01/45] Reduce using "please" in docs --- .../io/spine/tools/validation/java/JavaValidationPlugin.kt | 6 +++--- .../tools/validation/java/setonce/SetOnceBytesField.kt | 3 +-- .../spine/tools/validation/java/setonce/SetOnceEnumField.kt | 3 +-- .../src/main/kotlin/io/spine/validation/MessageValidator.kt | 2 +- .../kotlin/io/spine/validation/test/CurrencyOptionITest.kt | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/java/src/main/kotlin/io/spine/tools/validation/java/JavaValidationPlugin.kt b/java/src/main/kotlin/io/spine/tools/validation/java/JavaValidationPlugin.kt index fdd9c17afb..4187a6ce1c 100644 --- a/java/src/main/kotlin/io/spine/tools/validation/java/JavaValidationPlugin.kt +++ b/java/src/main/kotlin/io/spine/tools/validation/java/JavaValidationPlugin.kt @@ -71,9 +71,9 @@ private val customOptions: List by lazy { * Dynamically discovered instances of custom * [MessageValidator][io.spine.validation.MessageValidator]s. * - * Please note that the KSP module is responsible for the actual discovering - * of the message validators. The discovered validators are written to a text file - * in the KSP task output. This property loads the validators from that file. + * Note that the KSP module is responsible for the actual discovering of the message validators. + * The discovered validators are written to a text file in the KSP task output. + * This property loads the validators from that file. */ private val customValidators: Map by lazy { val workingDir = System.getProperty("user.dir") diff --git a/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceBytesField.kt b/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceBytesField.kt index 668383f1b6..b79a8940af 100644 --- a/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceBytesField.kt +++ b/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceBytesField.kt @@ -38,8 +38,7 @@ import io.spine.tools.psi.java.method /** * Renders Java code to support `(set_once)` option for the given byte array [field]. * - * Please note, in the generated Java code, Protobuf uses [ByteString] to represent - * an array of bytes. + * Note that Protobuf uses [ByteString] to represent an array of bytes in the generated code. * * @param field The byte array field that declared the option. * @param typeSystem The type system to resolve types. diff --git a/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceEnumField.kt b/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceEnumField.kt index 43361debc2..456deccdc6 100644 --- a/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceEnumField.kt +++ b/java/src/main/kotlin/io/spine/tools/validation/java/setonce/SetOnceEnumField.kt @@ -39,8 +39,7 @@ import io.spine.tools.psi.java.method /** * Renders Java code to support `(set_once)` option for the given enum [field]. * - * Please note, in the generated Java code, Protobuf uses an ordinal number - * to represent the currently set enum constant. + * Note that code generated by Protobuf uses an ordinal number to represent the currently set enum constant. * * @param field The enum field that declared the option. * @param typeSystem The type system to resolve types. 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 df147b202a..2b534fe50d 100644 --- a/jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt +++ b/jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt @@ -82,7 +82,7 @@ import io.spine.annotation.SPI * } * ``` * - * Please note that standalone instances of [M] and fields of [M] type that occur in + * Note that standalone instances of [M] and fields of [M] type that occur in * other external messages **will not be validated**. * * Consider the following example: diff --git a/tests/consumer/src/test/kotlin/io/spine/validation/test/CurrencyOptionITest.kt b/tests/consumer/src/test/kotlin/io/spine/validation/test/CurrencyOptionITest.kt index 3d6d8250c7..6d9a5a57a2 100644 --- a/tests/consumer/src/test/kotlin/io/spine/validation/test/CurrencyOptionITest.kt +++ b/tests/consumer/src/test/kotlin/io/spine/validation/test/CurrencyOptionITest.kt @@ -35,7 +35,7 @@ import org.junit.jupiter.api.Test * by [CurrencyOption] in the `:java-tests:extensions` module. * * The `extensions` module declares the `(currency)` message option, which is used by - * money data types in this module. Please see `main/proto/test/money.proto` for details. + * money data types in this module. See `main/proto/test/money.proto` for details. * * This test verifies that custom validation code works as expected. */ From 11c6121e49b035b19032818d1094e913c56c2904 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 20 Jan 2026 19:54:04 +0000 Subject: [PATCH 02/45] Improve text layout --- docs/01-getting-started/index.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/01-getting-started/index.md b/docs/01-getting-started/index.md index 9ed40dde89..57b8967ef0 100644 --- a/docs/01-getting-started/index.md +++ b/docs/01-getting-started/index.md @@ -1,6 +1,7 @@ # Getting Started -This section helps you set up Spine Validation, define your first validated Protobuf model, and see validation in action in Java and Kotlin. +This section helps you set up Spine Validation, define your first validated Protobuf model, +and see validation in action in Java and Kotlin. If you are new to the library, read the short overview first: - Introduction → [Overview](../00-intro/index.md) @@ -22,7 +23,9 @@ If you are new to the library, read the short overview first: - Protobuf compiler (`protoc`) - Optional: Kotlin 2.2.20+ for the Kotlin Protobuf DSL -If your project already generates Java/Kotlin sources from `.proto` files, you’re 90% there. Spine Validation integrates into the build to generate and inject validation logic into the code produced by `protoc`. +If your project already generates Java/Kotlin sources from `.proto` files, you’re 90% there. +Spine Validation integrates into the build to generate and inject validation logic into +the code produced by `protoc`. --- From b16788ace57f5b2ded9f9de608ba59cacd7761ca Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 20 Jan 2026 19:54:28 +0000 Subject: [PATCH 03/45] Bump version -> `2.0.0-SNAPSHOT.394` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index dc55d8b2ab..bae585a6c9 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.393") +val validationVersion by extra("2.0.0-SNAPSHOT.394") From 9c0a34b3fb2f5018b17b2462b3409f910bda81c2 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 20 Jan 2026 19:55:43 +0000 Subject: [PATCH 04/45] Bump Validation and CoreJvm Compiler --- .../main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt | 4 ++-- .../src/main/kotlin/io/spine/dependency/local/Validation.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt index 2737c39cf0..b48d90e9e2 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt @@ -46,12 +46,12 @@ object CoreJvmCompiler { /** * The version used to in the build classpath. */ - const val dogfoodingVersion = "2.0.0-SNAPSHOT.050" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.051" /** * The version to be used for integration tests. */ - const val version = "2.0.0-SNAPSHOT.050" + const val version = "2.0.0-SNAPSHOT.051" /** * The ID of the Gradle plugin. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt index 271ccee000..1cad8b7403 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt @@ -36,7 +36,7 @@ object Validation { /** * The version of the Validation library artifacts. */ - const val version = "2.0.0-SNAPSHOT.392" + const val version = "2.0.0-SNAPSHOT.393" /** * The last version of Validation compatible with ProtoData. From 6379b9f3ed638d697f923629ad28d35ef9ca1cfc Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 20 Jan 2026 19:56:55 +0000 Subject: [PATCH 05/45] Update dependency reports --- dependencies.md | 60 ++++++++++++++++++++++++------------------------- pom.xml | 10 ++++----- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/dependencies.md b/dependencies.md index 9faee3fde2..6ec36ba3f3 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.393` +# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.394` ## 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 **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -1731,14 +1731,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:51 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-gradle-plugin:2.0.0-SNAPSHOT.393` +# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -2807,14 +2807,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -3901,14 +3901,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.auto.service. **Name** : auto-service-annotations. **Version** : 1.1.1. @@ -3971,14 +3971,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:51 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.393` +# Dependencies of `io.spine:validation-jvm-runtime:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4811,14 +4811,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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-ksp:2.0.0-SNAPSHOT.393` +# Dependencies of `io.spine.tools:validation-ksp:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.auto.service. **Name** : auto-service-annotations. **Version** : 1.1.1. @@ -5747,14 +5747,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -6345,14 +6345,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:51 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.393` +# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -6863,14 +6863,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -7554,14 +7554,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8183,14 +8183,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8855,14 +8855,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -9613,14 +9613,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:52 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.393` +# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -9890,14 +9890,14 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:51 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.393` +# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.394` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -10248,6 +10248,6 @@ This report was generated on **Tue Jan 20 16:21:00 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 16:21:00 WET 2026** using +This report was generated on **Tue Jan 20 19:55:51 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/pom.xml b/pom.xml index 8b9da9273d..5f619d86ff 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.393 +2.0.0-SNAPSHOT.394 2015 @@ -62,7 +62,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-validation-jvm-runtime - 2.0.0-SNAPSHOT.392 + 2.0.0-SNAPSHOT.393 compile @@ -281,12 +281,12 @@ all modules and does not describe the project structure per-subproject. io.spine.tools core-jvm-gradle-plugins - 2.0.0-SNAPSHOT.050 + 2.0.0-SNAPSHOT.051 io.spine.tools core-jvm-routing - 2.0.0-SNAPSHOT.050 + 2.0.0-SNAPSHOT.051 io.spine.tools @@ -301,7 +301,7 @@ all modules and does not describe the project structure per-subproject. io.spine.tools validation-java-bundle - 2.0.0-SNAPSHOT.392 + 2.0.0-SNAPSHOT.393 net.sourceforge.pmd From 1397636d83a1f31092a9bc26d39f92572815eb93 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 20 Jan 2026 19:58:25 +0000 Subject: [PATCH 06/45] Update config ref. --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index dcd2cee3af..17e0dbb819 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit dcd2cee3af82ce8e4de407801636637f7cdcef3c +Subproject commit 17e0dbb819839d9b65b711efb085b38bcbb5eae9 From 6e2fe2b32c3418adbd1adbed89e50a60d9d37211 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 21 Jan 2026 18:28:56 +0000 Subject: [PATCH 07/45] Remove "Migration Guide" section --- docs/ToC.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/ToC.md b/docs/ToC.md index 87515e2df0..4129c539cd 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -84,13 +84,8 @@ - [Cross-field Logic](07-recipes/cross-field-logic.md) - [API Validation](07-recipes/api-validation.md) -## 8. Migration Guide -- [Overview](08-migration/index.md) -- [Migrating from `spine.base` Validation](08-migration/from-spine-base.md) -- [Version Changes](08-migration/version-changes.md) - -## 9. Reference -- [Reference Overview](09-reference/index.md) -- [List of Validation Options](09-reference/options.md) -- [Java/Kotlin API Index](09-reference/api.md) -- [Glossary](09-reference/glossary.md) +## 8. Reference +- [Reference Overview](08-reference/index.md) +- [List of Validation Options](08-reference/options.md) +- [Java/Kotlin API Index](08-reference/api.md) +- [Glossary](08-reference/glossary.md) From 479c28c4b5a500f56344bb2712f46115b249e1d5 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 21 Jan 2026 18:41:28 +0000 Subject: [PATCH 08/45] Improve text layout --- docs/00-intro/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index 542f713dac..85b897e3fc 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -3,8 +3,7 @@ Spine Validation is a Protobuf-centric validation framework that generates type-safe validation code directly from your `.proto` definitions. It allows you to describe constraints on fields, messages, and collections using -declarative options and then automatically enforces these constraints at -runtime. +declarative options and then automatically enforces these constraints at runtime. The library is part of the Spine toolchain but can also be used independently in any Java/Kotlin backend that models data using Protocol Buffers. From 4230509369fb9cba5351443149967ddefe9cf73b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 21 Jan 2026 19:56:20 +0000 Subject: [PATCH 09/45] Remove unnecessary decorations --- docs/00-intro/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index 85b897e3fc..8e47de76d4 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -12,7 +12,7 @@ in any Java/Kotlin backend that models data using Protocol Buffers. ## Key Capabilities -### 🔹 Declarative constraints in `.proto` +### Declarative constraints in `.proto` Validation rules are expressed as Protobuf options such as: - `required` @@ -24,8 +24,8 @@ Validation rules are expressed as Protobuf options such as: This keeps validation close to the data model and ensures it evolves together with it. -### 🔹 Generated validators -The Spine compiler plugin processes your Protobuf model and generates: +### Generated validators +The Spine Compiler plugin processes your Protobuf model and generates: - validation code for messages and builders, - runtime checks, @@ -33,7 +33,7 @@ The Spine compiler plugin processes your Protobuf model and generates: No manual validators, reflection, or annotations are required. -### 🔹 Runtime validation API +### Runtime validation API Every generated message can be validated at runtime via: - `validate()`, @@ -43,7 +43,7 @@ Every generated message can be validated at runtime via: Errors are represented as structured diagnostics suitable for API responses, logs, or domain exception flows. -### 🔹 Rich domain-oriented constraints +### Rich domain-oriented constraints Beyond simple “required/min/max”, the library includes: - collection rules (`distinct`, `non_empty`), @@ -51,7 +51,7 @@ Beyond simple “required/min/max”, the library includes: - advanced string formats (using regex), - temporal constraints (`PAST`, `FUTURE`). -### 🔹 Extensible architecture +### Extensible architecture Teams can define custom validation options by: - declaring new `.proto` options, From 77bbd9c2c1f79de58284dde04b6f8646d39fc64b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 21 Jan 2026 20:35:43 +0000 Subject: [PATCH 10/45] Remove hallucinated section links --- docs/ToC.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/ToC.md b/docs/ToC.md index 4129c539cd..b6deb3d2c3 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -38,9 +38,6 @@ ### 3.3. String Constraints - [Overview](03-built-in-options/strings/index.md) - [Advanced Patterns](03-built-in-options/strings/pattern-advanced.md) -- [email](03-built-in-options/strings/email.md) -- [hostname](03-built-in-options/strings/hostname.md) -- [uri](03-built-in-options/strings/uri.md) ### 3.4. Numeric Constraints - [Overview](03-built-in-options/numbers/index.md) From 4bf36f081d08e48a84242964e4a040b7637055b8 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 21 Jan 2026 20:35:55 +0000 Subject: [PATCH 11/45] Remove redundant horizontal lines --- docs/00-intro/target-audience.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/00-intro/target-audience.md b/docs/00-intro/target-audience.md index ad707be8da..8d08edd16c 100644 --- a/docs/00-intro/target-audience.md +++ b/docs/00-intro/target-audience.md @@ -6,8 +6,6 @@ constraints on that data. The library serves several overlapping groups: ---- - ## 1. Application Developers Developers building backend services in **Java** or **Kotlin** who need @@ -27,8 +25,6 @@ These developers benefit from: This group typically works directly with generated message builders and calls `validate()` at appropriate points in their workflow. ---- - ## 2. Teams Using the Spine Event Engine Spine Validation is the standard validation mechanism used inside the @@ -46,8 +42,6 @@ For Spine users, this library provides: - rich temporal and domain-focused constraints, - cross-field and message-level validations. ---- - ## 3. Framework and Platform Integrators Engineers who build frameworks, platforms, or infrastructure around Protobuf @@ -69,8 +63,7 @@ For these integrators, Spine Validation provides: ## 4. Library Authors and Tooling Developers -Those who extend `protoc`, build code-gen pipelines, or maintain shared data -models across a large organization can use Spine Validation to: +Those who maintain shared data models across a large organization can use Spine Validation to: - standardize validation behavior for all services, - define custom domain-specific validation options, From 09a9d477b3d25170b5eae566ccdfddbf66b0346a Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 17:03:59 +0000 Subject: [PATCH 12/45] Convert titles to sentence case --- docs/00-intro/index.md | 10 +--- docs/00-intro/philosophy.md | 17 +++--- docs/00-intro/target-audience.md | 16 ++--- docs/01-getting-started/index.md | 5 +- docs/ToC.md | 88 ++++++++++++++-------------- docs/developers-guide/key-modules.md | 2 +- 6 files changed, 62 insertions(+), 76 deletions(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index 8e47de76d4..ce1654c0cc 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -8,9 +8,8 @@ declarative options and then automatically enforces these constraints at runtime The library is part of the Spine toolchain but can also be used independently in any Java/Kotlin backend that models data using Protocol Buffers. ---- -## Key Capabilities +## Key capabilities ### Declarative constraints in `.proto` Validation rules are expressed as Protobuf options such as: @@ -60,9 +59,8 @@ Teams can define custom validation options by: This allows entire organizations to standardize domain validation rules. ---- -## When to Use Spine Validation +## When to use Spine Validation Use Spine Validation if: @@ -79,7 +77,6 @@ It is especially useful in: - systems with rich domain models, - multi-service environments where shared `.proto` models are common. ---- ## Relationship to the Spine Event Engine @@ -96,9 +93,8 @@ framework’s error reporting mechanisms. However, the library is fully standalone and can be used without the rest of the Spine stack. ---- -## What’s Next +## What's next - [Target Audience](target-audience.md) - [Philosophy](philosophy.md) diff --git a/docs/00-intro/philosophy.md b/docs/00-intro/philosophy.md index 31c5384de4..d4645284c5 100644 --- a/docs/00-intro/philosophy.md +++ b/docs/00-intro/philosophy.md @@ -5,9 +5,8 @@ model, generate, and enforce constraints on their data. This section outlines those principles and explains why the library is designed the way it is. ---- -## 1. Validation Belongs to the Model +## 1. Validation belongs to the model In many systems, validation rules drift between: @@ -32,9 +31,8 @@ By placing constraints in `.proto` definitions, validation becomes: This prevents the typical “broken windows” of duplicated or inconsistent validation logic. ---- -## 2. Generated Code Over Reflection +## 2. Generated code over reflection Reflection-based frameworks (Bean Validation, etc.) are convenient but come with: @@ -54,9 +52,8 @@ Benefits: This approach scales better for complex domain models or high-throughput services. ---- -## 3. Declarative, Not Imperative +## 3. Declarative, not imperative Validation is expressed declaratively through Protobuf options: @@ -72,7 +69,7 @@ Declarative rules: Developers describe intent, and the library handles implementation. -## 4. Predictability and Consistency +## 4. Predictability and consistency Validation should behave the same regardless of: @@ -91,7 +88,7 @@ Given the same inputs, you always get the same: Consistency is especially important in distributed systems and domain-driven design contexts. -## 5. Domain-Oriented Constraints +## 5. Domain-oriented constraints Instead of focusing only on primitive checks (min/max, required), the library embraces **domain semantics**, such as: @@ -102,7 +99,7 @@ embraces **domain semantics**, such as: * constraints on identity fields, * collection semantics. -## 6. Extensibility as a First-Class Feature +## 6. Extensibility as a first-class feature Spine Validation is extensible via: @@ -113,7 +110,7 @@ Spine Validation is extensible via: This makes the library a foundation for building consistent validation standards across teams and services. -## 7. No UI or Presentation Layer Concerns +## 7. No UI or presentation layer concerns Spine Validation intentionally does not attempt to validate UI forms, front-end models, or JSON schemas. diff --git a/docs/00-intro/target-audience.md b/docs/00-intro/target-audience.md index 8d08edd16c..fedfd52abe 100644 --- a/docs/00-intro/target-audience.md +++ b/docs/00-intro/target-audience.md @@ -1,4 +1,4 @@ -# Target Audience +# Target audience Spine Validation is designed for developers who model data and APIs using **Protocol Buffers** and need a structured, type-safe way to express and enforce @@ -6,7 +6,7 @@ constraints on that data. The library serves several overlapping groups: -## 1. Application Developers +## 1. Application developers Developers building backend services in **Java** or **Kotlin** who need validation for: @@ -25,7 +25,7 @@ These developers benefit from: This group typically works directly with generated message builders and calls `validate()` at appropriate points in their workflow. -## 2. Teams Using the Spine Event Engine +## 2. Teams using the Spine Event Engine Spine Validation is the standard validation mechanism used inside the **Spine Event Engine**. @@ -42,7 +42,7 @@ For Spine users, this library provides: - rich temporal and domain-focused constraints, - cross-field and message-level validations. -## 3. Framework and Platform Integrators +## 3. Framework and platform integrators Engineers who build frameworks, platforms, or infrastructure around Protobuf often need to enforce rules across: @@ -59,9 +59,7 @@ For these integrators, Spine Validation provides: - hooks for custom validators, - extension points via custom validation options. ---- - -## 4. Library Authors and Tooling Developers +## 4. Library authors and tooling developers Those who maintain shared data models across a large organization can use Spine Validation to: @@ -75,9 +73,8 @@ This group interacts with advanced parts of the library such as: - option policies, - custom code generation units. ---- -## 5. Not the Target Audience (Explicitly) +## 5. Not the target audience (explicitly) The library is **not** designed for: @@ -89,7 +86,6 @@ The library is **not** designed for: Spine Validation is intentionally focused on the **Protobuf → runtime code** pipeline and the domain layer of applications. ---- ## Summary diff --git a/docs/01-getting-started/index.md b/docs/01-getting-started/index.md index 57b8967ef0..a1d5708927 100644 --- a/docs/01-getting-started/index.md +++ b/docs/01-getting-started/index.md @@ -1,4 +1,4 @@ -# Getting Started +# Getting started This section helps you set up Spine Validation, define your first validated Protobuf model, and see validation in action in Java and Kotlin. @@ -8,7 +8,6 @@ If you are new to the library, read the short overview first: - Who this is for → [Target Audience](../00-intro/target-audience.md) - Design principles → [Philosophy](../00-intro/philosophy.md) ---- ## What you’ll learn @@ -27,7 +26,6 @@ If your project already generates Java/Kotlin sources from `.proto` files, you Spine Validation integrates into the build to generate and inject validation logic into the code produced by `protoc`. ---- ## Quick path @@ -45,7 +43,6 @@ the code produced by `protoc`. 4) Use the generated API - Validate on builder `build()` or call `validate()` explicitly. See [Your First Validated Model](first-model.md). ---- ## What’s next diff --git a/docs/ToC.md b/docs/ToC.md index b6deb3d2c3..3645eb7b9b 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -1,26 +1,26 @@ -# Spine Validation — Table of Contents +# Spine Validation — Table of contents ## 0. Introduction - [Overview](00-intro/index.md) -- [Target Audience](00-intro/target-audience.md) +- [Target audience](00-intro/target-audience.md) - [Philosophy](00-intro/philosophy.md) -## 1. Getting Started -- [Getting Started](01-getting-started/index.md) +## 1. Getting started +- [Getting started](01-getting-started/index.md) - [Installation](01-getting-started/installation.md) -- [Your First Validated Model](01-getting-started/first-model.md) -- [Validation Workflow](01-getting-started/workflow.md) +- [Your first validated model](01-getting-started/first-model.md) +- [Validation workflow](01-getting-started/workflow.md) ## 2. Concepts -- [Concepts Overview](02-concepts/index.md) -- [Validation Options Overview](02-concepts/options-overview.md) -- [The Validation Engine](02-concepts/validation-engine.md) -- [Integration with Protobuf & Compiler](02-concepts/protobuf-integration.md) +- [Concepts overview](02-concepts/index.md) +- [Validation options overview](02-concepts/options-overview.md) +- [The validation engine](02-concepts/validation-engine.md) +- [Integration with Protobuf & Spine Compiler](02-concepts/protobuf-integration.md) -## 3. Built-in Validation Options -- [Built-in Options Overview](03-built-in-options/index.md) +## 3. Built-in validation options +- [Built-in options overview](03-built-in-options/index.md) -### 3.1. Field Constraints +### 3.1. Field constraints - [Overview](03-built-in-options/fields/index.md) - [required](03-built-in-options/fields/required.md) - [pattern](03-built-in-options/fields/pattern.md) @@ -29,60 +29,60 @@ - [length/size](03-built-in-options/fields/length-size.md) - [unique](03-built-in-options/fields/unique.md) -### 3.2. Collection Constraints +### 3.2. Collection constraints - [Overview](03-built-in-options/collections/index.md) - [non_empty](03-built-in-options/collections/non-empty.md) - [distinct](03-built-in-options/collections/distinct.md) -- [collection size](03-built-in-options/collections/collection-size.md) +- [Collection size](03-built-in-options/collections/collection-size.md) -### 3.3. String Constraints +### 3.3. String constraints - [Overview](03-built-in-options/strings/index.md) -- [Advanced Patterns](03-built-in-options/strings/pattern-advanced.md) +- [Advanced patterns](03-built-in-options/strings/pattern-advanced.md) -### 3.4. Numeric Constraints +### 3.4. Numeric constraints - [Overview](03-built-in-options/numbers/index.md) -- [Numeric Bounds](03-built-in-options/numbers/numeric-bounds.md) +- [Numeric bounds](03-built-in-options/numbers/numeric-bounds.md) -### 3.5. Temporal Constraints +### 3.5. Temporal constraints - [Overview](03-built-in-options/temporal/index.md) - [when](03-built-in-options/temporal/when.md) -- [Timestamp & Duration](03-built-in-options/temporal/timestamp-duration.md) +- [Timestamp & duration](03-built-in-options/temporal/timestamp-duration.md) -### 3.6. Message-level Constraints +### 3.6. Message-level constraints - [Overview](03-built-in-options/message/index.md) - [required_for](03-built-in-options/message/required-for.md) -- [Nested Validation](03-built-in-options/message/nested-validation.md) -- [Cross-field Validation](03-built-in-options/message/cross-field.md) +- [Nested validation](03-built-in-options/message/nested-validation.md) +- [Cross-field validation](03-built-in-options/message/cross-field.md) -## 4. Using Validation in Code +## 4. Using validation in code - [Overview](04-using-validation/index.md) -- [Validating Messages](04-using-validation/validating-messages.md) -- [Handling Errors](04-using-validation/handling-errors.md) -- [Kotlin Usage](04-using-validation/kotlin-usage.md) -- [Framework Integration](04-using-validation/framework-integration.md) +- [Validating messages](04-using-validation/validating-messages.md) +- [Handling errors](04-using-validation/handling-errors.md) +- [Kotlin usage](04-using-validation/kotlin-usage.md) +- [Framework integration](04-using-validation/framework-integration.md) -## 5. Configuration & Tooling +## 5. Configuration & tooling - [Overview](05-configuration/index.md) -- [Compiler Configuration](05-configuration/compiler.md) -- [Library Modules](05-configuration/modules.md) -- [Debugging Generated Code](05-configuration/debugging.md) +- [Compiler configuration](05-configuration/compiler.md) +- [Library modules](05-configuration/modules.md) +- [Debugging generated code](05-configuration/debugging.md) -## 6. Extending Validation +## 6. Extending validation - [Overview](06-extending/index.md) - [Architecture](06-extending/architecture.md) -- [Custom Validation Options](06-extending/custom-options.md) -- [Custom Runtime Validators](06-extending/custom-runtime-validators.md) +- [Custom validation options](06-extending/custom-options.md) +- [Custom runtime validators](06-extending/custom-runtime-validators.md) -## 7. Recipes (Cookbook) +## 7. Recipes (cookbook) - [Overview](07-recipes/index.md) - [Domain IDs](07-recipes/domain-ids.md) -- [Common Cases](07-recipes/common-cases.md) -- [Temporal Logic](07-recipes/temporal-logic.md) -- [Cross-field Logic](07-recipes/cross-field-logic.md) -- [API Validation](07-recipes/api-validation.md) +- [Common cases](07-recipes/common-cases.md) +- [Temporal logic](07-recipes/temporal-logic.md) +- [Cross-field logic](07-recipes/cross-field-logic.md) +- [API validation](07-recipes/api-validation.md) ## 8. Reference -- [Reference Overview](08-reference/index.md) -- [List of Validation Options](08-reference/options.md) -- [Java/Kotlin API Index](08-reference/api.md) +- [Reference overview](08-reference/index.md) +- [List of validation options](08-reference/options.md) +- [Java/Kotlin API index](08-reference/api.md) - [Glossary](08-reference/glossary.md) diff --git a/docs/developers-guide/key-modules.md b/docs/developers-guide/key-modules.md index 4de498a725..f0d6b9bfc1 100644 --- a/docs/developers-guide/key-modules.md +++ b/docs/developers-guide/key-modules.md @@ -1,5 +1,5 @@ -### Key Modules +### Key modules | Module | Description | |-----------|----------------------------------------------------------------------| From 90bc7dbc76646389badf9fe72beaf4108a32ed6e Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 17:16:31 +0000 Subject: [PATCH 13/45] Allow empty lines after headers --- docs/00-intro/index.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index ce1654c0cc..c46fe76049 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -12,6 +12,7 @@ in any Java/Kotlin backend that models data using Protocol Buffers. ## Key capabilities ### Declarative constraints in `.proto` + Validation rules are expressed as Protobuf options such as: - `required` @@ -23,8 +24,9 @@ Validation rules are expressed as Protobuf options such as: This keeps validation close to the data model and ensures it evolves together with it. -### Generated validators -The Spine Compiler plugin processes your Protobuf model and generates: +### Generated validation code + +The Validation Plugin for Spine Compiler processes your Protobuf model and generates: - validation code for messages and builders, - runtime checks, @@ -43,14 +45,16 @@ Errors are represented as structured diagnostics suitable for API responses, logs, or domain exception flows. ### Rich domain-oriented constraints + Beyond simple “required/min/max”, the library includes: -- collection rules (`distinct`, `non_empty`), +- collection rules (`distinct`, `required`), - nested and cross-field validation, - advanced string formats (using regex), - temporal constraints (`PAST`, `FUTURE`). ### Extensible architecture + Teams can define custom validation options by: - declaring new `.proto` options, @@ -73,7 +77,7 @@ Use Spine Validation if: It is especially useful in: - backend services (Java/Kotlin), -- event-driven and CQRS systems, +- message-driven systems, - systems with rich domain models, - multi-service environments where shared `.proto` models are common. From 78da3fb30edaf11e6cecbad038ce53eefc8dde0b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 17:43:21 +0000 Subject: [PATCH 14/45] Fix the proto options example Also: * Update the reference to Jakarta Validation, addressing the recent name change. --- docs/00-intro/philosophy.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/00-intro/philosophy.md b/docs/00-intro/philosophy.md index d4645284c5..0efdab7c6d 100644 --- a/docs/00-intro/philosophy.md +++ b/docs/00-intro/philosophy.md @@ -34,7 +34,7 @@ validation logic. ## 2. Generated code over reflection -Reflection-based frameworks (Bean Validation, etc.) are convenient but come with: +Reflection-based frameworks (Jakarta Validation, etc.) are convenient but come with: - runtime penalties, - fragile metadata conventions (annotations, naming), @@ -48,17 +48,18 @@ Benefits: - fast, predictable runtime without reflection, - validation logic is type-safe, - errors in validation configuration are caught early, -- generated validators integrate naturally into the Protobuf builder model. +- generated validation code integrates naturally into the Protobuf builder model. This approach scales better for complex domain models or high-throughput services. - ## 3. Declarative, not imperative Validation is expressed declaratively through Protobuf options: ```proto -string email = 1 [(pattern).email = true, (required) = true]; +message Name { + string value = 1 [(required) = true, (pattern).regex = "^[A-Za-z ]+$"]; +} ``` Declarative rules: * are concise, @@ -90,7 +91,7 @@ design contexts. ## 5. Domain-oriented constraints -Instead of focusing only on primitive checks (min/max, required), the library +Instead of focusing only on primitive checks (`min`/`max`, `required`), the library embraces **domain semantics**, such as: * temporal rules (past, future), @@ -118,7 +119,7 @@ front-end models, or JSON schemas. Its focus is entirely on: ``` -Protobuf → generated Java/Kotlin → domain logic +Protobuf → generated Java/Kotlin/TypeScript → domain logic ``` Everything else (frontend validation, OpenAPI, view models) should build on top From 2d1f0aac8f9dde823824d49ecb9e10a95cff0ab7 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 17:55:11 +0000 Subject: [PATCH 15/45] Use braces around proto options --- docs/00-intro/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index c46fe76049..07aea6c51b 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -15,10 +15,10 @@ in any Java/Kotlin backend that models data using Protocol Buffers. Validation rules are expressed as Protobuf options such as: -- `required` -- `min` / `max` -- `pattern` -- `when.in = PAST | FUTURE` +- `(required)` +- `(min)` / `(max)` +- `(pattern)` +- `(when).in = PAST | FUTURE` - cross-field rules and message-level constraints This keeps validation close to the data model and ensures it evolves together @@ -48,7 +48,7 @@ logs, or domain exception flows. Beyond simple “required/min/max”, the library includes: -- collection rules (`distinct`, `required`), +- collection rules (`(distinct)`, `(required)`), - nested and cross-field validation, - advanced string formats (using regex), - temporal constraints (`PAST`, `FUTURE`). From 0300e790af9d37ec1f8de591238b6a880c6b7ad2 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 18:15:23 +0000 Subject: [PATCH 16/45] Temporarily add source code of proto options --- docs/options.proto | 1566 +++++++++++++++++++++++++++++++++++++++ docs/time_options.proto | 106 +++ 2 files changed, 1672 insertions(+) create mode 100644 docs/options.proto create mode 100644 docs/time_options.proto diff --git a/docs/options.proto b/docs/options.proto new file mode 100644 index 0000000000..31f02bfa67 --- /dev/null +++ b/docs/options.proto @@ -0,0 +1,1566 @@ +/* + * 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. + */ +syntax = "proto3"; + +// API Note on Packaging +// --------------------- +// We do not define the package for this file to allow shorter options for user-defined types. +// This allows to write: +// +// option (internal) = true; +// +// instead of: +// +// option (spine.base.internal) = true; +// + +// Custom Type Prefix Option +// ------------------------- +// The custom `type_url_prefix` option allows to define specify custom type URL prefix for messages +// defined in a proto file. This option is declared in this file. Other proto files must import +// `options.proto` to be able to specify custom type URL prefix. +// +// It is recommended that the import statement is provided before the line with `type_url_prefix` +// option to make it obvious that custom option is defined in the imported file. +// +// For example: +// +// syntax = "proto3"; +// +// package my.package; +// +// import "spine/options.proto"; +// +// option (type_url_prefix) = "type.example.org"; +// + +option (type_url_prefix) = "type.spine.io"; +option java_multiple_files = true; +option java_outer_classname = "OptionsProto"; +option java_package = "io.spine.option"; + +import "google/protobuf/descriptor.proto"; + +// +// Reserved Range of Option Field Numbers +// -------------------------------------- +// Spine Options use the range of option field numbers from the internal range reserved for +// individual organizations. For details of custom Protobuf options and this range please see: +// +// https://developers.google.com/protocol-buffers/docs/proto#customoptions +// +// The whole range reserved for individual organizations is 50000-99999. +// The range used by Spine Options is 73812-75000. +// In order to prevent number collision with custom options used by a project based on Spine, +// numbers for custom options defined in this project should be in the range 50000-73811 or +// 75001-99999. +// + +extend google.protobuf.FieldOptions { + + // Field Validation Constraints + //----------------------------- + // For constraints defined via message-based types, please see documentation of corresponding + // message types. + // + + // The option to mark a field as required. + // + // When this option is set: + // + // 1. For message or enum fields, the field must be set to a non-default instance. + // 2. For `string` and `bytes` fields, the value must be set to a non-empty string or an array. + // 3. For repeated fields and maps, at least one element must be present. + // + // Other field types are not supported by the option. + // + // Unlike the `required` keyword in Protobuf 2, this option does not affect message + // serialization or deserialization. Even if a message content violates the requirement + // set by the option, it would still be a valid message for the Protobuf library. + // + // Example: Using `(required)` field validation constraint. + // + // message MyOuterMessage { + // MyMessage field = 1 [(required) = true]; + // } + // + bool required = 73812; + + // See `IfMissingOption` + IfMissingOption if_missing = 73813; + + // Reserved 73814 and 73815 for deleted options `decimal_max` and `decimal_min`. + + // A higher boundary to the range of values of a number. + MaxOption max = 73816; + + // A lower boundary to the range of values of a number. + MinOption min = 73817; + + // 73818 reserved for the (digits) option. + + // 73819 reserved for the (when) option. + + // See `PatternOption`. + PatternOption pattern = 73820; + + // Enables in-depth validation for fields that refer to a message. + // + // This option applies only to fields that reference a message: + // + // 1. Singular message fields. + // 2. Repeated fields of message types. + // 3. Map fields with message types as values. + // + // When set to `true`, the field is valid only if its value satisfies the validation + // constraints defined in the corresponding message type: + // + // 1. For singular message fields: the message must meet its constraints. + // + // Note: default instances are considered valid even if the message has required fields. + // In such cases, it is unclear whether the field is set with an invalid instance or + // simply unset. + // + // Example: + // + // ``` + // // Note that the default instance of `Address` is not valid because the `value` field + // // is mandatory. + // message Address { + // string value = 1 [(required) = true]; + // } + // + // // However, the default instance of `Address` in `Student.address` is valid, despite + // // having `(validate)` constraint that forces the message to meet its constraints. + // // Since the `address` field is optional for `Student`, `(validate)` would allow + // // default instances for this field treating them as "no value set". + // message Student { + // Address address = 1 [(validate) = true]; // implicit `(required) = false`. + // } + // + // // Make the validated field required to avoid this behavior. In this case, `(validate)` + // // continues to bypass default instances, but the `(required)` option will report them. + // message Student { + // Address address = 1 [(validate) = true, (required) = true]; + // } + // ``` + // + // 2. For repeated fields: every element in the repeated field must meet the constraints + // of its message type. + // + // Example: + // + // ``` + // // Note that the default instance of `PhoneNumber` is not valid because the `value` + // // field is mandatory. + // message PhoneNumber { + // string value = 1 [(required) = true, (pattern).regex = "^\+?[0-9\s\-()]{1,30}$"]; + // } + // + // // In contrast to singular fields, the default instances in `repeated` will also be + // // reported by the `(validate)` constraint, with those that do not match the pattern. + // message Student { + // repeated PhoneNumber number = 1 [(validate) = true]; + // } + // ``` + // + // 3. For map fields: each value in the map must meet the constraints of message type. + // Note: Protobuf does not allow messages to be used as map keys. + // + // Example: + // + // ``` + // // Note that the default instance of `PhoneNumber` is not valid because the `value` + // // field is mandatory. + // message PhoneNumber { + // string value = 1 [(required) = true, (pattern).regex = "^\+?[0-9\s\-()]{1,30}$"]; + // } + // + // // In contrast to singular fields, the default instances in `map` values will also be + // // reported by the `(validate)` constraint, with those that do not match the pattern. + // message Contacts { + // map map = 1 [(validate) = true]; + // } + // ``` + // + // If the field contains `google.protobuf.Any`, the option will first attempt to unpack + // the enclosed message, and only then validate it. However, unpacking is not always possible: + // + // 1. The default instance of `Any` is always valid because there is nothing to unpack + // and validate. + // 2. Instances with type URLs that are unknown to the application are also valid. + // + // Unpacking requires a corresponding Java class to deserialize the message, but if + // the application does not recognize the type URL, it has no way to determine which + // class to use. + // + // Such may happen when the packed message comes from a newer app version, an external + // system, or is simply not included in the application’s dependencies. + // + bool validate = 73821; + + // See `IfInvalidOption`. + IfInvalidOption if_invalid = 73822 [deprecated = true]; + + // See `GoesOption`. + GoesOption goes = 73823; + + // Indicates that a field can only be set once. + // + // This option allows the target field to accept assignments only if one of the following + // conditions is met: + // + // 1. The current field value is the default for its type. + // Refer to the official docs on default values: https://protobuf.dev/programming-guides/proto3/#default. + // 2. The current field value equals to the proposed new value. + // + // The option can be applied to the following singular field types: + // + // - any message or enum type; + // - any numeric type; + // - `bool`, `string` and `bytes`. + // + // Repeated fields, maps, and fields with explicit `optional` cardinality are not supported. + // Such declarations will lead to build-time errors. For more information on field cardinality, + // refer to the official docs: https://protobuf.dev/programming-guides/proto3/#field-labels. + // + // Assigning a value to a message field can be done in various ways in the generated code. + // It depends on the target language and specific implementation of `protoc`. This option + // doesn't enforce field immutability at the binary representation level. Also, it does not + // prevent the use of Protobuf utilities that can construct new messages without using field + // setters or properties. The primary purpose of this option is to support standard use cases, + // such as assigning values through setters or retrieving them during data merging. + // + // For example, let's take a look on how it works for the generated Java code. The following + // use cases are supported and validated by the option: + // + // 1. Assigning a field value using the field setter. + // 2. Assigning a field value using the field descriptor. + // 3. Merging data from another instance of the message class. + // 4. Merging data from a message's binary representation. + // 5. Merging specific fields (available for Message fields). + // + // Unsupported Java use cases include: + // + // 1. Constructing a message using the `DynamicMessage` class. + // 2. Merging data from messages provided by third-party Protobuf implementations. + // 3. Clearing a specific field or an entire message. + // + // For unsupported scenarios, the option performs no validation. It does not throw errors + // or print warnings. + // + // A typical use case involves a field, such as an ID, which remains constant throughout + // the lifecycle of an entity. + // + // Example: using `(set_once)` field validation constraint. + // + // message User { + // UserId id = 1 [(set_once) = true]; + // } + // + // Once set, the `id` field cannot be changed. + // + // Use `(if_set_again).error_msg` option to specify a custom error message that will be used for + // composing the error upon attempting to re-assign the field value. Refer to the documentation + // for the corresponding option for an example of its usage. + // + bool set_once = 73824; + + // The option to enforce uniqueness for collection fields. + // + // When the option is set to `true`, the behavior is as follows: + // + // 1. For `repeated` fields: all elements must be unique. + // 2. For `map` fields: while the map keys are inherently unique, all associated values + // must also be unique. + // + // Other field types are not supported. + // + // Uniqueness is determined by comparing the elements themselves, using their full equality. + // For example, in Java, it is defined by the `equals()` method. No special cases are applied, + // such as comparing only specific fields like IDs. + // + // Example: using `(distinct)` constraint for a `repeated` field. + // + // message Blizzard { + // + // // All snowflakes must be unique in this blizzard. + // // + // // Attempting to add a snowflake that is equal to an existing one would result + // // in a constraint violation error. + // // + // repeated Snowflake snowflakes = 1 [(distinct) = true]; + // } + // + // Example: using `(distinct)` constraint for a `map` field. + // + // message UniqueEmails { + // + // // The associated email values must be unique. + // // + // // Attempting to add a key/value pair where the `Email` value duplicates + // // an existing one would result in a constraint violation error. + // // + // map emails = 1 [(distinct) = true]; + // } + // + bool distinct = 73825; + + // Reserved 73826 for deleted `range` option, which had `string` type. + + // Defines the error message used if a `set_once` field is set again. + // + // Applies only to the fields marked as `set_once`. + // + IfSetAgainOption if_set_again = 73827; + + // Defines the error message used if a `distinct` field has duplicates. + // + // Applies only to the repeated fields marked as `distinct`. + // + IfHasDuplicatesOption if_has_duplicates = 73828; + + // The option to indicate that a numeric field is required to have a value which belongs + // to the specified bounded range. + // + // For unbounded ranges, please use `(min)` and `(max) options. + // + RangeOption range = 73829; + + // Reserved 73830 to 73849 for future validation options. + + // API Annotations + //----------------- + + // Indicates a field which is internal to Spine, not part of the public API, and should not be + // used by users of the framework. + // + // If you plan to implement an extension of the framework, which is going to be + // wired into the framework, you may use the internal parts. Please consult with the Spine + // team, as the internal APIs do not have the same stability API guarantee as public ones. + // + bool internal = 73850; + + // Reserved 73851 for the deleted SPI option. + + // Indicates a field that can change at any time, and has no guarantee of API stability and + // backward-compatibility. + // + // Usage guidelines: + // 1. This annotation is used only on public API. Internal interfaces should not use it. + // 2. This annotation can only be added to new API. Adding it to an existing API is considered + // API-breaking. + // 3. Removing this annotation from an API gives it stable status. + // + bool experimental = 73852; + + // Signifies that a public API is subject to incompatible changes, or even removal, in a future + // release. + // + // An API bearing this annotation is exempt from any compatibility guarantees made by its + // containing library. Note that the presence of this annotation implies nothing about the + // quality of the API in question, only the fact that it is not "API-frozen." + // It is generally safe for applications to depend on beta APIs, at the cost of + // some extra work during upgrades. + // + bool beta = 73853; + + // Marks an entity state field as column. + // + // The column fields are stored separately from the entity record and can be specified as + // filtering criteria during entity querying. + // + // The column field should be declared as follows: + // + // message UserProfile { + // ... + // int32 year_of_registration = 8 [(column) = true]; + // } + // + // The `year_of_registration` field value can then be used as query parameter when reading + // entities of `UserProfile` type from the server side. + // + // The value of a column field can be updated in two ways: + // + // 1. In the receptors of the entity, just like any other part of entity state. + // 2. Using the language-specific tools like `EntityWithColumns` interface in Java. + // + // All column fields are considered optional by the framework. + // + // Currently, only entities of projection and process manager type are + // eligible for having columns (see `EntityOption`). + // For all other message types the column declarations are ignored. + // + // The `repeated` and `map` fields cannot be columns. + // + bool column = 73854; + + // Reserved 73855 to 73890 for future options. + + // Reserved 73900 for removed `by` option. +} + +extend google.protobuf.OneofOptions { + + // Deprecated: use the `(choice)` option instead. + bool is_required = 73891 [deprecated = true]; + + // Controls whether a `oneof` group must always have one of its fields set. + ChoiceOption choice = 73892; + + // Reserved 73893 to 73899 for future options. +} + +extend google.protobuf.MessageOptions { + + // Validation Constraints + //------------------------ + + // The default validation error message. + // + // Please note, this option is intended for INTERNAL USE only. It applies to message types + // that extend `FieldOptions` and is not intended for external usage. + // + // If a validation option detects a constraint violation and no custom error message is defined + // for that specific option, it will fall back to the message specified by `(default_message)`. + // + // For example, here is how to declare the default message for `(goes)` option: + // + // ``` + // message GoesOption { + // // The default error message. + // option (default_message) = "The field `${goes.companion}` must also be set when `${field.path}` is set."; + // } + // ``` + // + // Note: The placeholders available within `(default_message)` depend solely on the particular + // validation option that uses it. Each option may define its own set of placeholders, or none. + // + string default_message = 73901 [(internal) = true]; + + // Deprecated: use the `(require)` option instead. + string required_field = 73902 [deprecated = true]; + + // See `EntityOption`. + EntityOption entity = 73903; + + // An external validation constraint for a field. + // + // WARNING: This option is deprecated and is scheduled for removal in Spine v2.0.0. + // + // Allows to re-define validation constraints for a message when its usage as a field of + // another type requires alternative constraints. This includes definition of constraints for + // a message which does not have them defined within the type. + // + // A target field of an external constraint should be specified using a fully-qualified + // field name (e.g. `mypackage.MessageName.field_name`). + // + // Example: defining external validation constraint. + // + // package io.spine.example; + // + // // Defines a change in a string value. + // // + // // Both of the fields of this message are not `required` to be able to describe + // // a change from empty value to non-empty value, or from a non-empty value to + // // an empty string. + // // + // message StringChange { + // + // // The value of the field that's changing. + // string previous_value = 1; + // + // // The new value of the field. + // string new_value = 2; + // } + // + // // A command to change a name of a task. + // // + // // The task has a non-empty name. A new name cannot be empty. + // // + // message RenameTask { + // + // // The ID of the task to rename. + // string task_id = 1; + // + // // Instruction for changing the name. + // // + // // The value of `change.previous_value` is the current name of the task. + // // It cannot be empty. + // // + // // The value of `change.new_value` is the new name of the task. + // // It cannot be empty either. + // // + // StringChange change = 1 [(validate) = true]; + // } + // + // // External validation constraint for both fields of the `StringChange` message + // // in the scope of the `RenameTask` command. + // // + // message RequireTaskNames { + // option (constraint_for) = "spine.example.RenameTask.change"; + // + // string previous_value = 1 [(required) = true]; + // string new_value = 2 [(required) = true]; + // } + // + // NOTE: A target field for an external validation constraint must be have the option `(validate)` + // set to `true`. See the definition of the `RenameTask.change` field in the example + // above. If there is no such option defined, or it is set to `false`, the external + // constraint will not be applied. + // + // External validation constraints can be applied to fields of several types. + // To do so, separate fully-qualified references to these fields with comma. + // + // Example: external validation constraints for multiple fields. + // + // // External validation constraint for requiring a new value in renaming commands. + // message RequireNewName { + // option (constraint_for) = "spine.example.RenameTask.change," + // "spine.example.RenameProject.change,"; + // "spine.example.UpdateComment.text_change; + // + // string new_value = 1 [(required) = true]; + // } + // + // NOTE: An external validation constraint for a field must be defined only once. + // Spine Model Compiler does not check such an "overwriting". + // See the issue: https://github.com/SpineEventEngine/base/issues/318. + // + string constraint_for = 73904 [deprecated = true]; + + // Reserved 73905 to 73910 for future validation options. + + // API Annotations + //----------------- + + // Indicates a type usage of which is restricted in one of the following ways. + // + // 1. This type is internal to the Spine Event Engine framework. It is not a part of + // the public API, and must not be used by framework users. + // + // 2. The type is internal to a bounded context, artifact of which exposes the type to + // the outside world (presumably for historical reasons). + // + // The type with such an option can be used only inside the bounded context which declares it. + // + // The type must not be used neither for inbound (i.e. being sent to the bounded context + // which declares this type) nor for outbound communication (i.e. being sent by this + // bounded context outside). + // + // An attempt to violate these usage restrictions will result in a runtime error. + // + bool internal_type = 73911; + + // Indicates a file which contains elements of Service Provider Interface (SPI). + bool SPI_type = 73912; + + // Indicates a public API that can change at any time, and has no guarantee of + // API stability and backward-compatibility. + bool experimental_type = 73913; + + // Signifies that a public API is subject to incompatible changes, or even removal, + // in a future release. + bool beta_type = 73914; + + // Specifies a characteristic inherent in the the given message type. + // + // Example: using `(is)` message option. + // + // message CreateProject { + // option (is).java_type = "ProjectCommand"; + // + // // Remainder omitted. + // } + // + // In the example above, `CreateProject` message is a `ProjectCommand`. + // + // To specify a characteristic for every message in a `.proto` file, + // please use `(every_is)` file option. + // + // If both `(is)` and `(every_is)` options are applicable for a type, both are applied. + // + // When targeting Java, specify the name of a Java interface to be implemented by this + // message via `(is).java_type`. + // + IsOption is = 73915; + + // Reserved 73916 to 73921 for future API annotation options. + + // Reserved 73922 for removed `enrichment_for` option. + + // Specifies the natural ordering strategy for this type. + // + // Code generators should generate language-specific comparisons based on the field paths. + // + // Runtime comparators may use the reflection API to compare field values. + // + CompareByOption compare_by = 73923; + + // The constraint to require at least one of the fields or combinations of fields. + RequireOption require = 73924; + + // Reserved 73925 to 73938 for future options. + + // Reserved 73939 and 73940 for the deleted options `events` and `rejections`. +} + +extend google.protobuf.FileOptions { + + // Specifies a type URL prefix for all types within a file. + // + // This type URL will be used when packing messages into `Any`. + // See `any.proto` for more details. + // + string type_url_prefix = 73941; + + // Indicates a file which contains types usage of which is restricted. + // + // For more information on such restrictions please see the documentation of + // the type option called `internal_type`. + // + // If a file contains a declaration of a `service`, this option will NOT be applied to it. + // A service is not a data type, and therefore, this option does not apply to it. + // Internal services are not supported. + // + bool internal_all = 73942; + + // Indicates a file which contains elements of Service Provider Interface (SPI). + // + // This option applies to messages, enums, and services. + // + bool SPI_all = 73943; + + // Indicates a file declaring public data type API which that can change at any time, + // has no guarantee of API stability and backward-compatibility. + // + // If a file contains a declaration of a `service`, this option will NOT be applied to it. + // A service is not a data type, and therefore, this option does not apply to it. + // Experimental services are not supported. + // + bool experimental_all = 73944; + + // Signifies that a public data type API is subject to incompatible changes, or even removal, + // in a future release. + // + // If a file contains a declaration of a `service`, this option will NOT be applied to it. + // A service is not a data type, and therefore, this option does not apply to it. + // Beta services are not supported. + // + bool beta_all = 73945; + + // Specifies a characteristic common for all the message types in the given file. + // + // Example: marking all the messages using the `(every_is)` file option. + // ``` + // option (every_is).java_type = "ProjectCommand"; + // + // message CreateProject { + // // ... + // + // message WithAssignee { + // // ... + // } + // } + // + // message DeleteProject { /*...*/ } + // ``` + // + // In the example above, `CreateProject`, `CreateProject.WithAssignee`, and `DeleteProject` + // messages are `ProjectCommand`-s. + // + // To specify a characteristic for a single message, please use `(is)` message option. + // If both `(is)` and `(every_is)` options are applicable for a type, both are applied. + // + // When targeting Java, specify the name of a Java interface to be implemented by these + // message types via `(every_is).java_type`. + // + EveryIsOption every_is = 73946; + + // Reserved 73947 to 73970 for future use. +} + +extend google.protobuf.ServiceOptions { + + // Indicates that the service is a part of Service Provider Interface (SPI). + bool SPI_service = 73971; + + // Reserved 73971 to 73980. +} + +// Reserved 73981 to 74000 for other future Spine Options numbers. + +// +// Validation Option Types +//--------------------------- + +// Defines the error message used if a `required` field is not set. +// +// Applies only to the fields marked as `required`. +// +message IfMissingOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}`" + " of the type `${field.type}` must have a non-default value."; + + // A user-defined validation error format message. + // + // Use `error_msg` instead. + // + string msg_format = 1 [deprecated = true]; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.type}` – the fully qualified name of the field type. + // 3. `${parent.type}` – the fully qualified name of the validated message. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + // Example: using the `(if_missing)` option. + // + // message Student { + // Name name = 1 [(required) = true, + // (if_missing).error_msg = "The `${field.path}` field is mandatory for `${parent.type}`."]; + // } + // + string error_msg = 2; +} + +// Indicates that the numeric field must be greater than or equal to the specified value. +// +// The option supports all singular and repeated numeric fields. +// +message MinOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}`" + " must be ${min.operator} ${min.value}. The passed value: `${field.value}`."; + + // The string representation of the minimum field value. + // + // ## Integer and floating-point values + // + // A minimum value for an integer field must use an integer number. Specifying a decimal + // number is not allowed, even if it has no fractional part (e.g., `5.0` is invalid). + // + // A minimum value for a floating-point field must use a decimal separator (`.`), even if + // the value has no fractional part. An exponent part represented by `E` or `e`, followed + // by an optional sign and digits is allowed (e.g., `1.2E3`, `0.5e-2`). + // + // Example: defining minimum values for integer and floating-point fields. + // + // message Measurements { + // int32 temperature = 1 [(min).value = "0"]; + // uint32 mass = 2 [(min).value = "5"]; + // float degree = 3 [(min).value = "0.0"]; + // double angle = 4 [(min).value = "30.0"]; + // float pressure = 5 [(min).value = "950.0E-2"]; + // } + // + // ## Field Type Limitations + // + // A minimum value must not fall below the limits of the field type. + // + // Example: invalid values that fall below the field type limits. + // + // message OverflowMeasurements { + // float pressure = 1 [(min).value = "-5.5E38"]; // Falls below the `float` minimum. + // uint32 mass = 2 [(min).value = "-5"]; // Falls below the `uint32` minimum. + // } + // + // ## Field references + // + // Instead of numeric literals, you can reference another numeric field. + // At runtime, the field’s value will be used as the bound. Nested fields are supported. + // + // Example: defining minimum values using field references. + // + // message Measurements { + // + // int32 min_length = 1; + // int32 length = 2 [(min).value = "min_length"]; + // + // Limits limits = 3; + // int32 temperature = 4 [(min).value = "limits.min_temperature"]; + // float pressure = 5 [(min).value = "limits.min_pressure"]; + // } + // + // message Limits { + // int32 min_temperature = 1; + // float min_pressure = 2; + // } + // + // Note: Field type compatibility is not required in this case; the value is + // automatically converted. However, only numeric fields can be referenced. + // Repeated and map fields are not supported. + // + string value = 1; + + // Specifies if the field should be strictly greater than the specified minimum. + // + // The default value is false, i.e. the bound is inclusive. + // + bool exclusive = 2; + + // A user-defined validation error format message. + string msg_format = 3 [deprecated = true]; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.value}` - the field value. + // 2. `${field.path}` – the field path. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the validated message. + // 5. `${min.value}` – the specified minimum `value`. For referenced fields, the actual + // field value is also printed in round brackets along with the reference itself. + // 6. `${min.operator}` – if `exclusive` is set to `true`, this placeholder equals to ">". + // Otherwise, ">=". + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 4; +} + +// Indicates that the numeric field must be less than or equal to the specified value. +// +// The option supports all singular and repeated numeric fields. +// +message MaxOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}`" + " must be ${max.operator} ${max.value}. The passed value: `${field.value}`."; + + // The string representation of the maximum field value. + // + // ## Integer and floating-point values + // + // A maximum value for an integer field must use an integer number. Specifying a decimal + // number is not allowed, even if it has no fractional part (e.g., `5.0` is invalid). + // + // A maximum value for a floating-point field must use a decimal separator (`.`), even if + // the value has no fractional part. An exponent part represented by `E` or `e`, followed + // by an optional sign and digits is allowed (e.g., `1.2E3`, `0.5e-2`). + // + // Example: defining maximum values for integer and floating-point fields. + // + // message Measurements { + // int32 temperature = 1 [(max).value = "270"]; + // uint32 mass = 2 [(max).value = "1200"]; + // float degree = 3 [(max).value = "360.0"]; + // double angle = 4 [(max).value = "90.0"]; + // float pressure = 5 [(max).value = "1050.0E-2"]; + // } + // + // ## Field Type Limitations + // + // A maximum value must not exceed the limits of the field type. + // + // Example: invalid values that exceed the field type limits. + // + // message OverflowMeasurements { + // float pressure = 1 [(min).value = "5.5E38"]; // Exceeds the `float` maximum. + // int32 mass = 2 [(min).value = "2147483648"]; // Exceeds the `int32` maximum. + // } + // + // ## Field references + // + // Instead of numeric literals, you can reference another numeric field. + // At runtime, the field’s value will be used as the bound. Nested fields are supported. + // + // Example: defining maximum values using field references. + // + // message Measurements { + // + // int32 max_length = 1; + // int32 length = 2 [(max).value = "max_length"]; + // + // Limits limits = 3; + // int32 temperature = 4 [(max).value = "limits.max_temperature"]; + // float pressure = 5 [(max).value = "limits.max_pressure"]; + // } + // + // message Limits { + // int32 max_temperature = 1; + // float max_pressure = 2; + // } + // + // Note: Field type compatibility is not required in this case; the value is + // automatically converted. However, only numeric fields can be referenced. + // Repeated and map fields are not supported. + // + string value = 1; + + // Specifies if the field should be strictly less than the specified maximum + // + // The default value is false, i.e. the bound is inclusive. + // + bool exclusive = 2; + + // A user-defined validation error format message. + string msg_format = 3 [deprecated = true]; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.value}` - the field value. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the validated message. + // 5. `${max.value}` – the specified maximum `value`. For referenced fields, the actual + // field value is also printed in round brackets along with the reference itself. + // 6. `${max.operator}` – if `exclusive` is set to `true`, this placeholder equals to "<". + // Otherwise, "<=". + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 4; +} + +// A string field value must match the given regular expression. +// +// This option is applicable only to string fields, +// including those that are repeated. +// +// Example: using the `(pattern)` option. +// +// message CreateAccount { +// string id = 1 [(pattern).regex = "^[A-Za-z0-9+]+$", +// (pattern).error_msg = "ID must be alphanumerical in `${parent.type}`. Provided: `${field.value}`."]; +// } +// +message PatternOption { + + // The default error message. + option (default_message) = "The `${parent.type}.${field.path}` field" + " must match the regular expression `${regex.pattern}` (modifiers: `${regex.modifiers}`)." + " The passed value: `${field.value}`."; + + // The regular expression that the field value must match. + // + // Please use the Java regex dialect for the syntax baseline: + // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html + // + // Note: in Java, regex patterns are not wrapped in explicit delimiters like in Perl or PHP. + // Instead, the pattern is provided as a string literal. Therefore, `/` symbol does not need + // to be escaped. + // + // The provided string literal is passed directly to the regex engine. So, it must be exactly + // what you would supply to the `java.util.regex.Pattern.compile()` method. + // + string regex = 1; + + reserved 2; + reserved "flag"; + + // Modifiers for this pattern. + Modifier modifier = 4; + + // A user-defined validation error format message. + string msg_format = 3 [deprecated = true]; + + // A user-defined validation error format message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.value}` - the field value. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the validated message. + // 5. `${regex.pattern}` – the specified regex pattern. + // 6. `${regex.modifiers}` – the specified modifiers, if any. For example, `[dot_all, unicode]`. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 5; + + // Regular expression modifiers. + // + // These modifiers are specifically selected to be supported in many implementation platforms. + // + message Modifier { + + // Enables the dot (`.`) symbol to match all the characters. + // + // By default, the dot does not match line break characters. + // + // May also be known in some platforms as "single line" mode and be encoded with + // the `s` flag. + // + bool dot_all = 1; + + // Allows to ignore the case of the matched symbols. + // + // For example, this modifier is specified, string `ABC` would be a complete match for + // the regex `[a-z]+`. + // + // On some platforms may be represented by the `i` flag. + // + bool case_insensitive = 2; + + // Enables the `^` (caret) and `$` (dollar) signs to match a start and an end of a line + // instead of a start and an end of the whole expression. + // + // On some platforms may be represented by the `m` flag. + // + bool multiline = 3; + + // Enables matching the whole UTF-8 sequences, + // + // On some platforms may be represented by the `u` flag. + // + bool unicode = 4; + + // Allows the matched strings to contain a full match to the pattern and some other + // characters as well. + // + // By default, a string only matches a pattern if it is a full match, i.e. there are no + // unaccounted for leading and/or trailing characters. + // + // This modifier is usually not represented programming languages, as the control over + // weather to match an entire string or only its part is provided to the user by other + // language means. For example, in Java, this would be the difference between methods + // `matches()` and `find()` of the `java.util.regex.Matcher` class. + // + bool partial_match = 5; + } +} + +// Specifies the message to show if a validated field happens to be invalid. +// +// It is applicable only to fields marked with `(validate)`. +// +message IfInvalidOption { + + // Do not specify error message for `(validate)`, it is no longer used by + // the validation library. + option deprecated = true; + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}` of the type" + " `${field.type}` is invalid. The field value: `${field.value}`."; + + // A user-defined validation error format message. + // + // Use `error_msg` instead. + // + string msg_format = 1 [deprecated = true]; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.value}` - the field value. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the field declaring type. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + // Example: using the `(if_invalid)` option. + // + // message Transaction { + // TransactionDetails details = 1 [(validate) = true, + // (if_invalid).error_msg = "The `${field.path}` field is invalid."]; + // } + // + string error_msg = 2; +} + +// Specifies that another field must be present if the option's target field is present. +// +// Unlike the `required_field` that handles combination of required fields, this option is useful +// when it is needed to say that an optional field makes sense only when another optional field +// is present. +// +// This option can be applied to the same field types as `(required)`, including both the +// target field and its companion. Supported field types are: +// +// - Messages and enums. +// - Repeated fields and maps. +// - `string` and `bytes`. +// +// Example: requiring mutual presence of optional fields. +// +// message ScheduledItem { +// ... +// spine.time.LocalDate date = 4; +// spine.time.LocalTime time = 5 [(goes).with = "date"]; +// } +// +message GoesOption { + + // The default error message. + option (default_message) = "The field `${goes.companion}` must also be set when `${field.path}`" + " is set in `${parent.type}`."; + + // The name of the companion field whose presence is required for this field to be valid. + string with = 1; + + // A user-defined validation error format message. + string msg_format = 2 [deprecated = true]; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.value}` – the field value. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the validated message. + // 5. `${goes.companion}` – the name of the companion specified in `with`. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 3; +} + +// Defines options of a message representing a state of an entity. +message EntityOption { + + // A type of an entity for state of which the message is defined. + enum Kind { + option allow_alias = true; + + // Reserved for errors. + KIND_UNKNOWN = 0; + + // The message is an aggregate state. + AGGREGATE = 1; + + // The message is a state of a projection (same as "view"). + PROJECTION = 2; + + // The message is a state of a view (same as "projection"). + VIEW = 2; + + // The message is a state of a process manager. + PROCESS_MANAGER = 3; + + // The message is a state of an entity, which is not of the type + // defined by other members of this enumeration. + ENTITY = 4; + } + + // The type of the entity. + Kind kind = 1; + + // The level of visibility of the entity to queries. + enum Visibility { + + // Default visibility is different for different types of entities: + // - for projections, "FULL" is default; + // - for aggregates, process managers, and other entities, "NONE" is default. + // + DEFAULT = 0; + + // The entity is not visible to queries. + NONE = 1; + + // Client-side applications can subscribe to updates of entities of this type. + SUBSCRIBE = 2; + + // Client-side applications can query this type of entities. + QUERY = 3; + + // Client-side applications can subscribe and query this type of entity. + FULL = 4; + } + + // The visibility of the entity. + // + // If not defined, the value of this option is `DEFAULT`. + // + Visibility visibility = 2; +} + +// Defines a common type for message types declared in the same proto file. +// +// The nature of the type depends on the target programming language. +// For example, the `java_type` property defines a name of the Java interface common +// to all message classes generated for the proto file having this option. +// +// The option triggers creation of the common type if the `generate` property is set to true. +// Otherwise, it is expected that the user provides the reference to an existing type. +// +message EveryIsOption { + + // Enables the generation of the common type. + // + // The default value is `false`. + // + bool generate = 1; + + // The reference to a Java top-level interface. + // + // The interface cannot be nested into a class or another interface. + // If a nested interface is provided, the code generation should fail the build process. + // + // The value may be a fully-qualified or a simple name. + // + // When a simple name is set, it is assumed that the interface belongs to + // the package of the generated message classes. + // + // If the value of the `generate` field is set to `false` the referenced interface must exist. + // Otherwise, a compilation error will occur. + // + // If the value of the `generate` field is set to `true`, the framework will + // generate the interface using the given name and the package as described above. + // + // The generated interface will extend `com.google.protobuf.Message` and + // will have no declared methods. + // + string java_type = 2; +} + +// Defines additional type for a message type in which this option is declared. +// +// The nature of the type depends on the target programming language. +// For example, the `java_type` property defines a name of the Java interface which +// the generated message class will implement. +// +message IsOption { + + // The reference to a Java top-level interface. + // + // The interface cannot be nested into a class or another interface. + // If a nested interface is provided, the code generation should fail the build process. + // + // The value may be a fully-qualified or a simple name. + // + // When a simple name is set, it is assumed that the interface belongs to + // the package of the generated message classes. + // + // The referenced interface must exist. Otherwise, a compilation error will occur. + // + string java_type = 1; +} + +// Defines the way to compare two messages of the same type to one another. +// +// Comparisons can be used to sort values. +// +// See the `(compare_by)` option. +// +message CompareByOption { + + // Field paths used for comparisons. + // + // The allowed field types are: + // - any number type; + // - `bool` (`false` is less than `true`); + // - `string` (in the order of respective Unicode values); + // - enumerations (following the order of numbers associated with each constant); + // - local messages (generated within the current build) marked with `(compare_by)`; + // - external messages (from dependencies), which either marked with `(compare_by)` + // OR have a comparator provided in `io.spine.compare.ComparatorRegistry`. + // + // Other types are not permitted. Repeated or map fields are not permitted either. + // Such declarations will lead to build-time errors. + // + // To refer to nested fields, separate the field names with a dot (`.`). + // No fields in the path can be repeated or maps. + // + // When multiple field paths are specified, comparison is executed in the order of reference. + // For example, specifying `["seconds", "nanos"]` makes the comparison mechanism prioritize + // the `seconds` field and refer to `nanos` only when `seconds` are equal. + // + // NOTE: When comparing fields with a message type, a non-set message is always less than + // a set message. But if a message is set to a default value, the comparison falls back to + // the field-wise comparison, i.e. number values are treated as zeros, `bool` — as `false`, + // and so on. + // + repeated string field = 1; + + // If true, the default order is reversed. For example, numbers are ordered from the greater to + // the lower, enums — from the last number value to the 0th value, etc. + bool descending = 2; +} + +// Defines the error message used if a `set_once` field is set again. +// +// Applies only to the fields marked as `set_once`. +// +message IfSetAgainOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}` of the type" + " `${field.type}` already has the value `${field.value}` and cannot be reassigned" + " to `${field.proposed_value}`."; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.type}` – the fully qualified name of the field type. + // 3. `${field.value}` – the current field value. + // 4. `${field.proposed_value}` – the value, which was attempted to be set. + // 5. `${parent.type}` – the fully qualified name of the validated message. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + // Example: using the `(set_once)` option. + // + // message User { + // UserId id = 1 [(set_once) = true, + // (if_set_again).error_msg = "A student ID is used as a permanent identifier within academic system, and cannot be re-assigned."]; + // } + // + string error_msg = 1; +} + +// Defines the error message used if a `distinct` field has duplicates. +// +// Applies only to the `repeated` and `map` fields marked with the `(distinct)` option. +// +message IfHasDuplicatesOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}` of the type" + " `${field.type}` must not contain duplicates." + " The duplicates found: `${field.duplicates}`."; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.type}` – the fully qualified name of the field type. + // 3. `${field.value}` – the field value (the whole collection). + // 4. `${field.duplicates}` – the duplicates found (elements that occur more than once). + // 5. `${parent.type}` – the fully qualified name of the validated message. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + // Example: using the `(distinct)` option. + // + // message Blizzard { + // repeated Snowflake = 1 [(distinct) = true, + // (if_has_duplicates).error_msg = "Every snowflake must be unique! The duplicates found: `${field.duplicates}`."]; + // } + // + string error_msg = 1; +} + +// Indicate that the numeric field must belong to the specified bounded range. +// +// For unbounded ranges, please use `(min)` and `(max) options. +// +// The option supports all singular and repeated numeric fields. +// +message RangeOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}` must be within" + " the following range: `${range.value}`. The passed value: `${field.value}`."; + + // The string representation of the range. + // + // A range consists of two bounds: a lower bound and an upper bound. These bounds are + // separated by either the `..` or ` .. ` delimiter. Each bound can be either open + // or closed. The format of the bounds and the valid values depend on the type of field. + // + // ## Bound Types + // + // - Closed bounds include the endpoint and are indicated using square brackets (`[`, `]`). + // Example: `[0..10]` represents values from 0 to 10, inclusive. + // + // - Open bounds exclude the endpoint and are indicated using parentheses (`(`, `)`). + // Example: `(0..10)` represents values strictly between 0 and 10. + // + // The lower bound must be less than or equal to the upper bound. + // + // ## Integer Fields + // + // A range for an integer field must use integer numbers. Specifying a decimal number + // is not allowed, even if it has no fractional part (e.g., `5.0` is invalid). + // + // Example: defining ranges for integer fields. + // + // message Measurements { + // int32 length = 1 [(range).value = "[0..100)"]; + // uint32 mass = 2 [(range).value = "(0..100]"]; + // } + // + // ## Floating-Point Fields + // + // A range for a floating-point field must use a decimal separator (`.`), even if the value + // has no fractional part. An exponent part represented by `E` or `e`, followed by an optional + // sign and digits is allowed (e.g., `1.2E3`, `0.5e-2`). + // + // Example: defining ranges for floating-point fields. + // + // message Measurements { + // float degree = 1 [(range).value = "[0.0 .. 360.0)"]; + // double angle = 2 [(range).value = "(0.0 .. 180.0)"]; + // float pressure = 3 [(range).value = "[950.0E-2 .. 1050.0E-2]"]; + // } + // + // ## Field Type Limitations + // + // A range must not exceed the limits of the field type. + // + // Example: invalid ranges that exceed the field type limits. + // + // message OverflowMeasurements { + // float price = 1 [(range).value = "[0, 5.5E38]"]; // Exceeds the `float` maximum. + // uint32 size = 2 [(range).value = "[-5; 10]"]; // Falls below the `uint32` minimum. + // } + // + // ## Field references + // + // Instead of numeric literals, you can reference another numeric field. + // At runtime, the field’s value will be used as the bound. Nested fields are supported. + // + // Example: defining ranges using field references. + // + // message Measurements { + // + // int32 max_length = 1; + // int32 length = 2 [(range).value = "[1 .. max_length"]; + // + // Limits limits = 3; + // int32 temperature = 4 [(range).value = "[limits.low_temp .. limits.high_temp]"]; + // } + // + // message Limits { + // int32 low_temp = 1; + // int32 high_temp = 2; + // } + // + // Note: Field type compatibility is not required in this case; the value is + // automatically converted. However, only numeric fields can be referenced. + // Repeated and map fields are not supported. + // + string value = 1; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.value}` - the field value. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the validated message. + // 5. `${range.value}` – the specified range. For referenced fields, the actual + // field value is also printed in round brackets along with the reference itself. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 2; +} + +// Controls whether a `oneof` group must always have one of its fields set. +// +// Note that unlike the `(required)` constraint, this option supports any field types +// within the group cases. +// +message ChoiceOption { + + // The default error message. + option (default_message) = "The `oneof` group `${parent.type}.${group.path}` must" + " have one of its fields set."; + + // Enables or disables the requirement for the `oneof` group to have a value. + bool required = 1; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${group.path}` – the group path. + // 2. `${parent.type}` – the fully qualified name of the validated message. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 2; +} + +// Declares the field groups, at least one of which must have all of its fields set. +// +// Unlike the `(required)` field constraint, which requires the presence of +// a specific field, this option allows to specify alternative field groups. +// +message RequireOption { + + // The default error message. + option (default_message) = "The message `${message.type}` must have at least one of" + " the following field groups set: `${require.fields}`."; + + // A set of field groups, at least one of which must have all of its fields set. + // + // A field group can include one or more fields joined by the ampersand (`&`) symbol. + // `oneof` group names are also valid and can be used along with field names. + // Groups are separated using the pipe (`|`) symbol. + // + // The field type determines when the field is considered set: + // + // 1. For message or enum fields, it must have a non-default instance. + // 2. For `string` and `bytes` fields, it must be non-empty. + // 3. For repeated fields and maps, it must contain at least one element. + // + // Fields of other types are not supported by this option. + // + // For `oneof`s, the restrictions above do not apply. Any `oneof` group can be used + // without considering the field types, and its value will be checked directly without + // relying on the default values of the fields within the `oneof`. + // + // Example: defining two field groups. + // + // message PersonName { + // option (require).fields = "given_name | honorific_prefix & family_name"; + // + // string honorific_prefix = 1; + // string given_name = 2; + // string middle_name = 3; + // string family_name = 4; + // string honorific_suffix = 5; + // } + // + // In this example, at least `given_name` or a group of `honorific_prefix` + // and `family_name` fields must be set. + // + string fields = 1; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${message.type}` – the fully qualified name of the validated message. + // 2. `${require.fields}` – the specified field groups. + // + // The placeholders will be replaced at runtime when the error is constructed. + // + string error_msg = 2; +} diff --git a/docs/time_options.proto b/docs/time_options.proto new file mode 100644 index 0000000000..bc73fe15f5 --- /dev/null +++ b/docs/time_options.proto @@ -0,0 +1,106 @@ +/* + * 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. + */ + +syntax = "proto3"; + +// API Note on Packaging +// --------------------- +// We do not define the package for this file to allow shorter options for user-defined types. +// This allows to write: +// +// [(when).in = FUTURE]; +// +// instead of: +// +// [(spine.time.when).in = FUTURE]; +// + +import "spine/options.proto"; + +option (type_url_prefix) = "type.spine.io"; +option java_package = "io.spine.time.validation"; +option java_outer_classname = "TimeOptionsProto"; +option java_multiple_files = true; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FieldOptions { + + // See `TimeOption`. + TimeOption when = 73819; +} + +// Specifies that the field value is a point in time lying either in the future or in the past. +// +// Applicable to `google.protobuf.Timestamp` and types introduced in the `spine.time` package +// that describe time-related concepts. +// +// Repeated fields are supported. +// +// Example: Using the `(when)` option. +// +// message ScheduleMeeting { +// spine.time.ZonedDateTime start = 1 [(when).in = FUTURE]; +// } +// +message TimeOption { + + // The default error message. + option (default_message) = "The field `${parent.type}.${field.path}`" + " of the type `${field.type}` must be in the `${when.in}`." + " The encountered value: `${field.value}`."; + + // Defines a restriction for the timestamp. + Time in = 1; + + // Deprecated: please use `error_msg` instead. + string msg_format = 2 [deprecated = true]; + + // A user-defined error message. + // + // The specified message may include the following placeholders: + // + // 1. `${field.path}` – the field path. + // 2. `${field.value}` - the field value. + // 3. `${field.type}` – the fully qualified name of the field type. + // 4. `${parent.type}` – the fully qualified name of the validated message. + // 5. `${when.in}` – the specified timestamp restriction. It is either "past" or "future". + // + string error_msg = 3; +} + +// This enumeration defines restriction for date/time values. +enum Time { + + // The default value (if the time option is not set). + TIME_UNDEFINED = 0; + + // The value must be in the past. + PAST = 1; + + // The value must be in the future. + FUTURE = 2; +} From 94f69c0a79929eb39a69ceb2ecf4778dbd103ffc Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 18:15:55 +0000 Subject: [PATCH 17/45] Fix capitalization Also: * Update Kotlin requirement to 2.2.21+. --- docs/01-getting-started/index.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/01-getting-started/index.md b/docs/01-getting-started/index.md index a1d5708927..87b8f8af8e 100644 --- a/docs/01-getting-started/index.md +++ b/docs/01-getting-started/index.md @@ -5,7 +5,7 @@ and see validation in action in Java and Kotlin. If you are new to the library, read the short overview first: - Introduction → [Overview](../00-intro/index.md) -- Who this is for → [Target Audience](../00-intro/target-audience.md) +- Who this is for → [Target audience](../00-intro/target-audience.md) - Design principles → [Philosophy](../00-intro/philosophy.md) @@ -20,7 +20,7 @@ If you are new to the library, read the short overview first: - Java 17+ - Gradle (Kotlin DSL or Groovy) - Protobuf compiler (`protoc`) -- Optional: Kotlin 2.2.20+ for the Kotlin Protobuf DSL +- Optional: Kotlin 2.2.21+ for the Kotlin Protobuf DSL If your project already generates Java/Kotlin sources from `.proto` files, you’re 90% there. Spine Validation integrates into the build to generate and inject validation logic into @@ -38,10 +38,11 @@ the code produced by `protoc`. - Add declarative options like `(required)`, `(min)`, `(pattern)`, `(when).in = PAST`. 3) Build your project -- The Spine compiler plugin scans your model and generates validation code. +- The Validation plugin to Spine Compiler scans your model and generates validation code. 4) Use the generated API -- Validate on builder `build()` or call `validate()` explicitly. See [Your First Validated Model](first-model.md). +- Validate on builder `build()` or call `validate()` explicitly. + See [Your first validated model](first-model.md). ## What’s next From 449a789b4af410971477fce12bd24ea7880dd3d6 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 18:16:08 +0000 Subject: [PATCH 18/45] Backtick code in ToC --- docs/ToC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ToC.md b/docs/ToC.md index 3645eb7b9b..35a57c7e0f 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -45,8 +45,8 @@ ### 3.5. Temporal constraints - [Overview](03-built-in-options/temporal/index.md) -- [when](03-built-in-options/temporal/when.md) -- [Timestamp & duration](03-built-in-options/temporal/timestamp-duration.md) +- [`(when)`](03-built-in-options/temporal/when.md) +- [`Timestamp` & `Duration`](03-built-in-options/temporal/timestamp-duration.md) ### 3.6. Message-level constraints - [Overview](03-built-in-options/message/index.md) From 3671ae4c12d10bea092d6513a6adc2fb1eaff6a9 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 18:24:43 +0000 Subject: [PATCH 19/45] Put the "Collection constraints" section after "Temporal constraints" --- docs/ToC.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ToC.md b/docs/ToC.md index 35a57c7e0f..ccd0010228 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -29,24 +29,24 @@ - [length/size](03-built-in-options/fields/length-size.md) - [unique](03-built-in-options/fields/unique.md) -### 3.2. Collection constraints -- [Overview](03-built-in-options/collections/index.md) -- [non_empty](03-built-in-options/collections/non-empty.md) -- [distinct](03-built-in-options/collections/distinct.md) -- [Collection size](03-built-in-options/collections/collection-size.md) - -### 3.3. String constraints +### 3.2. String constraints - [Overview](03-built-in-options/strings/index.md) - [Advanced patterns](03-built-in-options/strings/pattern-advanced.md) -### 3.4. Numeric constraints +### 3.3. Numeric constraints - [Overview](03-built-in-options/numbers/index.md) - [Numeric bounds](03-built-in-options/numbers/numeric-bounds.md) -### 3.5. Temporal constraints +### 3.4. Temporal constraints - [Overview](03-built-in-options/temporal/index.md) - [`(when)`](03-built-in-options/temporal/when.md) -- [`Timestamp` & `Duration`](03-built-in-options/temporal/timestamp-duration.md) +- [Validating `Timestamp` & `Duration`](03-built-in-options/temporal/timestamp-duration.md) + +### 3.5. Collection constraints +- [Overview](03-built-in-options/collections/index.md) +- [`(required)`](03-built-in-options/collections/required.md) +- [`(distinct)`](03-built-in-options/collections/distinct.md) +- [Collection size](03-built-in-options/collections/collection-size.md) ### 3.6. Message-level constraints - [Overview](03-built-in-options/message/index.md) From 16a2736db12f90c72a3a8ebc6baa5bc4352f3c50 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 18:35:38 +0000 Subject: [PATCH 20/45] Describe Validation components --- docs/00-intro/index.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index 07aea6c51b..571934de27 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -8,6 +8,21 @@ declarative options and then automatically enforces these constraints at runtime The library is part of the Spine toolchain but can also be used independently in any Java/Kotlin backend that models data using Protocol Buffers. +## Components + +Spine Validation consists of three main parts: + +1. **Validation plugin to Spine Compiler** – analyzes your Protobuf definitions and + generates validation code for messages and builders. + +2. **Gradle plugin** – integrates with your build, configuring Spine Compiler to run + the Validation code generation as part of the Protobuf compilation process. + +3. **Runtime library** – provides validation APIs and error reporting mechanisms + for JVM projects (Java and Kotlin). + +Together, these components enable declarative, type-safe validation that is enforced +both at compile time and at runtime. ## Key capabilities @@ -26,7 +41,7 @@ with it. ### Generated validation code -The Validation Plugin for Spine Compiler processes your Protobuf model and generates: +The validation plugin to Spine Compiler processes your Protobuf model and generates: - validation code for messages and builders, - runtime checks, From 7fb87acebdc35c27b7970d4151ab1ab39dc31131 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 18:36:37 +0000 Subject: [PATCH 21/45] Remove "Configuration & tooling" section --- docs/ToC.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/ToC.md b/docs/ToC.md index ccd0010228..868955e1e5 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -61,19 +61,13 @@ - [Kotlin usage](04-using-validation/kotlin-usage.md) - [Framework integration](04-using-validation/framework-integration.md) -## 5. Configuration & tooling -- [Overview](05-configuration/index.md) -- [Compiler configuration](05-configuration/compiler.md) -- [Library modules](05-configuration/modules.md) -- [Debugging generated code](05-configuration/debugging.md) - -## 6. Extending validation +## 5. Extending validation - [Overview](06-extending/index.md) - [Architecture](06-extending/architecture.md) - [Custom validation options](06-extending/custom-options.md) - [Custom runtime validators](06-extending/custom-runtime-validators.md) -## 7. Recipes (cookbook) +## 6. Recipes (cookbook) - [Overview](07-recipes/index.md) - [Domain IDs](07-recipes/domain-ids.md) - [Common cases](07-recipes/common-cases.md) @@ -81,7 +75,7 @@ - [Cross-field logic](07-recipes/cross-field-logic.md) - [API validation](07-recipes/api-validation.md) -## 8. Reference +## 7. Reference - [Reference overview](08-reference/index.md) - [List of validation options](08-reference/options.md) - [Java/Kotlin API index](08-reference/api.md) From 51c50362d9ba90218a23ea78544f70ce0674b057 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 19:45:52 +0000 Subject: [PATCH 22/45] Rename "Installation" to "Adding Validation to your build" --- docs/01-getting-started/index.md | 7 +++---- docs/ToC.md | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/01-getting-started/index.md b/docs/01-getting-started/index.md index 87b8f8af8e..1fc0196a61 100644 --- a/docs/01-getting-started/index.md +++ b/docs/01-getting-started/index.md @@ -29,9 +29,8 @@ the code produced by `protoc`. ## Quick path -1) Install the library and compiler integration -- Follow [Installation](installation.md) to add the necessary repositories and build configuration. -- If you use a custom Protobuf setup, see [Compiler Configuration](../05-configuration/compiler.md). +1) Add Spine Validation to your Gradle build +- Follow the [instructions](adding-to-build.md). 2) Define constraints in your `.proto` files - Import `spine/options.proto` (and `spine/time_options.proto` for temporal constraints). @@ -47,6 +46,6 @@ the code produced by `protoc`. ## What’s next -- [Installation](installation.md) +- [Adding Validation to your build](adding-to-build.md) - [Your First Validated Model](first-model.md) - [Validation Workflow](workflow.md) diff --git a/docs/ToC.md b/docs/ToC.md index 868955e1e5..c3640baf0d 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -7,7 +7,7 @@ ## 1. Getting started - [Getting started](01-getting-started/index.md) -- [Installation](01-getting-started/installation.md) +- [Adding Validation to your build](01-getting-started/installation.md) - [Your first validated model](01-getting-started/first-model.md) - [Validation workflow](01-getting-started/workflow.md) From d3d0b60b2a54a1b3e0d0c138606d491f10a80000 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 20:42:02 +0000 Subject: [PATCH 23/45] Describe adding Validation to a Gradle build --- docs/01-getting-started/adding-to-build.md | 58 ++++++++++++++++++++++ docs/ToC.md | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 docs/01-getting-started/adding-to-build.md diff --git a/docs/01-getting-started/adding-to-build.md b/docs/01-getting-started/adding-to-build.md new file mode 100644 index 0000000000..3f2ceca970 --- /dev/null +++ b/docs/01-getting-started/adding-to-build.md @@ -0,0 +1,58 @@ +# Adding Validation to a Gradle build + +Spine Validation can be added to a JVM project in two different ways. +Choose the setup that matches your project: + +- Standalone use in any Protobuf-based project. +- As part of a Spine-based project that already uses the CoreJvm toolchain. + +Both modes integrate the Validation compiler into the build and add the runtime library. + + +## Mode 1: Standalone via Validation Gradle Plugin + +Use this mode if you want to run Validation without the rest of Spine. + +1) Add the Validation plugin to the build. + +```kotlin +plugins { + id("io.spine.validation") version "" +} +``` + +2) Make sure your repositories include Spine artifacts (the same repositories you use + for other Spine tools and libraries). + +3) Optional: control Validation generation explicitly. + +```kotlin +spine { + validation { + enabled.set(true) // `true` by default + } +} +``` + +The plugin wires Validation into Spine Compiler, adds the Validation Java codegen bundle, +and brings in the JVM runtime dependency automatically. + + +## Mode 2: Spine-based project via CoreJvm Gradle Plugin + +If your project is base on the Spine CoreJvm library, apply the CoreJvm Gradle plugin instead of +adding Validation directly. CoreJvm brings in the Validation Gradle plugin for you. + +```kotlin +plugins { + id("io.spine.core-jvm") version "" +} +``` + +Validation is available right away, and you can configure it using the same `validation` +extension if needed. + + +## Next step + +Continue with [Your first validated model](first-model.md). diff --git a/docs/ToC.md b/docs/ToC.md index c3640baf0d..088f3a9935 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -7,7 +7,7 @@ ## 1. Getting started - [Getting started](01-getting-started/index.md) -- [Adding Validation to your build](01-getting-started/installation.md) +- [Adding Validation to a Gradle build](01-getting-started/adding-to-build.md) - [Your first validated model](01-getting-started/first-model.md) - [Validation workflow](01-getting-started/workflow.md) From 5383ae2c17e79507598ca0136cc2181e2784369c Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 21:21:31 +0000 Subject: [PATCH 24/45] Improve the example type name in the `ValidationError` docs --- .../src/main/proto/spine/validation/validation_error.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jvm-runtime/src/main/proto/spine/validation/validation_error.proto b/jvm-runtime/src/main/proto/spine/validation/validation_error.proto index 093d38e0a5..c9562d093e 100644 --- a/jvm-runtime/src/main/proto/spine/validation/validation_error.proto +++ b/jvm-runtime/src/main/proto/spine/validation/validation_error.proto @@ -72,7 +72,7 @@ message ConstraintViolation { // Example: // // ``` - // message Student { + // message AddressBook { // Contacts contacts = 1 [(validate) = true]; // } // @@ -85,8 +85,8 @@ message ConstraintViolation { // } // ``` // - // When the `Student` message is validated and the `value` field in the nested `Email` - // message is invalid, this property will contain the `Student` type name, not `Email`. + // When the `AddressBook` message is validated and the `value` field in the nested `Email` + // message is invalid, this property will contain the `AddressBook` type name, not `Email`. // This is so because this type is an entry point to the nested validation. // string type_name = 7; From 0bb61960e4d3f4137cadf656e0845ee0d7249cd2 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 21:21:57 +0000 Subject: [PATCH 25/45] Remove the leading zero in the Introduction section title --- docs/ToC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ToC.md b/docs/ToC.md index 088f3a9935..ff30ffdb1d 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -1,6 +1,6 @@ # Spine Validation — Table of contents -## 0. Introduction +## Introduction - [Overview](00-intro/index.md) - [Target audience](00-intro/target-audience.md) - [Philosophy](00-intro/philosophy.md) From f99685afcc8494a98eab77592a61014cad43349a Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 22 Jan 2026 21:22:28 +0000 Subject: [PATCH 26/45] Add wording for the `first-model.md` --- docs/01-getting-started/first-model.md | 104 +++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 docs/01-getting-started/first-model.md diff --git a/docs/01-getting-started/first-model.md b/docs/01-getting-started/first-model.md new file mode 100644 index 0000000000..917490a27d --- /dev/null +++ b/docs/01-getting-started/first-model.md @@ -0,0 +1,104 @@ +# Your first validated model + +This guide shows how to define a Protobuf model with validation rules and how those +rules are enforced in the generated JVM code. + +The validation options come from two files: + +- `spine/options.proto` for general constraints like `(required)`, `(min)`, `(max)`, + `(pattern)`, `(distinct)`, and `(validate)`. +- `spine/time_options.proto` for time constraints such as `(when).in = PAST | FUTURE`. + + +## 1) Define a validated message + +Create a `.proto` file and import the validation options you need: + +```protobuf +syntax = "proto3"; + +package example; + +import "google/protobuf/timestamp.proto"; +import "spine/options.proto"; +import "spine/time_options.proto"; + +// Provides bank card information with validation rules. +// +// The digits of the card are simplified for the sake of the example. +// +message BankCard { + + // Must be present and match a simple card pattern. + string digits = 1 [ + (required) = true, + (pattern).regex = "^\\d{4}(?: \\d{4}){3}" + ]; + + // Must be present and contain at least 4 Latin letters or spaces. + string owner = 2 [ + (required) = true, + (pattern).regex = "^[A-Z](?:[A-Za-z ]{2,}[a-z])$" + ]; + + // Must be in the past. + google.protobuf.Timestamp issued_at = 3 [ + (when).in = PAST, + (required) = true, + (validate) = true + ]; + + // All tags must be unique. Tags are optional. + repeated string tags = 4 [ + (distinct) = true + ]; +} +``` + +Notes on the options used: + +- `(required)` ensures the field is set and not default or empty. +- `(pattern).regex` validates string contents with a regular expression. +- `(when).in` restricts time values to `PAST` or `FUTURE`. +- `(distinct)` enforces uniqueness in `repeated` and `map` fields. + + +## 2) Build the project + +Run your Gradle build as usual. The Validation Gradle plugin integrates with Spine Compiler +and injects validation checks into the generated Java code. + + +## 3) Use the generated validation + +Validation runs on `build()` and can be triggered manually with `validate()`. + +```java +var card = BankCard.newBuilder() + .setDigits("invalid") + .setOwner("Al") + .build(); // Throws `ValidationException`. +``` + +```kotlin +val card = bankCard { + digits = "invalid" + owner = "Al" +} // Throws `ValidationException`. +``` + +To validate without throwing, use `validate()` on a built message: + +```java +var card = BankCard.newBuilder() + .setDigits("invalid") + .buildPartial(); + +var error = card.validate(); +error.ifPresent(err -> System.out.println(err.getMessage())); +``` + + +## What’s next + +Continue with [Validation Workflow](workflow.md). From 9f5204af960512c2ead47ee81adab11ebbf3c6cb Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 16:16:45 +0000 Subject: [PATCH 27/45] Remove mentioning of temporal constraints They will be described separately and later. Probably, it's going to be a part of the documentation of the Spine Time library. --- docs/00-intro/index.md | 4 +--- docs/00-intro/philosophy.md | 1 - docs/00-intro/target-audience.md | 2 +- docs/01-getting-started/first-model.md | 20 +++----------------- docs/01-getting-started/index.md | 4 ++-- docs/ToC.md | 10 ++-------- 6 files changed, 9 insertions(+), 32 deletions(-) diff --git a/docs/00-intro/index.md b/docs/00-intro/index.md index 571934de27..2c9dfa3ce5 100644 --- a/docs/00-intro/index.md +++ b/docs/00-intro/index.md @@ -33,7 +33,6 @@ Validation rules are expressed as Protobuf options such as: - `(required)` - `(min)` / `(max)` - `(pattern)` -- `(when).in = PAST | FUTURE` - cross-field rules and message-level constraints This keeps validation close to the data model and ensures it evolves together @@ -65,8 +64,7 @@ Beyond simple “required/min/max”, the library includes: - collection rules (`(distinct)`, `(required)`), - nested and cross-field validation, -- advanced string formats (using regex), -- temporal constraints (`PAST`, `FUTURE`). +- advanced string formats (using regex). ### Extensible architecture diff --git a/docs/00-intro/philosophy.md b/docs/00-intro/philosophy.md index 0efdab7c6d..ea6528686e 100644 --- a/docs/00-intro/philosophy.md +++ b/docs/00-intro/philosophy.md @@ -94,7 +94,6 @@ design contexts. Instead of focusing only on primitive checks (`min`/`max`, `required`), the library embraces **domain semantics**, such as: - * temporal rules (past, future), * cross-field logic, * nested validation, * constraints on identity fields, diff --git a/docs/00-intro/target-audience.md b/docs/00-intro/target-audience.md index fedfd52abe..caa8e06261 100644 --- a/docs/00-intro/target-audience.md +++ b/docs/00-intro/target-audience.md @@ -39,7 +39,7 @@ validate: For Spine users, this library provides: - consistent validation semantics across the entire model, -- rich temporal and domain-focused constraints, +- rich domain-focused constraints, - cross-field and message-level validations. ## 3. Framework and platform integrators diff --git a/docs/01-getting-started/first-model.md b/docs/01-getting-started/first-model.md index 917490a27d..83ccf579cd 100644 --- a/docs/01-getting-started/first-model.md +++ b/docs/01-getting-started/first-model.md @@ -3,11 +3,8 @@ This guide shows how to define a Protobuf model with validation rules and how those rules are enforced in the generated JVM code. -The validation options come from two files: - -- `spine/options.proto` for general constraints like `(required)`, `(min)`, `(max)`, - `(pattern)`, `(distinct)`, and `(validate)`. -- `spine/time_options.proto` for time constraints such as `(when).in = PAST | FUTURE`. +The validation options come from `spine/options.proto` and include constraints like +`(required)`, `(min)`, `(max)`, `(pattern)`, `(distinct)`, and `(validate)`. ## 1) Define a validated message @@ -19,16 +16,13 @@ syntax = "proto3"; package example; -import "google/protobuf/timestamp.proto"; import "spine/options.proto"; -import "spine/time_options.proto"; // Provides bank card information with validation rules. // // The digits of the card are simplified for the sake of the example. // message BankCard { - // Must be present and match a simple card pattern. string digits = 1 [ (required) = true, @@ -41,15 +35,8 @@ message BankCard { (pattern).regex = "^[A-Z](?:[A-Za-z ]{2,}[a-z])$" ]; - // Must be in the past. - google.protobuf.Timestamp issued_at = 3 [ - (when).in = PAST, - (required) = true, - (validate) = true - ]; - // All tags must be unique. Tags are optional. - repeated string tags = 4 [ + repeated string tags = 3 [ (distinct) = true ]; } @@ -59,7 +46,6 @@ Notes on the options used: - `(required)` ensures the field is set and not default or empty. - `(pattern).regex` validates string contents with a regular expression. -- `(when).in` restricts time values to `PAST` or `FUTURE`. - `(distinct)` enforces uniqueness in `repeated` and `map` fields. diff --git a/docs/01-getting-started/index.md b/docs/01-getting-started/index.md index 1fc0196a61..8e9279f293 100644 --- a/docs/01-getting-started/index.md +++ b/docs/01-getting-started/index.md @@ -33,8 +33,8 @@ the code produced by `protoc`. - Follow the [instructions](adding-to-build.md). 2) Define constraints in your `.proto` files -- Import `spine/options.proto` (and `spine/time_options.proto` for temporal constraints). -- Add declarative options like `(required)`, `(min)`, `(pattern)`, `(when).in = PAST`. +- Import `spine/options.proto`. +- Add declarative options like `(required)`, `(min)`, `(pattern)`. 3) Build your project - The Validation plugin to Spine Compiler scans your model and generates validation code. diff --git a/docs/ToC.md b/docs/ToC.md index ff30ffdb1d..bca994db07 100644 --- a/docs/ToC.md +++ b/docs/ToC.md @@ -37,18 +37,13 @@ - [Overview](03-built-in-options/numbers/index.md) - [Numeric bounds](03-built-in-options/numbers/numeric-bounds.md) -### 3.4. Temporal constraints -- [Overview](03-built-in-options/temporal/index.md) -- [`(when)`](03-built-in-options/temporal/when.md) -- [Validating `Timestamp` & `Duration`](03-built-in-options/temporal/timestamp-duration.md) - -### 3.5. Collection constraints +### 3.4. Collection constraints - [Overview](03-built-in-options/collections/index.md) - [`(required)`](03-built-in-options/collections/required.md) - [`(distinct)`](03-built-in-options/collections/distinct.md) - [Collection size](03-built-in-options/collections/collection-size.md) -### 3.6. Message-level constraints +### 3.5. Message-level constraints - [Overview](03-built-in-options/message/index.md) - [required_for](03-built-in-options/message/required-for.md) - [Nested validation](03-built-in-options/message/nested-validation.md) @@ -71,7 +66,6 @@ - [Overview](07-recipes/index.md) - [Domain IDs](07-recipes/domain-ids.md) - [Common cases](07-recipes/common-cases.md) -- [Temporal logic](07-recipes/temporal-logic.md) - [Cross-field logic](07-recipes/cross-field-logic.md) - [API validation](07-recipes/api-validation.md) From e409aa57c11fbe711e6805764cd4422af072af94 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:21:46 +0000 Subject: [PATCH 28/45] Add `code/first-model` subproject --- docs/code/first-model/build.gradle.kts | 73 ++++++++++++++++ docs/code/first-model/buildSrc | 1 + docs/code/first-model/gradle.properties | 1 + docs/code/first-model/gradlew | 1 + docs/code/first-model/gradlew.bat | 1 + docs/code/first-model/settings.gradle.kts | 41 +++++++++ .../src/main/proto/example/bank_card.proto | 33 +++++++ .../src/test/kotlin/example/BankCardTest.kt | 85 +++++++++++++++++++ 8 files changed, 236 insertions(+) create mode 100644 docs/code/first-model/build.gradle.kts create mode 120000 docs/code/first-model/buildSrc create mode 120000 docs/code/first-model/gradle.properties create mode 120000 docs/code/first-model/gradlew create mode 120000 docs/code/first-model/gradlew.bat create mode 100644 docs/code/first-model/settings.gradle.kts create mode 100644 docs/code/first-model/src/main/proto/example/bank_card.proto create mode 100644 docs/code/first-model/src/test/kotlin/example/BankCardTest.kt diff --git a/docs/code/first-model/build.gradle.kts b/docs/code/first-model/build.gradle.kts new file mode 100644 index 0000000000..a35d1cc1db --- /dev/null +++ b/docs/code/first-model/build.gradle.kts @@ -0,0 +1,73 @@ +import io.spine.dependency.lib.Guava +import io.spine.dependency.local.Base +import io.spine.gradle.repo.standardToSpineSdk + +/* + * 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. + */ + +buildscript { + standardSpineSdkRepositories() + dependencies { + classpath(io.spine.dependency.local.Compiler.pluginLib) + } +} + +plugins { + `java-library` + kotlin("jvm") + id("com.google.protobuf") + id("io.spine.compiler").version("2.0.0-SNAPSHOT.038") + id("io.spine.validation") //.version("2.0.0-SNAPSHOT.394") +} + +group = "io.spine.example" +version = "0.1.0" + +repositories { + mavenLocal() + standardToSpineSdk() +} + +//configurations.all { +// resolutionStrategy.dependencySubstitution { +// substitute(module("io.spine:spine-validation-jvm-runtime")) +// .using(project(":validation:jvm-runtime")) +// } +//} + +dependencies { + implementation(Guava.lib) + implementation(Base.lib) + testImplementation(kotlin("test")) +} + +kotlin { + jvmToolchain(17) +} + +tasks.test { + useJUnitPlatform() +} diff --git a/docs/code/first-model/buildSrc b/docs/code/first-model/buildSrc new file mode 120000 index 0000000000..f59909572b --- /dev/null +++ b/docs/code/first-model/buildSrc @@ -0,0 +1 @@ +/Users/sanders/Projects/Spine/validation/buildSrc \ No newline at end of file diff --git a/docs/code/first-model/gradle.properties b/docs/code/first-model/gradle.properties new file mode 120000 index 0000000000..e98d4551a2 --- /dev/null +++ b/docs/code/first-model/gradle.properties @@ -0,0 +1 @@ +/Users/sanders/Projects/Spine/validation/gradle.properties \ No newline at end of file diff --git a/docs/code/first-model/gradlew b/docs/code/first-model/gradlew new file mode 120000 index 0000000000..b1781d235f --- /dev/null +++ b/docs/code/first-model/gradlew @@ -0,0 +1 @@ +/Users/sanders/Projects/Spine/validation/gradlew \ No newline at end of file diff --git a/docs/code/first-model/gradlew.bat b/docs/code/first-model/gradlew.bat new file mode 120000 index 0000000000..1ed85a1435 --- /dev/null +++ b/docs/code/first-model/gradlew.bat @@ -0,0 +1 @@ +/Users/sanders/Projects/Spine/validation/gradlew.bat \ No newline at end of file diff --git a/docs/code/first-model/settings.gradle.kts b/docs/code/first-model/settings.gradle.kts new file mode 100644 index 0000000000..9a6760f57a --- /dev/null +++ b/docs/code/first-model/settings.gradle.kts @@ -0,0 +1,41 @@ +/* + * 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. + */ + +rootProject.name = "first-model" + +pluginManagement { + repositories { + gradlePluginPortal() + mavenLocal() + } +} + +includeBuild("../../../") { +// dependencySubstitution { +// substitute(module("io.spine:spine-validation-jvm-runtime")) +// .using(project(":jvm-runtime")) +// } +} diff --git a/docs/code/first-model/src/main/proto/example/bank_card.proto b/docs/code/first-model/src/main/proto/example/bank_card.proto new file mode 100644 index 0000000000..7c4d2a1327 --- /dev/null +++ b/docs/code/first-model/src/main/proto/example/bank_card.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package example; + +import "spine/options.proto"; + +option (type_url_prefix) = "type.spine.io"; +option java_package = "io.spine.example"; +option java_multiple_files = true; + +// Provides bank card information with validation rules. +// +// The digits of the card are simplified for the sake of the example. +// +message BankCard { + + // Must be present and match a simple card pattern. + string digits = 1 [ + (required) = true, + (pattern).regex = "^\\d{4}(?: \\d{4}){3}" + ]; + + // Must be present and contain at least 4 Latin letters or spaces. + string owner = 2 [ + (required) = true, + (pattern).regex = "^[A-Z](?:[A-Za-z ]{2,}[A-Za-z])$" + ]; + + // All tags must be unique. Tags are optional. + repeated string tags = 3 [ + (distinct) = true + ]; +} diff --git a/docs/code/first-model/src/test/kotlin/example/BankCardTest.kt b/docs/code/first-model/src/test/kotlin/example/BankCardTest.kt new file mode 100644 index 0000000000..45481914da --- /dev/null +++ b/docs/code/first-model/src/test/kotlin/example/BankCardTest.kt @@ -0,0 +1,85 @@ +/* + * 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.example + +import io.spine.validation.ValidationException +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.Assertions.assertDoesNotThrow + +@DisplayName("`BankCard` should") +class BankCardTest { + + @Test + @DisplayName("throw `ValidationException` if digits are invalid") + fun invalidDigits() { + assertThrows { + BankCard.newBuilder() + .setDigits("invalid") + .setOwner("ALEX SMITH") + .build() + } + } + + @Test + @DisplayName("throw `ValidationException` if owner is invalid") + fun invalidOwner() { + assertThrows { + BankCard.newBuilder() + .setDigits("1234 5678 1234 5678") + .setOwner("Al") + .build() + } + } + + @Test + @DisplayName("throw `ValidationException` if tags are not distinct") + fun duplicateTags() { + assertThrows { + BankCard.newBuilder() + .setDigits("1234 5678 1234 5678") + .setOwner("ALEX SMITH") + .addTags("personal") + .addTags("personal") + .build() + } + } + + @Test + @DisplayName("be built if all fields are valid") + fun validCard() { + assertDoesNotThrow { + BankCard.newBuilder() + .setDigits("1234 5678 1234 5678") + .setOwner("ALEX SMITH") + .addTags("personal") + .addTags("travel") + .build() + } + } +} From c681d66c002904cf80460500d45f7c9e969ea0da Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:29:26 +0000 Subject: [PATCH 29/45] Make JVM runtime expose `Base.lib` at the `api` level --- jvm-runtime/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jvm-runtime/build.gradle.kts b/jvm-runtime/build.gradle.kts index 8be6a6b4ec..2614f4bd11 100644 --- a/jvm-runtime/build.gradle.kts +++ b/jvm-runtime/build.gradle.kts @@ -68,7 +68,7 @@ dependencies { annotationProcessor(AutoService.processor) compileOnly(AutoService.annotations) - implementation(Base.lib) + api(Base.lib) testImplementation(TestLib.lib) } From 9e9772712c448ce56d8bd54ac08e055047fa47d4 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:29:54 +0000 Subject: [PATCH 30/45] Remove redundant dependencies --- docs/code/first-model/build.gradle.kts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/docs/code/first-model/build.gradle.kts b/docs/code/first-model/build.gradle.kts index a35d1cc1db..3d3aecfc9d 100644 --- a/docs/code/first-model/build.gradle.kts +++ b/docs/code/first-model/build.gradle.kts @@ -40,27 +40,18 @@ plugins { kotlin("jvm") id("com.google.protobuf") id("io.spine.compiler").version("2.0.0-SNAPSHOT.038") - id("io.spine.validation") //.version("2.0.0-SNAPSHOT.394") + id("io.spine.validation") } -group = "io.spine.example" -version = "0.1.0" +group = "io.spine.docs" +version = "2.0.0" repositories { mavenLocal() standardToSpineSdk() } -//configurations.all { -// resolutionStrategy.dependencySubstitution { -// substitute(module("io.spine:spine-validation-jvm-runtime")) -// .using(project(":validation:jvm-runtime")) -// } -//} - dependencies { - implementation(Guava.lib) - implementation(Base.lib) testImplementation(kotlin("test")) } From 591846222f9b836d679b23828114d246190c2371 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:48:43 +0000 Subject: [PATCH 31/45] Remove commented out code --- docs/code/first-model/settings.gradle.kts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/code/first-model/settings.gradle.kts b/docs/code/first-model/settings.gradle.kts index 9a6760f57a..4379439985 100644 --- a/docs/code/first-model/settings.gradle.kts +++ b/docs/code/first-model/settings.gradle.kts @@ -33,9 +33,4 @@ pluginManagement { } } -includeBuild("../../../") { -// dependencySubstitution { -// substitute(module("io.spine:spine-validation-jvm-runtime")) -// .using(project(":jvm-runtime")) -// } -} +includeBuild("../../../") From fe8e72289d16609dec591c68d117a0a91937b904 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:51:36 +0000 Subject: [PATCH 32/45] Improve package name for the `BankCard` example --- docs/01-getting-started/first-model.md | 5 ++++- .../validation/docs/first_model}/bank_card.proto | 4 ++-- .../validation/docs/first_model}/BankCardTest.kt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) rename docs/code/first-model/src/main/proto/{example => spine/validation/docs/first_model}/bank_card.proto (87%) rename docs/code/first-model/src/test/kotlin/{example => spine/validation/docs/first_model}/BankCardTest.kt (98%) diff --git a/docs/01-getting-started/first-model.md b/docs/01-getting-started/first-model.md index 83ccf579cd..15e820cc14 100644 --- a/docs/01-getting-started/first-model.md +++ b/docs/01-getting-started/first-model.md @@ -14,10 +14,13 @@ Create a `.proto` file and import the validation options you need: ```protobuf syntax = "proto3"; -package example; +package spine.validation.docs.first_model; import "spine/options.proto"; +option java_package = "io.spine.validation.docs.firstmodel"; +option java_multiple_files = true; + // Provides bank card information with validation rules. // // The digits of the card are simplified for the sake of the example. diff --git a/docs/code/first-model/src/main/proto/example/bank_card.proto b/docs/code/first-model/src/main/proto/spine/validation/docs/first_model/bank_card.proto similarity index 87% rename from docs/code/first-model/src/main/proto/example/bank_card.proto rename to docs/code/first-model/src/main/proto/spine/validation/docs/first_model/bank_card.proto index 7c4d2a1327..ce5bc4d4c2 100644 --- a/docs/code/first-model/src/main/proto/example/bank_card.proto +++ b/docs/code/first-model/src/main/proto/spine/validation/docs/first_model/bank_card.proto @@ -1,11 +1,11 @@ syntax = "proto3"; -package example; +package spine.validation.docs.first_model; import "spine/options.proto"; option (type_url_prefix) = "type.spine.io"; -option java_package = "io.spine.example"; +option java_package = "io.spine.validation.docs.firstmodel"; option java_multiple_files = true; // Provides bank card information with validation rules. diff --git a/docs/code/first-model/src/test/kotlin/example/BankCardTest.kt b/docs/code/first-model/src/test/kotlin/spine/validation/docs/first_model/BankCardTest.kt similarity index 98% rename from docs/code/first-model/src/test/kotlin/example/BankCardTest.kt rename to docs/code/first-model/src/test/kotlin/spine/validation/docs/first_model/BankCardTest.kt index 45481914da..c79c0587ae 100644 --- a/docs/code/first-model/src/test/kotlin/example/BankCardTest.kt +++ b/docs/code/first-model/src/test/kotlin/spine/validation/docs/first_model/BankCardTest.kt @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.spine.example +package io.spine.validation.docs.firstmodel import io.spine.validation.ValidationException import org.junit.jupiter.api.DisplayName From fe75e363aca2fd109d6b627d7e77a43be8999d5d Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:52:47 +0000 Subject: [PATCH 33/45] Fix Kotlin package path --- .../{ => io}/spine/validation/docs/first_model/BankCardTest.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/code/first-model/src/test/kotlin/{ => io}/spine/validation/docs/first_model/BankCardTest.kt (100%) diff --git a/docs/code/first-model/src/test/kotlin/spine/validation/docs/first_model/BankCardTest.kt b/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/first_model/BankCardTest.kt similarity index 100% rename from docs/code/first-model/src/test/kotlin/spine/validation/docs/first_model/BankCardTest.kt rename to docs/code/first-model/src/test/kotlin/io/spine/validation/docs/first_model/BankCardTest.kt From 5316c03d02e75dc13bdc446f47444a53c4d70490 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 18:55:49 +0000 Subject: [PATCH 34/45] Add Java test for `BankCard` --- .../docs/firstmodel/BankCardTest.java | 86 +++++++++++++++++++ .../BankCardTestKt.kt} | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 docs/code/first-model/src/test/java/io/spine/validation/docs/firstmodel/BankCardTest.java rename docs/code/first-model/src/test/kotlin/io/spine/validation/docs/{first_model/BankCardTest.kt => firstmodel/BankCardTestKt.kt} (99%) diff --git a/docs/code/first-model/src/test/java/io/spine/validation/docs/firstmodel/BankCardTest.java b/docs/code/first-model/src/test/java/io/spine/validation/docs/firstmodel/BankCardTest.java new file mode 100644 index 0000000000..a31d505097 --- /dev/null +++ b/docs/code/first-model/src/test/java/io/spine/validation/docs/firstmodel/BankCardTest.java @@ -0,0 +1,86 @@ +/* + * 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.docs.firstmodel; + +import io.spine.validation.ValidationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("`BankCard` should") +class BankCardTest { + + @Test + @DisplayName("throw `ValidationException` if digits are invalid") + void invalidDigits() { + assertThrows(ValidationException.class, () -> + BankCard.newBuilder() + .setDigits("invalid") + .setOwner("ALEX SMITH") + .build() + ); + } + + @Test + @DisplayName("throw `ValidationException` if owner is invalid") + void invalidOwner() { + assertThrows(ValidationException.class, () -> + BankCard.newBuilder() + .setDigits("1234 5678 1234 5678") + .setOwner("Al") + .build() + ); + } + + @Test + @DisplayName("throw `ValidationException` if tags are not distinct") + void duplicateTags() { + assertThrows(ValidationException.class, () -> + BankCard.newBuilder() + .setDigits("1234 5678 1234 5678") + .setOwner("ALEX SMITH") + .addTags("personal") + .addTags("personal") + .build() + ); + } + + @Test + @DisplayName("be built if all fields are valid") + void validCard() { + assertDoesNotThrow(() -> + BankCard.newBuilder() + .setDigits("1234 5678 1234 5678") + .setOwner("ALEX SMITH") + .addTags("personal") + .addTags("travel") + .build() + ); + } +} diff --git a/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/first_model/BankCardTest.kt b/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardTestKt.kt similarity index 99% rename from docs/code/first-model/src/test/kotlin/io/spine/validation/docs/first_model/BankCardTest.kt rename to docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardTestKt.kt index c79c0587ae..53813395bf 100644 --- a/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/first_model/BankCardTest.kt +++ b/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardTestKt.kt @@ -33,7 +33,7 @@ import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.Assertions.assertDoesNotThrow @DisplayName("`BankCard` should") -class BankCardTest { +class BankCardTestKt { @Test @DisplayName("throw `ValidationException` if digits are invalid") From 8ee49bdb59a495b56cddd062505d379e3daba73e Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 19:04:16 +0000 Subject: [PATCH 35/45] Improve Kotlin test name --- .../{BankCardTestKt.kt => BankCardKtTest.kt} | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) rename docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/{BankCardTestKt.kt => BankCardKtTest.kt} (73%) diff --git a/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardTestKt.kt b/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardKtTest.kt similarity index 73% rename from docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardTestKt.kt rename to docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardKtTest.kt index 53813395bf..8587787d45 100644 --- a/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardTestKt.kt +++ b/docs/code/first-model/src/test/kotlin/io/spine/validation/docs/firstmodel/BankCardKtTest.kt @@ -32,17 +32,17 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.Assertions.assertDoesNotThrow -@DisplayName("`BankCard` should") -class BankCardTestKt { +@DisplayName("`BankCard`in Kotlin should") +class BankCardKtTest { @Test @DisplayName("throw `ValidationException` if digits are invalid") fun invalidDigits() { assertThrows { - BankCard.newBuilder() - .setDigits("invalid") - .setOwner("ALEX SMITH") - .build() + bankCard { + digits = "invalid" + owner = "ALEX SMITH" + } } } @@ -50,10 +50,10 @@ class BankCardTestKt { @DisplayName("throw `ValidationException` if owner is invalid") fun invalidOwner() { assertThrows { - BankCard.newBuilder() - .setDigits("1234 5678 1234 5678") - .setOwner("Al") - .build() + bankCard { + digits = "1234 5678 1234 5678" + owner = "Al" + } } } @@ -61,12 +61,12 @@ class BankCardTestKt { @DisplayName("throw `ValidationException` if tags are not distinct") fun duplicateTags() { assertThrows { - BankCard.newBuilder() - .setDigits("1234 5678 1234 5678") - .setOwner("ALEX SMITH") - .addTags("personal") - .addTags("personal") - .build() + bankCard { + digits = "1234 5678 1234 5678" + owner = "ALEX SMITH" + tags.add("personal") + tags.add("personal") + } } } @@ -74,12 +74,12 @@ class BankCardTestKt { @DisplayName("be built if all fields are valid") fun validCard() { assertDoesNotThrow { - BankCard.newBuilder() - .setDigits("1234 5678 1234 5678") - .setOwner("ALEX SMITH") - .addTags("personal") - .addTags("travel") - .build() + bankCard { + digits = "1234 5678 1234 5678" + owner = "ALEX SMITH" + tags.add("personal") + tags.add("travel") + } } } } From 652a545cd02e7b43e93478d628cf1d888de169de Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 19:29:13 +0000 Subject: [PATCH 36/45] Bump Gradle -> `9.3.0` --- gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37058 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 NNswbwE82Y!{{uvZmpcFe delta 36446 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 JWd7DQ`9Hc?h(rJY diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2b54..19a6bdeb84 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From c1dde859d957ffb3e008fb76cda43f6fbb481001 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Fri, 23 Jan 2026 19:29:21 +0000 Subject: [PATCH 37/45] Update dependency reports --- dependencies.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/dependencies.md b/dependencies.md index 6ec36ba3f3..7eec9c7620 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1139,7 +1139,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -1731,7 +1731,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:51 WET 2026** using +This report was generated on **Fri Jan 23 18:24:45 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). @@ -2807,7 +2807,7 @@ This report was generated on **Tue Jan 20 19:55:51 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -3901,7 +3901,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -3971,7 +3971,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:51 WET 2026** using +This report was generated on **Fri Jan 23 18:24:45 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). @@ -4811,7 +4811,7 @@ This report was generated on **Tue Jan 20 19:55:51 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -5747,7 +5747,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -6345,7 +6345,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:51 WET 2026** using +This report was generated on **Fri Jan 23 18:24:45 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). @@ -6863,7 +6863,7 @@ This report was generated on **Tue Jan 20 19:55:51 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -7554,7 +7554,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -8183,7 +8183,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -8855,7 +8855,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -9613,7 +9613,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:52 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -9890,7 +9890,7 @@ This report was generated on **Tue Jan 20 19:55:52 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:51 WET 2026** using +This report was generated on **Fri Jan 23 18:24:46 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). @@ -10248,6 +10248,6 @@ This report was generated on **Tue Jan 20 19:55:51 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jan 20 19:55:51 WET 2026** using +This report was generated on **Fri Jan 23 18:24:45 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 From de11e7e7305a4157774e67f5c6d78b382c1a1cd3 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 29 Jan 2026 18:20:42 +0000 Subject: [PATCH 38/45] Add `spine-compiler` plugin dependency ... so that we don't have to use the version in the `docs/code` modules explicitly. --- buildSrc/src/main/kotlin/BuildExtensions.kt | 3 +++ docs/code/first-model/build.gradle.kts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildExtensions.kt b/buildSrc/src/main/kotlin/BuildExtensions.kt index f7a11e3e04..1c28990c57 100644 --- a/buildSrc/src/main/kotlin/BuildExtensions.kt +++ b/buildSrc/src/main/kotlin/BuildExtensions.kt @@ -152,6 +152,9 @@ val PluginDependenciesSpec.protobuf: PluginDependencySpec val PluginDependenciesSpec.prototap: PluginDependencySpec get() = id(ProtoTap.gradlePluginId).version(ProtoTap.version) +val PluginDependenciesSpec.`spine-compiler`: PluginDependencySpec + get() = id(Compiler.pluginId).version(Compiler.version) + val PluginDependenciesSpec.`gradle-doctor`: PluginDependencySpec get() = id(GradleDoctor.pluginId).version(GradleDoctor.version) diff --git a/docs/code/first-model/build.gradle.kts b/docs/code/first-model/build.gradle.kts index 3d3aecfc9d..63d5ae9c96 100644 --- a/docs/code/first-model/build.gradle.kts +++ b/docs/code/first-model/build.gradle.kts @@ -39,7 +39,7 @@ plugins { `java-library` kotlin("jvm") id("com.google.protobuf") - id("io.spine.compiler").version("2.0.0-SNAPSHOT.038") + `spine-compiler` id("io.spine.validation") } From e48782ac8839dd0ee6f29a6a3fe5d850c263eec7 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 29 Jan 2026 18:47:55 +0000 Subject: [PATCH 39/45] Remove outdated configuration proto code --- proto/configuration/build.gradle.kts | 41 ----------------- .../main/proto/spine/validation/config.proto | 46 ------------------- 2 files changed, 87 deletions(-) delete mode 100644 proto/configuration/build.gradle.kts delete mode 100644 proto/configuration/src/main/proto/spine/validation/config.proto diff --git a/proto/configuration/build.gradle.kts b/proto/configuration/build.gradle.kts deleted file mode 100644 index 3fc1917dd9..0000000000 --- a/proto/configuration/build.gradle.kts +++ /dev/null @@ -1,41 +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. - */ - -import io.spine.dependency.local.Compiler - -plugins { - `build-proto-model` -} - -dependencies { - api(Compiler.api) -} - -afterEvaluate { - val kspKotlin by tasks.getting - val launchSpineCompiler by tasks.getting - kspKotlin.dependsOn(launchSpineCompiler) -} diff --git a/proto/configuration/src/main/proto/spine/validation/config.proto b/proto/configuration/src/main/proto/spine/validation/config.proto deleted file mode 100644 index 704e2fbbd9..0000000000 --- a/proto/configuration/src/main/proto/spine/validation/config.proto +++ /dev/null @@ -1,46 +0,0 @@ -syntax = "proto3"; - -package spine.validation; - -option java_package = "io.spine.validation"; -option java_outer_classname = "ConfigProto"; -option java_multiple_files = true; - -import "spine/compiler/file_pattern.proto"; - -// The container type for the validation config. -// -// ProtoData supports multiple configurations merges into a single object. In order for -// the Validation library to be satisfied with the config, it must always have -// the `message_markers` field of type `spine.validation.MessageMarkers`. -// -message ValidationConfig { - - MessageMarkers message_markers = 1; -} - -// The markers that define special kinds of messages. -message MessageMarkers { - - // Names of options used to mark entity state messages. - // - // For example, `["entity", "view"]`. - // - repeated string entity_option_name = 1; - - // File patterns of files where entity state messages are defined. - // - // Deprecated: entity states are defined by the `entity` option, not - // by a file pattern. - // - repeated compiler.FilePattern entity_pattern = 2 [deprecated = true]; - - // File patterns of files where event messages are defined. - repeated compiler.FilePattern event_pattern = 3; - - // File patterns of files where command messages are defined. - repeated compiler.FilePattern command_pattern = 4; - - // File patterns of files where rejection messages are defined. - repeated compiler.FilePattern rejection_pattern = 5; -} From 5cc8b4054a123d2b1a49f5c8abd3983695e68a17 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 29 Jan 2026 18:48:03 +0000 Subject: [PATCH 40/45] Update dependency reports --- dependencies.md | 70 ++++++++++++++++++++++++------------------------- pom.xml | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/dependencies.md b/dependencies.md index 7eec9c7620..c9bbc99e40 100644 --- a/dependencies.md +++ b/dependencies.md @@ -781,16 +781,16 @@ * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.29.0.Final. @@ -1139,7 +1139,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:12 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). @@ -1731,7 +1731,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:45 WET 2026** using +This report was generated on **Thu Jan 29 18:44:07 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). @@ -2433,16 +2433,16 @@ This report was generated on **Fri Jan 23 18:24:45 WET 2026** using * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) 1. **Group** : org.jcommander. **Name** : jcommander. **Version** : 1.85. @@ -2807,7 +2807,7 @@ This report was generated on **Fri Jan 23 18:24:45 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:11 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). @@ -3551,16 +3551,16 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) 1. **Group** : org.jcommander. **Name** : jcommander. **Version** : 1.85. @@ -3901,7 +3901,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:11 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). @@ -3971,7 +3971,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:45 WET 2026** using +This report was generated on **Thu Jan 29 18:44:07 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). @@ -4465,16 +4465,16 @@ This report was generated on **Fri Jan 23 18:24:45 WET 2026** using * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) 1. **Group** : org.jcommander. **Name** : jcommander. **Version** : 1.85. @@ -4811,7 +4811,7 @@ This report was generated on **Fri Jan 23 18:24:45 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:11 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). @@ -5368,16 +5368,16 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.13. +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.14. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) 1. **Group** : org.jcommander. **Name** : jcommander. **Version** : 1.85. @@ -5747,7 +5747,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:11 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). @@ -6345,7 +6345,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:45 WET 2026** using +This report was generated on **Thu Jan 29 18:44:07 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). @@ -6863,7 +6863,7 @@ This report was generated on **Fri Jan 23 18:24:45 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:08 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). @@ -7554,7 +7554,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:08 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). @@ -8183,7 +8183,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:08 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). @@ -8855,7 +8855,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:08 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). @@ -9613,7 +9613,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:08 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). @@ -9890,7 +9890,7 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:46 WET 2026** using +This report was generated on **Thu Jan 29 18:44:07 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). @@ -10248,6 +10248,6 @@ This report was generated on **Fri Jan 23 18:24:46 WET 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jan 23 18:24:45 WET 2026** using +This report was generated on **Thu Jan 29 18:44:07 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/pom.xml b/pom.xml index 5f619d86ff..8859a27d9e 100644 --- a/pom.xml +++ b/pom.xml @@ -316,7 +316,7 @@ all modules and does not describe the project structure per-subproject. org.jacoco org.jacoco.agent - 0.8.13 + 0.8.14 org.jacoco From 01e5e12498b1d416045cf94e8a61e25230abaa3b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 29 Jan 2026 20:09:34 +0000 Subject: [PATCH 41/45] Add requirements for supported browsers --- site/.browserslistrc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 site/.browserslistrc diff --git a/site/.browserslistrc b/site/.browserslistrc new file mode 100644 index 0000000000..e996378126 --- /dev/null +++ b/site/.browserslistrc @@ -0,0 +1,3 @@ +> 0.5% +last 2 versions +not dead From 7300c062ab282336c67ba6346098e3e2f0f8519f Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 29 Jan 2026 20:11:26 +0000 Subject: [PATCH 42/45] Auto-updated by IDEA --- .idea/inspectionProfiles/Project_Default.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 0bd1d9dc2b..312610353e 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -867,6 +867,7 @@ +