Skip to content

Commit 7ace375

Browse files
authored
Validate typings against schema (#49)
1 parent 993ae54 commit 7ace375

File tree

2 files changed

+58
-7
lines changed

2 files changed

+58
-7
lines changed

.github/workflows/test.main.kts

+55-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
@file:Repository("https://repo.maven.apache.org/maven2/")
33
@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.2.0")
44
@file:DependsOn("it.krzeminski:snakeyaml-engine-kmp:3.1.0")
5+
@file:DependsOn("io.github.optimumcode:json-schema-validator-jvm:0.3.1")
56

67
@file:Repository("https://bindings.krzeminski.it")
78
@file:DependsOn("actions:checkout:v4")
89
@file:OptIn(ExperimentalKotlinLogicStep::class)
910
@file:Suppress("UNCHECKED_CAST")
1011

12+
import io.github.optimumcode.json.schema.ErrorCollector
13+
import io.github.optimumcode.json.schema.JsonSchema
14+
import io.github.optimumcode.json.schema.ValidationError
1115
import io.github.typesafegithub.workflows.actions.actions.Checkout
1216
import io.github.typesafegithub.workflows.annotations.ExperimentalKotlinLogicStep
1317
import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest
@@ -17,6 +21,11 @@ import io.github.typesafegithub.workflows.domain.triggers.Push
1721
import io.github.typesafegithub.workflows.domain.triggers.Schedule
1822
import io.github.typesafegithub.workflows.dsl.workflow
1923
import it.krzeminski.snakeyaml.engine.kmp.api.Load
24+
import kotlinx.serialization.json.JsonArray
25+
import kotlinx.serialization.json.JsonElement
26+
import kotlinx.serialization.json.JsonNull
27+
import kotlinx.serialization.json.JsonObject
28+
import kotlinx.serialization.json.JsonPrimitive
2029
import java.io.File
2130
import java.io.IOException
2231
import java.net.URI
@@ -53,13 +62,13 @@ workflow(
5362
}
5463

5564
job(
56-
id = "check_inputs_and_outputs",
57-
name = "Check inputs and outputs against action manifests",
65+
id = "validate_typings",
66+
name = "Validate typings",
5867
runsOn = UbuntuLatest,
5968
) {
6069
uses(action = Checkout())
6170
run(name = "Check for all actions") {
62-
checkInputAndOutputNames()
71+
validateTypings()
6372
}
6473
}
6574

@@ -97,7 +106,12 @@ private data class ActionCoords(
97106
val pathToTypings: String,
98107
)
99108

100-
private fun checkInputAndOutputNames() {
109+
private fun validateTypings() {
110+
val typingsSchema = JsonSchema.fromDefinition(
111+
URI.create("https://raw.githubusercontent.com/typesafegithub/github-actions-typing/" +
112+
"refs/heads/schema-latest/github-actions-typing.schema.json"
113+
).toURL().readText())
114+
101115
val notValidatedActions: List<(ActionCoords) -> Boolean> = listOf(
102116
// Doesn't have a major version branch/tag, and we keep the typings by the major version
103117
{ it.owner == "DamianReeves" && it.name == "write-file-action" },
@@ -137,6 +151,15 @@ private fun checkInputAndOutputNames() {
137151
}
138152

139153
val typings = loadTypings(path = action.pathToTypings)
154+
155+
val schemaComplianceErrors = typingsSchema.checkForSchemaComplianceErrors(typings)
156+
if (schemaComplianceErrors != null) {
157+
println("\uD83D\uDD34 Typings aren't compliant with the schema!")
158+
println(schemaComplianceErrors)
159+
shouldFail = true
160+
continue
161+
}
162+
140163
val typingsInputs = if ("inputs" in typings) (typings["inputs"] as Map<String, Any>).keys else emptySet()
141164
val typingsOutputs = if ("outputs" in typings) (typings["outputs"] as Map<String, Any>).keys else emptySet()
142165
val manifest = fetchManifest(action)
@@ -192,6 +215,34 @@ private fun fetchManifest(action: ActionCoords): Map<String, Any>? {
192215
}?.let { Load().loadOne(string = it) } as Map<String, Any>?
193216
}
194217

218+
private fun JsonSchema.checkForSchemaComplianceErrors(typings: Map<String, Any>): String? {
219+
var errorMessage: String? = null
220+
this.validate(typings.toJsonElement(), object : ErrorCollector {
221+
override fun onError(error: ValidationError) {
222+
errorMessage = buildString {
223+
appendLine("Error message: ${error.message}")
224+
appendLine("Object path: ${error.objectPath}")
225+
}
226+
}
227+
})
228+
return errorMessage
229+
}
230+
231+
// work-around for https://github.com/OptimumCode/json-schema-validator/issues/194 (direct support for Kotlin classes)
232+
// or https://github.com/OptimumCode/json-schema-validator/issues/195 (direct support for snakeyaml Node)
233+
// or https://github.com/OptimumCode/json-schema-validator/issues/190 (direct support for kaml YamlNode)
234+
private fun Any?.toJsonElement(): JsonElement {
235+
return when (this) {
236+
is Map<*, *> -> JsonObject(entries.associate { (key, value) -> "$key" to value.toJsonElement() })
237+
is List<*> -> JsonArray(map { it.toJsonElement() })
238+
is Boolean -> JsonPrimitive(this)
239+
is Number -> JsonPrimitive(this)
240+
is String -> JsonPrimitive(this)
241+
null -> JsonNull
242+
else -> error("Unexpected type: ${this::class.qualifiedName}")
243+
}
244+
}
245+
195246
private val ActionCoords.actionYmlUrl: String get() = "https://raw.githubusercontent.com/$owner/$name/$version$subName/action.yml"
196247

197248
private val ActionCoords.actionYamlUrl: String get() = "https://raw.githubusercontent.com/$owner/$name/$version$subName/action.yaml"

.github/workflows/test.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ jobs:
3939
echo "Compiling $file..."
4040
kotlinc -Werror -Xallow-any-scripts-in-source-roots -Xuse-fir-lt=false "$file"
4141
done
42-
check_inputs_and_outputs:
43-
name: 'Check inputs and outputs against action manifests'
42+
validate_typings:
43+
name: 'Validate typings'
4444
runs-on: 'ubuntu-latest'
4545
needs:
4646
- 'check_yaml_consistency'
@@ -51,7 +51,7 @@ jobs:
5151
name: 'Check for all actions'
5252
env:
5353
GHWKT_GITHUB_CONTEXT_JSON: '${{ toJSON(github) }}'
54-
run: 'GHWKT_RUN_STEP=''check_inputs_and_outputs:step-1'' ''.github/workflows/test.main.kts'''
54+
run: 'GHWKT_RUN_STEP=''validate_typings:step-1'' ''.github/workflows/test.main.kts'''
5555
workflows_consistency_check:
5656
name: 'Run consistency check on all GitHub workflows'
5757
runs-on: 'ubuntu-latest'

0 commit comments

Comments
 (0)