-
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
Changes from 78 commits
44f253e
a38cb3b
12e9905
eed6eea
ceb4763
a25515a
8f473c7
1e17875
dc2d9e0
afe5c2f
b1a232f
11a9b7e
8fa81fc
5adc510
4baa44c
767b8e3
1c2fbe6
c6700d4
6d7356d
af9a2f1
e5b719c
d369e60
3e44ac1
42b6caf
68e1724
47662fc
77acacc
b661a4f
7c94b89
49d5536
36a67d4
433c0e2
3447734
e5ecf5d
8f9da03
5b9257b
5553e21
6ba9dcd
e611ab8
5850f30
b510883
62c1cd0
80b5196
9028bb2
edcbd39
346e37d
3e122cf
3eef81f
d1f3bc1
c81f246
5917f30
3032d41
82bbea1
a9de0a9
18fcea4
940c31c
a5d686f
80db0c7
f06758f
03f9fdf
a25d66b
772d548
e8b88b4
183bdfe
05bfe8f
a513a2a
ef1a3f7
b9027f2
c16d89a
e1fe3dc
3cb0ee0
131512c
84ab928
5548192
4015a23
ad0a8fc
fe7991d
9fbe3f0
0a712b3
5aeba49
eff29ff
b0f1766
4f65755
34f7049
ed00afe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| 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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * 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 | ||
|
|
||
| /** | ||
| * 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" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| /* | ||
| * 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 custom validator for an external Protobuf message of type [M]. | ||
| * | ||
| * ## Notation of external and local messages | ||
|
||
| * | ||
armiol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * **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. | ||
| * | ||
| * **External messages** are message types for which end-users do not generate or control | ||
| * the Java/Kotlin classes (for example, pre-compiled classes from third-party dependencies). | ||
| * Because these classes are already generated, users cannot modify the underlying `.proto` | ||
| * definitions and add validation options at compile time. There is no control of | ||
| * the code generation. | ||
| * | ||
| * ## Validation of external messages | ||
| * | ||
| * The validation library provides a customization mechanism that allows validating | ||
| * of the external messages, **which are embedded within local messages**. | ||
|
||
| * Implement this interface and annotate the implementing class with | ||
| * the [@Validator][Validator] annotation, specifying the type of the message 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. | ||
| * | ||
| * ## Implementation | ||
| * | ||
| * The message validator does not have restrictions upon how exactly the message | ||
|
||
| * must be validated. It can 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 validation library | ||
| * converts [DetectedViolation] to a [ConstraintViolation][io.spine.validate.ConstraintViolation]. | ||
| * Returning of an empty list of violations means that the passed 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. | ||
| * | ||
| * An implementation of [MessageValidator] will be rejected by the validation library | ||
| * in the following cases: | ||
| * | ||
| * 1) It is used to validate a local message. Only external messages are allowed | ||
| * to have a validator. | ||
| * 2) There already exists a validator for the specified message type. Having several | ||
|
||
| * validators for the same message type is prohibited. | ||
| * | ||
| * @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> | ||
| } | ||
| 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> | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have our own implementations of this
MessageValidator? I mean, is it expected that all implementations are for the custom "external"Messagetypes only?