2
2
@file:Repository(" https://repo.maven.apache.org/maven2/" )
3
3
@file:DependsOn(" io.github.typesafegithub:github-workflows-kt:3.2.0" )
4
4
@file:DependsOn(" it.krzeminski:snakeyaml-engine-kmp:3.1.0" )
5
+ @file:DependsOn(" io.github.optimumcode:json-schema-validator-jvm:0.3.1" )
5
6
6
7
@file:Repository(" https://bindings.krzeminski.it" )
7
8
@file:DependsOn(" actions:checkout:v4" )
8
9
@file:OptIn(ExperimentalKotlinLogicStep ::class )
9
10
@file:Suppress(" UNCHECKED_CAST" )
10
11
12
+ import io.github.optimumcode.json.schema.ErrorCollector
13
+ import io.github.optimumcode.json.schema.JsonSchema
14
+ import io.github.optimumcode.json.schema.ValidationError
11
15
import io.github.typesafegithub.workflows.actions.actions.Checkout
12
16
import io.github.typesafegithub.workflows.annotations.ExperimentalKotlinLogicStep
13
17
import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest
@@ -17,6 +21,11 @@ import io.github.typesafegithub.workflows.domain.triggers.Push
17
21
import io.github.typesafegithub.workflows.domain.triggers.Schedule
18
22
import io.github.typesafegithub.workflows.dsl.workflow
19
23
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
20
29
import java.io.File
21
30
import java.io.IOException
22
31
import java.net.URI
@@ -53,13 +62,13 @@ workflow(
53
62
}
54
63
55
64
job(
56
- id = " check_inputs_and_outputs " ,
57
- name = " Check inputs and outputs against action manifests " ,
65
+ id = " validate_typings " ,
66
+ name = " Validate typings " ,
58
67
runsOn = UbuntuLatest ,
59
68
) {
60
69
uses(action = Checkout ())
61
70
run (name = " Check for all actions" ) {
62
- checkInputAndOutputNames ()
71
+ validateTypings ()
63
72
}
64
73
}
65
74
@@ -97,7 +106,12 @@ private data class ActionCoords(
97
106
val pathToTypings : String ,
98
107
)
99
108
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
+
101
115
val notValidatedActions: List < (ActionCoords ) -> Boolean > = listOf (
102
116
// Doesn't have a major version branch/tag, and we keep the typings by the major version
103
117
{ it.owner == " DamianReeves" && it.name == " write-file-action" },
@@ -137,6 +151,15 @@ private fun checkInputAndOutputNames() {
137
151
}
138
152
139
153
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
+
140
163
val typingsInputs = if (" inputs" in typings) (typings[" inputs" ] as Map <String , Any >).keys else emptySet()
141
164
val typingsOutputs = if (" outputs" in typings) (typings[" outputs" ] as Map <String , Any >).keys else emptySet()
142
165
val manifest = fetchManifest(action)
@@ -192,6 +215,34 @@ private fun fetchManifest(action: ActionCoords): Map<String, Any>? {
192
215
}?.let { Load ().loadOne(string = it) } as Map <String , Any >?
193
216
}
194
217
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
+
195
246
private val ActionCoords .actionYmlUrl: String get() = " https://raw.githubusercontent.com/$owner /$name /$version$subName /action.yml"
196
247
197
248
private val ActionCoords .actionYamlUrl: String get() = " https://raw.githubusercontent.com/$owner /$name /$version$subName /action.yaml"
0 commit comments