generated from SpineEventEngine/template
-
Notifications
You must be signed in to change notification settings - Fork 0
Support custom message validators #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
85 commits
Select commit
Hold shift + click to select a range
44f253e
Add `MessageValidator` SPI interface
a38cb3b
Merge branch 'master' into message-validator
12e9905
Bump the version -> `2.0.0-SNAPSHOT.333`
eed6eea
Update reports
ceb4763
Introduce `@Validator` annotation
a25515a
Remove an empty line
8f473c7
Implement a test stub
1e17875
Make `MessageValidator` return a list of violations
dc2d9e0
Create `:java-tests:validator` module
afe5c2f
Implement `FileDescriptorSetValidator`
b1a232f
Create `:java-ksp` module
11a9b7e
Try to discover custom validators in `JavaValidationPlugin`
8fa81fc
Comment out task dependencies
5adc510
Suppress Detekt warnings
4baa44c
Make `kspKotlin` run before ProtoData
767b8e3
Implement `ValidatorRegistry`
1c2fbe6
Prototype `ValidatorProcessor` and `ValidatorRegistry` coordination
c6700d4
Remove `ValidatorRegistry` class
6d7356d
Merge branch 'master' into message-validator
af9a2f1
Add a new shortcut for declaring dependencies
e5b719c
Declare KSP dependency using new API
d369e60
Assert `WhenFactory` respectively
3e44ac1
Bump the version -> `2.0.0-SNAPSHOT.342`
42b6caf
Update reports
68e1724
Make sure KSP always runs
47662fc
Merge branch 'master' into message-validator
77acacc
Bump the version -> `2.0.0-SNAPSHOT.342`
b661a4f
Have discovered validators in ProtoData using a plain file
7c94b89
Pass `customValidators` to `JavaValidationRenderer`
49d5536
Rename the constant
36a67d4
Make `MessageValidator` accept parental info
433c0e2
Make `MessageValidator` accept `fieldPath` and `typeName`
3447734
Map message class to validator class during discovering
e5ecf5d
Implement `ValidatorGenerator`
8f9da03
Leave `todo`s for `@Validator` annotation
5b9257b
Implement integration tests for validators
5553e21
Remove `ValidatorProcessorSpec`
6ba9dcd
Make the processor always create an output file
e611ab8
Do not throw if there are no validators
5850f30
Create `ValidatorViolation`
b510883
Temporary commit specs to a todo comment
62c1cd0
Adjust validators to the new interface
80b5196
Implement more test cases
9028bb2
Make the base class contain all necessary properties
edcbd39
Handle `ValidatorViolation` in the generated code
346e37d
Allow validator codegen for `repeated` and `map` fields
3e122cf
Support `repeated` and `map` fields for validators
3eef81f
Rename the private method to avoid confusion
d1f3bc1
Make test validator check exact instance
c81f246
Cover repeated and map fields with tests
5917f30
Clean up imports
3032d41
Check validators declared only for external messages
82bbea1
Test external messages from dependencies
a9de0a9
Reference artifacts with extension
18fcea4
Give a usage example
940c31c
Implement `EarphonesValidator`
a5d686f
Implement tests for messages from dependencies
80db0c7
Move `DetectedViolation` to a separate file
f06758f
Document `MessageValidator`
03f9fdf
Rename the object
a25d66b
Update docs to the `@Validator` annotation
772d548
Bump `KotlinCompileTesting`
e8b88b4
Set up KSP testing
183bdfe
Force Kotlin embeddable and KSP
05bfe8f
Assert the generated file
a513a2a
Extract common code of positive tests
ef1a3f7
Implement tests for KSP processor
b9027f2
Suppress `ReturnCount`
c16d89a
Proofread docs
e1fe3dc
Refactor the assertion methods
3cb0ee0
Shorten the list of required dependencies
131512c
Remove no longer needed dependency
84ab928
Proofread docs
5548192
Move `ValidatorGenerator` to the `generate` package
4015a23
Remain only the necessary task dependencies
ad0a8fc
Update reports
fe7991d
Explain the difference between local and external messages
9fbe3f0
Remove duplicate mentioning of the restriction
0a712b3
Capitalize the library name
5aeba49
Rename the section
eff29ff
Get rid of `custom` bound
b0f1766
Introduce the `Problem` section
4f65755
Avoid `embedded` in docs to the validator
34f7049
Move the resolving logic to the `DiscoveredValidators` object
ed00afe
Enhance docs
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
65 changes: 65 additions & 0 deletions
65
java-api/src/main/kotlin/io/spine/validation/api/DetectedViolation.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* | ||
| * Copyright 2025, TeamDev. All rights reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Redistribution and use in source and/or binary forms, with or without | ||
| * modification, must retain the above copyright notice and the following | ||
| * disclaimer. | ||
| * | ||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
|
|
||
| package io.spine.validation.api | ||
|
|
||
| import io.spine.base.FieldPath | ||
| import io.spine.validate.TemplateString | ||
|
|
||
| /** | ||
| * Abstract base for violations detected by [MessageValidator]s. | ||
| * | ||
| * @param message The error message describing the violation. | ||
| * @param fieldPath The path to the field where the violation occurred, if applicable. | ||
| * @param fieldValue The field value that caused the violation, if any. | ||
| */ | ||
| public abstract class DetectedViolation( | ||
| public val message: TemplateString, | ||
| public val fieldPath: FieldPath?, | ||
| public val fieldValue: Any?, | ||
| ) | ||
|
|
||
| /** | ||
| * A violation tied to a specific field in a message. | ||
| * | ||
| * @param message The error message describing the violation. | ||
| * @param fieldPath The path to the field where the violation occurred. | ||
| * @param fieldValue The field value that caused the violation, if any. | ||
| */ | ||
| public class FieldViolation( | ||
| message: TemplateString, | ||
| fieldPath: FieldPath, | ||
| fieldValue: Any? = null, | ||
| ) : DetectedViolation(message, fieldPath, fieldValue) | ||
|
|
||
| /** | ||
| * A violation related to the message level (not tied to a specific field). | ||
| * | ||
| * @param message The error message describing the violation. | ||
| */ | ||
| public class MessageViolation( | ||
| message: TemplateString | ||
| ) : DetectedViolation(message, fieldPath = null, fieldValue = null) |
56 changes: 56 additions & 0 deletions
56
java-api/src/main/kotlin/io/spine/validation/api/DiscoveredValidators.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /* | ||
| * Copyright 2025, TeamDev. All rights reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Redistribution and use in source and/or binary forms, with or without | ||
| * modification, must retain the above copyright notice and the following | ||
| * disclaimer. | ||
| * | ||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
|
|
||
| package io.spine.validation.api | ||
|
|
||
| import io.spine.annotation.Internal | ||
| import java.io.File | ||
|
|
||
| /** | ||
| * Holds a path to a file with the discovered validators. | ||
| * | ||
| * The KSP processor generates a resource file using this path. | ||
| * Then, the Java codegen plugin picks up this file. | ||
| */ | ||
| @Internal | ||
| public object DiscoveredValidators { | ||
|
|
||
| /** | ||
| * The path to the file with the discovered message validators. | ||
| * | ||
| * The path is relative to the output directory of the KSP processor. | ||
| */ | ||
| public const val RESOURCES_LOCATION: String = "spine/validation/message-validators" | ||
|
|
||
| /** | ||
| * Resolves the path to the file containing discovered message validators. | ||
| * | ||
| * @param kspOutputDirectory The path to the KSP output. | ||
| */ | ||
| public fun resolve(kspOutputDirectory: File): File = kspOutputDirectory | ||
| .resolve("resources") | ||
| .resolve(RESOURCES_LOCATION) | ||
| } |
142 changes: 142 additions & 0 deletions
142
java-api/src/main/kotlin/io/spine/validation/api/MessageValidator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| /* | ||
| * Copyright 2025, TeamDev. All rights reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Redistribution and use in source and/or binary forms, with or without | ||
| * modification, must retain the above copyright notice and the following | ||
| * disclaimer. | ||
| * | ||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
|
|
||
| package io.spine.validation.api | ||
|
|
||
| import com.google.protobuf.Message | ||
| import io.spine.annotation.SPI | ||
|
|
||
| /** | ||
| * A validator for an external Protobuf message of type [M]. | ||
| * | ||
| * This interface enforces validation rules for `Message`s whose Java/Kotlin classes | ||
| * are already generated by third parties, in case they are used in the Protobuf | ||
| * codebase to which the Validation library is applied. | ||
| * | ||
| * ## Problem | ||
| * | ||
| * Java/Kotlin libraries that use Protobuf messages often distribute both the `.proto` | ||
| * definitions and the compiled class files (`.class`) for these messages. | ||
| * As these classes are pre-generated, consumers cannot modify their underlying | ||
| * `.proto` files to define validation constraints and the Validation library | ||
| * cannot use code generation to enforce the constraints. | ||
| * | ||
| * Thus, the library effectively deals with the two types of messages: | ||
| * | ||
| * 1. **Local messages** are message types for which end-users generate and control | ||
| * the Java/Kotlin classes by compiling the corresponding `.proto` definitions | ||
| * within their own codebase. For such messages, the Validation library allows | ||
| * declaring validation constraints and enforces them with the generated code. | ||
| * | ||
| * 2. **External messages** are message types for which end-users do not generate or control | ||
| * the Java/Kotlin classes. Because the classes are already generated, users cannot modify | ||
| * the underlying `.proto` definitions and add validation options at compile time. | ||
| * | ||
| * ## Validation of external messages | ||
| * | ||
| * The Validation library provides a mechanism that allows validating of | ||
| * the external messages, **which are used for fields within local messages**. | ||
| * Implement this interface and annotate the implementing class with | ||
| * the [@Validator][Validator] annotation, specifying the message type to validate. | ||
| * | ||
| * For each field of type [M] within any local message, the library will invoke | ||
| * the [MessageValidator.validate] method when validating the local message. | ||
| * | ||
| * The following Protobuf field types are supported: | ||
| * | ||
| * 1. Singular fields of type [M]. | ||
| * 2. Repeated field of type [M]. | ||
| * 3. Map field with values of type [M]. | ||
| * | ||
| * An example of the validator declaration for the `Earphones` message: | ||
| * | ||
| * ```kotlin | ||
| * @Validator(Earphones::class) | ||
| * public class EarphonesValidator : MessageValidator<Earphones> { | ||
| * public override fun validate(message: Earphones): List<DetectedViolation> { | ||
| * return emptyList() // Always valid. | ||
| * } | ||
| * } | ||
| * ``` | ||
| * | ||
| * Please 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: | ||
| * | ||
| * ```proto | ||
| * // Brings the `Earphones` message from dependencies. | ||
| * // Suppose we don't control the generated code of declarations from this file. | ||
| * import "earphones.proto"; | ||
| * | ||
| * // A locally-declared message. | ||
armiol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * message WorkingSetup { | ||
| * | ||
| * // The field of external message type. | ||
| * Earphones earphones = 1; | ||
| * } | ||
| * ``` | ||
| * | ||
| * Supposing that the Validation library applied to the module where both `WorkingSetup` | ||
| * and `EarphonesValidator` classes are declared, then the generated code of `WorkingSetup` | ||
| * will apple the validator to each instance passed to the `WorkingSetup.earphones` field. | ||
| * | ||
| * Please note that the following use cases are NOT supported and will lead to an error: | ||
| * | ||
| * 1) Declaring a validator for a local message is prohibited. Only external messages are | ||
| * allowed to have a validator. Use built-in or custom validation options to declare | ||
| * constraints for local messages. | ||
| * 2) Declaring multiple validators for the same message type is prohibited. The library | ||
| * scans the module’s classpath to discover validators, and expects exactly one validator | ||
| * per message type. | ||
| * | ||
| * ## Implementation | ||
| * | ||
| * The interface offers flexible validation strategies. Implementations can choose to | ||
| * validate a particular field, several fields, the whole message instance (for example, | ||
| * checking the field relations), and perform a deep validation. | ||
| * | ||
| * It is a responsibility of the validator to provide the correct instances | ||
| * of [DetectedViolation]. Before reporting to the user, the library converts | ||
| * [DetectedViolation] to a [ConstraintViolation][io.spine.validate.ConstraintViolation]. | ||
| * Returning of an empty list of violations means that the given message is valid. | ||
| * | ||
| * Please keep in mind that for each invocation a new instance of [MessageValidator] | ||
| * is created. Every implementation of [MessageValidator] must have a public, | ||
| * no-args constructor. | ||
| * | ||
| * @param M the type of Protobuf [Message] being validated. | ||
| */ | ||
| @SPI | ||
| public interface MessageValidator<M : Message> { | ||
|
|
||
| /** | ||
| * Validates the given [message]. | ||
| * | ||
| * @return the detected violations or empty list. | ||
| */ | ||
| public fun validate(message: M): List<DetectedViolation> | ||
| } | ||
55 changes: 55 additions & 0 deletions
55
java-api/src/main/kotlin/io/spine/validation/api/Validator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /* | ||
| * Copyright 2025, TeamDev. All rights reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Redistribution and use in source and/or binary forms, with or without | ||
| * modification, must retain the above copyright notice and the following | ||
| * disclaimer. | ||
| * | ||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
|
|
||
| package io.spine.validation.api | ||
|
|
||
| import com.google.protobuf.Message | ||
| import kotlin.annotation.AnnotationRetention.SOURCE | ||
| import kotlin.annotation.AnnotationTarget.CLASS | ||
| import kotlin.reflect.KClass | ||
|
|
||
| /** | ||
| * Marks the class as a message validator. | ||
| * | ||
| * Applying this annotation to an implementation of [MessageValidator] | ||
| * makes the class visible to the validation library. | ||
| * | ||
| * Please note that the following requirements are imposed to the marked class: | ||
| * | ||
| * 1. The class must implement the [MessageValidator] interface. | ||
| * 2. The class must have a public, no-args constructor. | ||
| * 3. The class cannot be `inner`, but nested classes are allowed. | ||
| * 4. The message type of [Validator.value] and [MessageValidator] must match. | ||
| */ | ||
| @Target(CLASS) | ||
| @Retention(SOURCE) | ||
| public annotation class Validator( | ||
|
|
||
| /** | ||
| * The class of the validated external message. | ||
| */ | ||
| val value: KClass<out Message> | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.