Skip to content

Commit 090285b

Browse files
committed
fix: use snakeyaml-engine-kmp for validation
1 parent 3fa95b9 commit 090285b

30 files changed

+130
-116
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ kotlin {
131131
jvmMain {
132132
dependencies {
133133
implementation("com.charleskorn.kaml:kaml:0.72.0")
134+
implementation("it.krzeminski:snakeyaml-engine-kmp:3.1.1")
134135
}
135136
}
136137

src/jvmMain/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package it.krzeminski.githubactionstyping
22

3-
import it.krzeminski.githubactionstyping.parsing.TypesManifest
3+
import it.krzeminski.githubactionstyping.parsing.ValidationException
44
import it.krzeminski.githubactionstyping.parsing.parseManifest
55
import it.krzeminski.githubactionstyping.parsing.parseTypesManifest
66
import it.krzeminski.githubactionstyping.reporting.appendStatus
@@ -25,11 +25,14 @@ fun manifestsToReport(manifestAndPath: Pair<String, Path>?, typesManifest: Strin
2525
})
2626
}
2727

28-
val parsedTypesManifest = if (typesManifest.isNotBlank()) {
28+
val parsedTypesManifest =
2929
parseTypesManifest(typesManifest)
30-
} else {
31-
TypesManifest()
32-
}
30+
.onFailure {
31+
if (it is ValidationException) {
32+
return false to it.message
33+
}
34+
throw it
35+
}.getOrThrow()
3336
val parsedManifest = parseManifest(manifestAndPath.first)
3437

3538
val inputsInTypesManifest = parsedTypesManifest.inputs?.keys ?: emptySet()
Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,99 @@
11
package it.krzeminski.githubactionstyping.parsing
22

3-
import com.charleskorn.kaml.AnchorsAndAliases
4-
import com.charleskorn.kaml.EmptyYamlDocumentException
5-
import com.charleskorn.kaml.Yaml
6-
import kotlinx.serialization.SerialName
7-
import kotlinx.serialization.Serializable
8-
import kotlinx.serialization.decodeFromString
9-
10-
@Serializable
3+
import it.krzeminski.snakeyaml.engine.kmp.api.Load
4+
import it.krzeminski.snakeyaml.engine.kmp.api.LoadSettings
5+
import it.krzeminski.snakeyaml.engine.kmp.schema.CoreSchema
6+
117
data class TypesManifest(
128
val inputs: Map<String, ApiItem>? = null,
139
val outputs: Map<String, ApiItem>? = null,
1410
)
1511

16-
@Serializable
1712
data class ApiItem(
1813
val type: String? = null,
1914
val name: String? = null,
20-
@SerialName("allowed-values")
2115
val allowedValues: List<String>? = null,
2216
val separator: String? = null,
23-
@SerialName("named-values")
24-
val namedValues: Map<String, String>? = null,
25-
@SerialName("list-item")
17+
val namedValues: Map<String, Int>? = null,
2618
val listItem: ApiItem? = null,
2719
)
2820

29-
private val myYaml = Yaml(
30-
configuration = Yaml.default.configuration.copy(
31-
strictMode = false,
32-
anchorsAndAliases = AnchorsAndAliases.Permitted(),
33-
)
34-
)
35-
36-
fun parseTypesManifest(manifestString: String): TypesManifest =
21+
fun parseTypesManifest(manifestString: String): Result<TypesManifest> =
3722
runCatching {
38-
myYaml.decodeFromString<TypesManifest>(manifestString)
39-
}.getOrElse {
40-
if (it is EmptyYamlDocumentException) {
41-
return TypesManifest()
23+
val loadedTypesManifest = Load(
24+
// work-around for https://github.com/krzema12/snakeyaml-engine-kmp/pull/390
25+
LoadSettings.builder().setSchema(CoreSchema()).build()
26+
).loadOne(manifestString)
27+
28+
when (loadedTypesManifest) {
29+
null -> TypesManifest()
30+
31+
is Map<*, *> -> {
32+
TypesManifest(
33+
inputs = loadedTypesManifest.toInputsOrOutputs("inputs"),
34+
outputs = loadedTypesManifest.toInputsOrOutputs("outputs"),
35+
)
36+
}
37+
38+
else -> throw ValidationException("Types file must be a mapping.")
39+
}
40+
}
41+
42+
private fun Map<*, *>.toInputsOrOutputs(key: String): Map<String, ApiItem>? =
43+
when (val inputsOrOutputs = this[key]) {
44+
null -> null
45+
46+
is Map<*, *> -> {
47+
inputsOrOutputs.entries.associate { (key, value) -> key as String to value.toApiItem(key) }
4248
}
43-
throw it
49+
50+
else -> throw ValidationException("$key must be a mapping.")
4451
}
52+
53+
private fun Any?.toApiItem(key: String): ApiItem =
54+
when (this) {
55+
null -> ApiItem()
56+
57+
is Map<*, *> -> {
58+
ApiItem(
59+
type = get("type")?.let {
60+
"$it"
61+
},
62+
name = get("name")?.let {
63+
"$it"
64+
},
65+
allowedValues = get("allowed-values")?.let {
66+
if (it !is List<*>) {
67+
throw ValidationException("allowed-values must be a sequence.")
68+
}
69+
it.map {
70+
"$it"
71+
}
72+
},
73+
separator = get("separator")?.let {
74+
if (it !is String) {
75+
throw ValidationException("separator must be string.")
76+
}
77+
it
78+
},
79+
namedValues = get("named-values")?.let {
80+
if (it !is Map<*, *>) {
81+
throw ValidationException("named-values must be a mapping.")
82+
}
83+
it.mapKeys { (key, _) -> key as String }.mapValues { (_, value) ->
84+
if (value !is Int) {
85+
throw ValidationException("Named values must be integer.")
86+
}
87+
value
88+
}
89+
},
90+
listItem = toApiItem("list-item"),
91+
)
92+
}
93+
94+
else -> throw ValidationException("$key must be a mapping.")
95+
}
96+
97+
private fun Map<*, *>.toApiItem(key: String): ApiItem? = get(key)?.toApiItem(key)
98+
99+
internal class ValidationException(override val message: String) : Exception(message)

src/jvmMain/kotlin/it/krzeminski/githubactionstyping/validation/types/Integer.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ fun ApiItem.validateInteger(): ItemValidationResult {
1313
if (this.namedValues?.keys?.any { it.isBlank() } == true) {
1414
return ItemValidationResult.Invalid("Named value names must not be empty.")
1515
}
16-
if (this.namedValues?.values?.any { it.toIntOrNull() == null } == true) {
17-
return ItemValidationResult.Invalid("Named values must be integer.")
18-
}
1916
if (this.name != null && this.name.isBlank()) {
2017
return ItemValidationResult.Invalid("Name must not be empty.")
2118
}

src/jvmTest/kotlin/it/krzeminski/githubactionstyping/LogicConsistencyTest.kt

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package it.krzeminski.githubactionstyping
22

3-
import com.charleskorn.kaml.IncorrectTypeException
4-
import com.charleskorn.kaml.InvalidPropertyValueException
53
import io.kotest.assertions.assertSoftly
64
import io.kotest.assertions.withClue
75
import io.kotest.matchers.should
86
import io.kotest.matchers.shouldBe
97
import io.kotest.matchers.types.beOfType
8+
import it.krzeminski.githubactionstyping.parsing.ValidationException
109
import it.krzeminski.githubactionstyping.parsing.parseTypesManifest
1110
import it.krzeminski.githubactionstyping.reporting.toPlaintextReport
1211
import it.krzeminski.githubactionstyping.validation.ItemValidationResult
@@ -18,7 +17,7 @@ import java.io.File
1817
*/
1918
class LogicConsistencyTest : UseCaseTest() {
2019
override suspend fun testValid(typing: File) {
21-
val validationResult = parseTypesManifest(typing.readText()).validate(typing.toPath().fileName)
20+
val validationResult = parseTypesManifest(typing.readText()).getOrThrow().validate(typing.toPath().fileName)
2221
withClue(validationResult.toPlaintextReport()) {
2322
validationResult.overallResult shouldBe ItemValidationResult.Valid
2423
}
@@ -31,15 +30,11 @@ class LogicConsistencyTest : UseCaseTest() {
3130
.trimMargin("#")
3231
.trim()
3332

34-
val parsedTypesManifest = runCatching {
35-
parseTypesManifest(typesManifest).validate(typing.toPath().fileName)
36-
}
37-
if (parsedTypesManifest.isFailure &&
38-
((parsedTypesManifest.exceptionOrNull() is InvalidPropertyValueException) || (parsedTypesManifest.exceptionOrNull() is IncorrectTypeException))
39-
) {
33+
val parsedTypesManifest = parseTypesManifest(typesManifest)
34+
if (parsedTypesManifest.isFailure && parsedTypesManifest.exceptionOrNull() is ValidationException) {
4035
parsedTypesManifest.exceptionOrNull()!!.message shouldBe expectedValidationError
4136
} else {
42-
val validationResult = parsedTypesManifest.getOrThrow()
37+
val validationResult = parsedTypesManifest.getOrThrow().validate(typing.toPath().fileName)
4338
assertSoftly {
4439
validationResult.overallResult should beOfType<ItemValidationResult.Invalid>()
4540
validationResult

src/jvmTest/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ class ManifestValidationTest : FunSpec({
1717
"integer-input" to ApiItem(type = "integer"),
1818
"integer-with-named-values-input" to ApiItem(
1919
type = "integer",
20-
namedValues = mapOf("foo" to "1", "bar" to "2")
20+
namedValues = mapOf("foo" to 1, "bar" to 2)
2121
),
2222
"integer-with-named-values-and-custom-item-name-input" to ApiItem(
2323
type = "integer",
2424
name = "SomeItemName",
25-
namedValues = mapOf("foo" to "1", "bar" to "2")
25+
namedValues = mapOf("foo" to 1, "bar" to 2)
2626
),
2727
"float-input" to ApiItem(type = "float"),
2828
),
@@ -481,11 +481,11 @@ class ManifestValidationTest : FunSpec({
481481
// given
482482
val manifest = TypesManifest(
483483
inputs = mapOf(
484-
"string-input" to ApiItem(type = "string", namedValues = mapOf("foo" to "1")),
485-
"boolean-input" to ApiItem(type = "boolean", namedValues = mapOf("foo" to "1")),
486-
"float-input" to ApiItem(type = "float", namedValues = mapOf("foo" to "1")),
487-
"list-input" to ApiItem(type = "list", separator = ",", listItem = ApiItem(type = "string"), namedValues = mapOf("foo" to "1")),
488-
"enum-input" to ApiItem(type = "enum", allowedValues = listOf("foo", "bar"), namedValues = mapOf("foo" to "1")),
484+
"string-input" to ApiItem(type = "string", namedValues = mapOf("foo" to 1)),
485+
"boolean-input" to ApiItem(type = "boolean", namedValues = mapOf("foo" to 1)),
486+
"float-input" to ApiItem(type = "float", namedValues = mapOf("foo" to 1)),
487+
"list-input" to ApiItem(type = "list", separator = ",", listItem = ApiItem(type = "string"), namedValues = mapOf("foo" to 1)),
488+
"enum-input" to ApiItem(type = "enum", allowedValues = listOf("foo", "bar"), namedValues = mapOf("foo" to 1)),
489489
),
490490
)
491491

src/test/resources/bad-typings/inputs_enum_list_item_with_non_sequence_allowed_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ inputs:
1010

1111
# Expected validation error
1212
#
13-
#Value for 'inputs' is invalid: Value for 'granted-scopes' is invalid: Value for 'list-item' is invalid: Value for 'allowed-values' is invalid: Expected a list, but got a scalar value
13+
#allowed-values must be a sequence.
1414
#

src/test/resources/bad-typings/inputs_enum_with_non_sequence_allowed_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ inputs:
77

88
# Expected validation error
99
#
10-
#Value for 'inputs' is invalid: Value for 'permissions' is invalid: Value for 'allowed-values' is invalid: Expected a list, but got a scalar value
10+
#allowed-values must be a sequence.
1111
#

src/test/resources/bad-typings/inputs_integer_list_item_with_non_integer_named_value.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,9 @@ inputs:
88
type: integer
99
name: AllowedValues
1010
named-values:
11-
foo: bar
11+
foo: '0'
1212

1313
# Expected validation error
1414
#
15-
#For action with manifest at 'inputs_integer_list_item_with_non_integer_named_value.yml':
16-
#Result:
17-
#\x1B[31m❌ INVALID: Some typing is invalid.\x1B[0m
18-
#
19-
#Inputs:
20-
#• list-of-integer:
21-
# \x1B[31m❌ INVALID: List item type: Named values must be integer.\x1B[0m
22-
#
23-
#Outputs:
24-
#None.
15+
#Named values must be integer.
2516
#

src/test/resources/bad-typings/inputs_integer_list_item_with_non_mapping_named_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ inputs:
1111

1212
# Expected validation error
1313
#
14-
#Value for 'inputs' is invalid: Value for 'list-of-integer' is invalid: Value for 'list-item' is invalid: Value for 'named-values' is invalid: Expected a map, but got a list
14+
#named-values must be a mapping.
1515
#

src/test/resources/bad-typings/inputs_integer_with_non_integer_named_value.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,9 @@ inputs:
55
type: integer
66
name: AllowedValues
77
named-values:
8-
foo: bar
8+
foo: '0'
99

1010
# Expected validation error
1111
#
12-
#For action with manifest at 'inputs_integer_with_non_integer_named_value.yml':
13-
#Result:
14-
#\x1B[31m❌ INVALID: Some typing is invalid.\x1B[0m
15-
#
16-
#Inputs:
17-
#• retries:
18-
# \x1B[31m❌ INVALID: Named values must be integer.\x1B[0m
19-
#
20-
#Outputs:
21-
#None.
12+
#Named values must be integer.
2213
#

src/test/resources/bad-typings/inputs_integer_with_non_mapping_named_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ inputs:
88

99
# Expected validation error
1010
#
11-
#Value for 'inputs' is invalid: Value for 'retries' is invalid: Value for 'named-values' is invalid: Expected a map, but got a list
11+
#named-values must be a mapping.
1212
#

src/test/resources/bad-typings/inputs_with_non_string_type.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ inputs:
1212
#
1313
#Inputs:
1414
#• permissions:
15-
# \x1B[31m❌ INVALID: Unknown type: '0x0'.\x1B[0m
15+
# \x1B[31m❌ INVALID: Unknown type: '0'.\x1B[0m
1616
#
1717
#Outputs:
1818
#None.

src/test/resources/bad-typings/outputs_enum_list_item_with_non_sequence_allowed_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ outputs:
1010

1111
# Expected validation error
1212
#
13-
#Value for 'outputs' is invalid: Value for 'granted-scopes' is invalid: Value for 'list-item' is invalid: Value for 'allowed-values' is invalid: Expected a list, but got a scalar value
13+
#allowed-values must be a sequence.
1414
#

src/test/resources/bad-typings/outputs_enum_with_non_sequence_allowed_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ outputs:
77

88
# Expected validation error
99
#
10-
#Value for 'outputs' is invalid: Value for 'permissions' is invalid: Value for 'allowed-values' is invalid: Expected a list, but got a scalar value
10+
#allowed-values must be a sequence.
1111
#

src/test/resources/bad-typings/outputs_integer_list_item_with_non_integer_named_value.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,9 @@ outputs:
88
type: integer
99
name: AllowedValues
1010
named-values:
11-
foo: bar
11+
foo: '0'
1212

1313
# Expected validation error
1414
#
15-
#For action with manifest at 'outputs_integer_list_item_with_non_integer_named_value.yml':
16-
#Result:
17-
#\x1B[31m❌ INVALID: Some typing is invalid.\x1B[0m
18-
#
19-
#Inputs:
20-
#None.
21-
#
22-
#Outputs:
23-
#• list-of-integer:
24-
# \x1B[31m❌ INVALID: List item type: Named values must be integer.\x1B[0m
15+
#Named values must be integer.
2516
#

src/test/resources/bad-typings/outputs_integer_list_item_with_non_mapping_named_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ outputs:
1010

1111
# Expected validation error
1212
#
13-
#Value for 'outputs' is invalid: Value for 'list-of-integer' is invalid: Value for 'list-item' is invalid: Value for 'named-values' is invalid: Expected a map, but got a list
13+
#named-values must be a mapping.
1414
#

src/test/resources/bad-typings/outputs_integer_with_non_integer_named_value.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,9 @@ outputs:
55
type: integer
66
name: AllowedValues
77
named-values:
8-
foo: bar
8+
foo: '0'
99

1010
# Expected validation error
1111
#
12-
#For action with manifest at 'outputs_integer_with_non_integer_named_value.yml':
13-
#Result:
14-
#\x1B[31m❌ INVALID: Some typing is invalid.\x1B[0m
15-
#
16-
#Inputs:
17-
#None.
18-
#
19-
#Outputs:
20-
#• retries:
21-
# \x1B[31m❌ INVALID: Named values must be integer.\x1B[0m
12+
#Named values must be integer.
2213
#

src/test/resources/bad-typings/outputs_integer_with_non_mapping_named_values.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ outputs:
77

88
# Expected validation error
99
#
10-
#Value for 'outputs' is invalid: Value for 'retries' is invalid: Value for 'named-values' is invalid: Expected a map, but got a list
10+
#named-values must be a mapping.
1111
#

0 commit comments

Comments
 (0)