Skip to content

Commit 8d1831e

Browse files
Merge pull request #67 from SpineEventEngine/new-validaation-settings
Add `RequiredIdOptionReaction` and `RequiredIdPatternCreation` from Validation
2 parents 9bb9340 + 2b0d28f commit 8d1831e

File tree

30 files changed

+2832
-480
lines changed

30 files changed

+2832
-480
lines changed

base/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies {
6363
val apiDeps = arrayOf(
6464
Compiler.api,
6565
Compiler.jvm,
66-
Validation.configuration,
66+
Validation.context,
6767
ToolBase.classicCodegen,
6868
ToolBase.pluginBase,
6969
ToolBase.protobufSetupPlugins,
@@ -94,6 +94,9 @@ dependencies {
9494
// Expose using API level for the submodules.
9595
testFixturesApi(it)
9696
}
97+
testFixturesImplementation(Validation.java)?.because(
98+
"We apply `JavaValidationPlugin` before a plugin under a test in `PluginTestSetup`."
99+
)
97100

98101
testImplementation(TestLib.lib)
99102
testImplementation(gradleTestKit())
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2025, TeamDev. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Redistribution and use in source and/or binary forms, with or without
11+
* modification, must retain the above copyright notice and the following
12+
* disclaimer.
13+
*
14+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
package io.spine.tools.core.jvm.field
28+
29+
import io.spine.option.OptionsProto
30+
import io.spine.server.event.NoReaction
31+
import io.spine.server.event.asA
32+
import io.spine.server.tuple.EitherOf2
33+
import io.spine.tools.compiler.ast.Field
34+
import io.spine.tools.compiler.ast.event.TypeDiscovered
35+
import io.spine.tools.compiler.ast.findOption
36+
import io.spine.tools.compiler.ast.ref
37+
import io.spine.tools.compiler.plugin.Reaction
38+
import io.spine.tools.validation.event.RequiredFieldDiscovered
39+
import io.spine.tools.validation.event.requiredFieldDiscovered
40+
import io.spine.tools.validation.option.required.RequiredFieldSupport.isSupported
41+
42+
/**
43+
* An abstract base for reactions that control whether an ID field
44+
* should be implicitly validated as required.
45+
*
46+
* The ID of a signal message or an entity state is the first field
47+
* declared in the type, disregarding the index of the proto field.
48+
*
49+
* The ID field is assumed as required for commands and entity states,
50+
* unless it is specifically marked otherwise using the field options.
51+
*
52+
* Implementations define the ways of discovering signal and entity
53+
* state messages.
54+
*/
55+
public abstract class RequiredIdReaction : Reaction<TypeDiscovered>() {
56+
57+
/**
58+
* Controls whether the given ID [field] should be implicitly validated
59+
* as required.
60+
*
61+
* The method emits [RequiredFieldDiscovered] event if the following
62+
* conditions are met:
63+
*
64+
* 1. The field does not have the `(required)` option specified explicitly.
65+
* If it has, the field is handled by
66+
* `io.spine.tools.validation.option.required.RequiredReaction` of
67+
* the Validation Compiler.
68+
*
69+
* 2. The field type is supported by the option.
70+
*
71+
* The method emits [NoReaction] in case of violation of the above conditions.
72+
*
73+
* @param field The ID field.
74+
* @param message The error message for the violation.
75+
*/
76+
@Suppress("ReturnCount") // Prefer sooner exit and precise conditions.
77+
protected fun withField(
78+
field: Field,
79+
message: String
80+
): EitherOf2<RequiredFieldDiscovered, NoReaction> {
81+
val requiredOption = field.findOption(OptionsProto.required)
82+
if (requiredOption != null) {
83+
return ignore()
84+
}
85+
86+
val fieldTypeUnsupported = field.type.isSupported().not()
87+
if (fieldTypeUnsupported) {
88+
return ignore()
89+
}
90+
91+
return requiredFieldDiscovered {
92+
id = field.ref
93+
defaultErrorMessage = message
94+
subject = field
95+
}.asA()
96+
}
97+
}

base/src/testFixtures/kotlin/io/spine/tools/core/java/PluginTestSetup.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import io.spine.format.Format
4747
import io.spine.tools.code.Java
4848
import io.spine.tools.code.SourceSetName
4949
import io.spine.tools.core.jvm.gradle.settings.CoreJvmCompilerSettings
50+
import io.spine.tools.validation.java.JavaValidationPlugin
5051
import io.spine.type.toJson
5152
import java.nio.file.Path
5253
import kotlin.io.path.exists
@@ -128,6 +129,9 @@ abstract class PluginTestSetup<S: Message>(
128129
val setup = byResources(
129130
params = params,
130131
plugins = listOf(
132+
// Apply the Java Validation plugin first to ensure
133+
// that custom validations are handled by the Validation backend.
134+
JavaValidationPlugin(),
131135
plugin,
132136
// We want to be able to see the code in debug formatted for easier reading.
133137
JavaCodeStyleFormatterPlugin()

buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ object Validation {
3636
/**
3737
* The version of the Validation library artifacts.
3838
*/
39-
const val version = "2.0.0-SNAPSHOT.375"
39+
const val version = "2.0.0-SNAPSHOT.378"
4040

4141
/**
4242
* The last version of Validation compatible with ProtoData.
@@ -62,5 +62,5 @@ object Validation {
6262
const val model = "$group:$prefix-model:$version"
6363

6464
const val configModule = "$group:$prefix-configuration"
65-
const val configuration = "$configModule:$version"
65+
const val context = "$group:$prefix-context:$version"
6666
}

buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ import org.gradle.api.file.SourceDirectorySet
3535
import org.gradle.api.tasks.bundling.Jar
3636

3737
/**
38-
* Tells whether there are any Proto sources in "main" source set.
38+
* Tells whether there are any Proto sources in the "main" source set.
3939
*/
40-
internal fun Project.hasProto(): Boolean {
40+
fun Project.hasProto(): Boolean {
4141
val protoSources = protoSources()
42-
val result = protoSources.any { it.exists() }
42+
val result = protoSources.any {
43+
it.exists()
44+
&& it.isDirectory
45+
&& it.listFiles()?.isNotEmpty() ?: false
46+
}
4347
return result
4448
}
4549

buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import org.gradle.api.Project
3636
import org.gradle.api.Task
3737
import org.gradle.api.publish.PublicationContainer
3838
import org.gradle.api.publish.PublishingExtension
39+
import org.gradle.api.publish.maven.MavenPublication
3940
import org.gradle.api.tasks.TaskContainer
4041
import org.gradle.api.tasks.TaskProvider
4142
import org.gradle.api.tasks.bundling.Jar
@@ -231,7 +232,7 @@ fun TaskContainer.excludeGoogleProtoFromArtifacts() {
231232
* Java and Kotlin sources are default to `main` source set since it is created by `java` plugin.
232233
* For Proto sources to be included – [special treatment][protoSources] is needed.
233234
*/
234-
internal fun Project.sourcesJar(): TaskProvider<Jar> = tasks.getOrCreate("sourcesJar") {
235+
fun Project.sourcesJar(): TaskProvider<Jar> = tasks.getOrCreate("sourcesJar") {
235236
dependOnGenerateProto()
236237
archiveClassifier.set("sources")
237238
from(sourceSets["main"].allSource) // Puts Java and Kotlin sources.
@@ -245,7 +246,7 @@ internal fun Project.sourcesJar(): TaskProvider<Jar> = tasks.getOrCreate("source
245246
* The output of this task is a `jar` archive. The archive contains only
246247
* [Proto sources][protoSources] from `main` source set.
247248
*/
248-
internal fun Project.protoJar(): TaskProvider<Jar> = tasks.getOrCreate("protoJar") {
249+
fun Project.protoJar(): TaskProvider<Jar> = tasks.getOrCreate("protoJar") {
249250
dependOnGenerateProto()
250251
archiveClassifier.set("proto")
251252
from(protoSources())
@@ -317,3 +318,20 @@ internal fun Project.artifacts(jarFlags: JarFlags): Set<TaskProvider<Jar>> {
317318

318319
return tasks
319320
}
321+
322+
/**
323+
* Adds the source code and documentation JARs to the publication.
324+
*/
325+
@Suppress("unused")
326+
fun MavenPublication.addSourceAndDocJars(project: Project) {
327+
val tasks = mutableSetOf<TaskProvider<Jar>>()
328+
tasks.add(project.sourcesJar())
329+
tasks.add(project.javadocJar())
330+
tasks.add(project.htmlDocsJar())
331+
if (project.hasProto()) {
332+
tasks.add(project.protoJar())
333+
}
334+
tasks.forEach {
335+
artifact(it)
336+
}
337+
}

buildSrc/src/main/kotlin/module.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ fun Module.forceConfigurations() {
195195
Validation.runtime,
196196
Validation.java,
197197
Validation.javaBundle,
198-
Validation.configuration
199198
)
200199
}
201200
}

0 commit comments

Comments
 (0)