Skip to content

Commit 2a7aa83

Browse files
Merge pull request #190 from SpineEventEngine/goes-standalone-generator
Generate code for `(goes)` with a standalone generator
2 parents 7925cd0 + c71941f commit 2a7aa83

File tree

64 files changed

+1674
-633
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1674
-633
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ package io.spine.dependency.local
3333
*/
3434
@Suppress("ConstPropertyName")
3535
object Base {
36-
const val version = "2.0.0-SNAPSHOT.241"
36+
const val version = "2.0.0-SNAPSHOT.242"
3737
const val versionForBuildScript = "2.0.0-SNAPSHOT.241"
3838
const val group = Spine.group
3939
const val artifact = "spine-base"

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ object McJava {
4242
/**
4343
* The version used to in the build classpath.
4444
*/
45-
const val dogfoodingVersion = "2.0.0-SNAPSHOT.262"
45+
const val dogfoodingVersion = "2.0.0-SNAPSHOT.264"
4646

4747
/**
4848
* The version to be used for integration tests.
4949
*/
50-
const val version = "2.0.0-SNAPSHOT.263"
50+
const val version = "2.0.0-SNAPSHOT.264"
5151

5252
/**
5353
* The ID of the Gradle plugin.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ object ProtoData {
7373
* The version of ProtoData dependencies.
7474
*/
7575
val version: String
76-
private const val fallbackVersion = "0.92.3"
76+
private const val fallbackVersion = "0.92.9"
7777

7878
/**
7979
* The distinct version of ProtoData used by other build tools.
@@ -82,7 +82,7 @@ object ProtoData {
8282
* transitional dependencies, this is the version used to build the project itself.
8383
*/
8484
val dogfoodingVersion: String
85-
private const val fallbackDfVersion = "0.91.4"
85+
private const val fallbackDfVersion = "0.92.7"
8686

8787
/**
8888
* The artifact for the ProtoData Gradle plugin.

dependencies.md

Lines changed: 44 additions & 28 deletions
Large diffs are not rendered by default.

java-tests/runtime/src/test/proto/spine/test/validation/goes_option_test.proto

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,3 @@ message PaymentId {
5353

5454
string uuid = 1;
5555
}
56-
57-
58-
message WithFieldNotFound {
59-
60-
string id = 1;
61-
62-
bytes avatar = 2 [(goes).with = "user_id"];
63-
}

java-tests/validating/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ dependencies {
5757
testImplementation(JUnit.params)
5858
testImplementation(TestLib.lib)
5959
testImplementation(Time.lib)
60+
testImplementation(testFixtures(project(":model")))
61+
?.because("We need extensions from `ProtobufJavaClass.kt`")
6062
}
6163

6264
testProtoDataRemoteDebug(enabled = false)

java-tests/validating/src/test/kotlin/io/spine/test/options/goes/GoesMutualITest.kt

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
package io.spine.test.options.goes
2828

2929
import com.google.protobuf.Message
30-
import io.spine.test.options.goes.given.newBuilder
31-
import io.spine.test.options.goes.given.protoDescriptor
32-
import io.spine.test.options.goes.given.protoValue
30+
import io.spine.test.options.set
3331
import io.spine.validate.ValidationException
32+
import io.spine.validation.getDescriptor
33+
import io.spine.validation.newBuilder
3434
import org.junit.jupiter.api.DisplayName
3535
import org.junit.jupiter.api.assertDoesNotThrow
3636
import org.junit.jupiter.api.assertThrows
@@ -51,7 +51,7 @@ import org.junit.jupiter.params.provider.MethodSource
5151
internal class GoesMutualITest {
5252

5353
@Suppress("MaxLineLength") // So not to wrap the test display name.
54-
@MethodSource("io.spine.test.options.goes.given.GoesMutualTestEnv#interdependentFields")
54+
@MethodSource("io.spine.test.options.goes.GoesMutualTestEnv#interdependentFields")
5555
@ParameterizedTest(name = "throw if one of mutually dependent `{1}` and `{3}` fields is not set")
5656
fun throwIfOneOfMutuallyDependentFieldsNotSet(
5757
message: Class<out Message>,
@@ -60,24 +60,22 @@ internal class GoesMutualITest {
6060
fieldName2: String,
6161
fieldValue2: Any
6262
) {
63-
val descriptor = message.protoDescriptor()
63+
val descriptor = message.getDescriptor()
6464
val field1 = descriptor.findFieldByName(fieldName1)!!
65-
val protoValue1 = protoValue(field1, fieldValue1)
6665
assertThrows<ValidationException> {
6766
message.newBuilder()
68-
.setField(field1, protoValue1)
67+
.set(field1, fieldValue1)
6968
.build()
7069
}
7170
val field2 = descriptor.findFieldByName(fieldName2)!!
72-
val protoValue2 = protoValue(field2, fieldValue2)
7371
assertThrows<ValidationException> {
7472
message.newBuilder()
75-
.setField(field2, protoValue2)
73+
.set(field2, fieldValue2)
7674
.build()
7775
}
7876
}
7977

80-
@MethodSource("io.spine.test.options.goes.given.GoesMutualTestEnv#interdependentFields")
78+
@MethodSource("io.spine.test.options.goes.GoesMutualTestEnv#interdependentFields")
8179
@ParameterizedTest(name = "pass if both mutually dependent `{1}` and `{3}` fields are set")
8280
fun passIfBothMutuallyDependentFieldsSet(
8381
message: Class<out Message>,
@@ -86,21 +84,19 @@ internal class GoesMutualITest {
8684
fieldName2: String,
8785
fieldValue2: Any
8886
) {
89-
val descriptor = message.protoDescriptor()
87+
val descriptor = message.getDescriptor()
9088
val field1 = descriptor.findFieldByName(fieldName1)!!
91-
val protoValue1 = protoValue(field1, fieldValue1)
9289
val field2 = descriptor.findFieldByName(fieldName2)!!
93-
val protoValue2 = protoValue(field2, fieldValue2)
9490
assertDoesNotThrow {
9591
message.newBuilder()
96-
.setField(field1, protoValue1)
97-
.setField(field2, protoValue2)
92+
.set(field1, fieldValue1)
93+
.set(field2, fieldValue2)
9894
.build()
9995
}
10096
}
10197

10298
@Suppress("MaxLineLength") // Due to a long method source.
103-
@MethodSource("io.spine.test.options.goes.given.GoesMutualTestEnv#messagesWithInterdependentFields")
99+
@MethodSource("io.spine.test.options.goes.GoesMutualTestEnv#messagesWithInterdependentFields")
104100
@ParameterizedTest(name = "pass if both mutually dependent fields are not set")
105101
fun passIfBothMutuallyDependentFieldsNotSet(message: Class<out Message>) {
106102
assertDoesNotThrow {

java-tests/validating/src/test/kotlin/io/spine/test/options/goes/GoesOneWayITest.kt

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
package io.spine.test.options.goes
2828

2929
import com.google.protobuf.Message
30-
import io.spine.test.options.goes.given.newBuilder
31-
import io.spine.test.options.goes.given.protoDescriptor
32-
import io.spine.test.options.goes.given.protoValue
30+
import io.spine.test.options.set
3331
import io.spine.validate.ValidationException
32+
import io.spine.validation.getDescriptor
33+
import io.spine.validation.newBuilder
3434
import org.junit.jupiter.api.DisplayName
3535
import org.junit.jupiter.api.assertDoesNotThrow
3636
import org.junit.jupiter.api.assertThrows
@@ -50,38 +50,36 @@ import org.junit.jupiter.params.provider.MethodSource
5050
@DisplayName("`(goes)` constraint should")
5151
internal class GoesOneWayITest {
5252

53-
@MethodSource("io.spine.test.options.goes.given.GoesOneWayTestEnv#onlyTargetFields")
53+
@MethodSource("io.spine.test.options.goes.GoesOneWayTestEnv#onlyTargetFields")
5454
@ParameterizedTest(name = "throw if only the target `{1}` field is set")
5555
fun throwIfOnlyTargetFieldSet(message: Class<Message>, fieldName: String, fieldValue: Any) {
56-
val descriptor = message.protoDescriptor()
56+
val descriptor = message.getDescriptor()
5757
val field = descriptor.findFieldByName(fieldName)!!
58-
val protoValue = protoValue(field, fieldValue)
5958
assertThrows<ValidationException> {
6059
message.newBuilder()
61-
.setField(field, protoValue)
60+
.set(field, fieldValue)
6261
.build()
6362
}
6463
}
6564

66-
@MethodSource("io.spine.test.options.goes.given.GoesOneWayTestEnv#onlyCompanionFields")
65+
@MethodSource("io.spine.test.options.goes.GoesOneWayTestEnv#onlyCompanionFields")
6766
@ParameterizedTest(name = "pass if only the companion `{1}` field is set")
6867
fun passIfOnlyCompanionFieldSet(
6968
message: Class<out Message>,
7069
fieldName: String,
7170
fieldValue: Any
7271
) {
73-
val descriptor = message.protoDescriptor()
72+
val descriptor = message.getDescriptor()
7473
val companionField = descriptor.findFieldByName(fieldName)!!
75-
val companionProtoValue = protoValue(companionField, fieldValue)
7674
assertDoesNotThrow {
7775
message.newBuilder()
78-
.setField(companionField, companionProtoValue)
76+
.set(companionField, fieldValue)
7977
.build()
8078
}
8179
}
8280

8381
@Suppress("MaxLineLength") // So not to wrap the test name.
84-
@MethodSource("io.spine.test.options.goes.given.GoesOneWayTestEnv#bothTargetAndCompanionFields")
82+
@MethodSource("io.spine.test.options.goes.GoesOneWayTestEnv#bothTargetAndCompanionFields")
8583
@ParameterizedTest(name = "pass if both the target `{1}` and its companion `{3}` fields are set")
8684
fun passIfBothTargetAndCompanionFieldsSet(
8785
message: Class<out Message>,
@@ -90,15 +88,13 @@ internal class GoesOneWayITest {
9088
fieldName: String,
9189
fieldValue: Any
9290
) {
93-
val descriptor = message.protoDescriptor()
91+
val descriptor = message.getDescriptor()
9492
val field = descriptor.findFieldByName(fieldName)!!
95-
val protoValue = protoValue(field, fieldValue)
9693
val companionField = descriptor.findFieldByName(companionName)!!
97-
val companionProtoValue = protoValue(companionField, companionValue)
9894
assertDoesNotThrow {
9995
message.newBuilder()
100-
.setField(field, protoValue)
101-
.setField(companionField, companionProtoValue)
96+
.set(field, fieldValue)
97+
.set(companionField, companionValue)
10298
.build()
10399
}
104100
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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.test.options.goes
28+
29+
import com.google.protobuf.Message.Builder
30+
import io.kotest.matchers.maps.shouldContainExactly
31+
import io.kotest.matchers.shouldBe
32+
import io.spine.base.FieldPath
33+
import io.spine.protobuf.TypeConverter.toAny
34+
import io.spine.protobuf.field
35+
import io.spine.test.options.set
36+
import io.spine.test.tools.validate.GoesCustomMessage
37+
import io.spine.test.tools.validate.GoesDefaultMessage
38+
import io.spine.validate.RuntimeErrorPlaceholder.FIELD_PATH
39+
import io.spine.validate.RuntimeErrorPlaceholder.FIELD_TYPE
40+
import io.spine.validate.RuntimeErrorPlaceholder.FIELD_VALUE
41+
import io.spine.validate.RuntimeErrorPlaceholder.GOES_COMPANION
42+
import io.spine.validate.RuntimeErrorPlaceholder.PARENT_TYPE
43+
import io.spine.validate.ValidationException
44+
import org.junit.jupiter.api.DisplayName
45+
import org.junit.jupiter.api.assertThrows
46+
import org.junit.jupiter.params.ParameterizedTest
47+
import org.junit.jupiter.params.provider.MethodSource
48+
49+
/**
50+
* Tests [ConstraintViolation][io.spine.validate.ConstraintViolation]s created by `(goes)`.
51+
*/
52+
@DisplayName("`(goes)` constraint should")
53+
internal class GoesViolationITest {
54+
55+
@MethodSource("io.spine.test.options.goes.GoesViolationTestEnv#onlyTargetFields")
56+
@ParameterizedTest(name = "use the default error message for `{0}` field")
57+
fun useDefaultErrorMessage(fieldName: String, fieldType: String, fieldValue: Any) =
58+
GoesDefaultMessage.newBuilder()
59+
.assertConstraintViolation(fieldName, fieldType, fieldValue, ::defaultTemplate)
60+
61+
@MethodSource("io.spine.test.options.goes.GoesViolationTestEnv#onlyTargetFields")
62+
@ParameterizedTest(name = "use the custom error message for `{0}` field")
63+
fun useCustomErrorMessage(fieldName: String, fieldType: String, fieldValue: Any) =
64+
GoesCustomMessage.newBuilder()
65+
.assertConstraintViolation(fieldName, fieldType, fieldValue, ::customTemplate)
66+
}
67+
68+
/**
69+
* Asserts that this message [Builder] throws [ValidationException] with
70+
* the expected parameters when [fieldName] is set without its [COMPANION_FIELD].
71+
*
72+
* @param fieldName The name of the field.
73+
* @param fieldType The name of the field type.
74+
* @param value The field value to set.
75+
* @param template The error message template to check.
76+
*/
77+
private fun Builder.assertConstraintViolation(
78+
fieldName: String,
79+
fieldType: String,
80+
value: Any,
81+
template: (Int) -> String
82+
) {
83+
val field = descriptorForType.field(fieldName)!!
84+
val parentType = descriptorForType.fullName
85+
val exception = assertThrows<ValidationException> {
86+
set(field, value)
87+
.build()
88+
}
89+
90+
val violations = exception.constraintViolations.also { it.size shouldBe 1 }
91+
val violation = violations.first()
92+
93+
with(violation) {
94+
message.withPlaceholders shouldBe template(field.index + 1)
95+
message.placeholderValueMap shouldContainExactly mapOf(
96+
FIELD_PATH to fieldName,
97+
FIELD_VALUE to "$value",
98+
FIELD_TYPE to fieldType,
99+
PARENT_TYPE to parentType,
100+
GOES_COMPANION to COMPANION_FIELD,
101+
).mapKeys { it.key.toString() }
102+
103+
typeName shouldBe parentType
104+
fieldPath shouldBe FieldPath(fieldName)
105+
fieldValue shouldBe toAny(value)
106+
}
107+
}
108+
109+
@Suppress("UNUSED_PARAMETER") // The function should match the expected interface.
110+
private fun defaultTemplate(fieldNumber: Int) =
111+
"The field `\${goes.companion}` must also be set when `\${field.path}`" +
112+
" is set in `\${parent.type}`."
113+
114+
private fun customTemplate(fieldNumber: Int) =
115+
"Field_$fieldNumber: `{companionName}`, `{fieldName}`."

java-tests/validating/src/test/kotlin/io/spine/test/options/setonce/SetOnceFieldsITest.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,20 @@
2727
package io.spine.test.options.setonce
2828

2929
import com.google.protobuf.ByteString.copyFromUtf8
30-
import io.spine.test.options.setonce.given.TestEnv.DONALD
31-
import io.spine.test.options.setonce.given.TestEnv.EIGHTY_KG
32-
import io.spine.test.options.setonce.given.TestEnv.FIFTY_KG
33-
import io.spine.test.options.setonce.given.TestEnv.FIRST_YEAR
34-
import io.spine.test.options.setonce.given.TestEnv.CERT1
35-
import io.spine.test.options.setonce.given.TestEnv.JACK
36-
import io.spine.test.options.setonce.given.TestEnv.TALL_HEIGHT
37-
import io.spine.test.options.setonce.given.TestEnv.SHORT_HEIGHT
38-
import io.spine.test.options.setonce.given.TestEnv.NO
39-
import io.spine.test.options.setonce.given.TestEnv.CERT2
40-
import io.spine.test.options.setonce.given.TestEnv.STUDENT1
41-
import io.spine.test.options.setonce.given.TestEnv.STUDENT2
42-
import io.spine.test.options.setonce.given.TestEnv.THIRD_YEAR
43-
import io.spine.test.options.setonce.given.TestEnv.YES
30+
import io.spine.test.options.setonce.SetOnceTestEnv.DONALD
31+
import io.spine.test.options.setonce.SetOnceTestEnv.EIGHTY_KG
32+
import io.spine.test.options.setonce.SetOnceTestEnv.FIFTY_KG
33+
import io.spine.test.options.setonce.SetOnceTestEnv.FIRST_YEAR
34+
import io.spine.test.options.setonce.SetOnceTestEnv.CERT1
35+
import io.spine.test.options.setonce.SetOnceTestEnv.JACK
36+
import io.spine.test.options.setonce.SetOnceTestEnv.TALL_HEIGHT
37+
import io.spine.test.options.setonce.SetOnceTestEnv.SHORT_HEIGHT
38+
import io.spine.test.options.setonce.SetOnceTestEnv.NO
39+
import io.spine.test.options.setonce.SetOnceTestEnv.CERT2
40+
import io.spine.test.options.setonce.SetOnceTestEnv.STUDENT1
41+
import io.spine.test.options.setonce.SetOnceTestEnv.STUDENT2
42+
import io.spine.test.options.setonce.SetOnceTestEnv.THIRD_YEAR
43+
import io.spine.test.options.setonce.SetOnceTestEnv.YES
4444
import io.spine.test.tools.validate.StudentSetOnce
4545
import io.spine.test.tools.validate.studentSetOnce
4646
import io.spine.validation.assertions.assertValidationFails

0 commit comments

Comments
 (0)