Skip to content
This repository was archived by the owner on Aug 8, 2025. It is now read-only.

Commit 2fb8f36

Browse files
committed
Add gradle task to check YAML files adhere to JSON schema
1 parent d623369 commit 2fb8f36

7 files changed

Lines changed: 121 additions & 40 deletions

File tree

build.gradle.kts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
1-
/*
2-
* This file was generated by the Gradle 'init' task.
3-
*
4-
* This is a general purpose Gradle build.
5-
* To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.2.1/samples
6-
* This project uses @Incubating APIs which are subject to change.
7-
*/
81
val scalaVersion: String by project
92
val scalaSpecificVersion: String by project
103
val dataCatererVersion: String by project
114

12-
135
plugins {
146
scala
157
}
168

179
repositories {
18-
// Use Maven Central for resolving dependencies.
1910
mavenCentral()
2011
}
2112

@@ -25,3 +16,11 @@ dependencies {
2516
compileOnly("io.github.data-catering:data-caterer-api:$dataCatererVersion")
2617
}
2718

19+
tasks.register<ValidateYamlAgainstSchema>("validateYaml") {
20+
yamlDirectory.set(layout.projectDirectory.dir("docker/data/custom"))
21+
schemaFile.set(layout.projectDirectory.file("schema/data-caterer-latest.json"))
22+
}
23+
24+
tasks.build {
25+
dependsOn("validateYaml")
26+
}

buildSrc/build.gradle.kts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
plugins {
2+
kotlin("jvm") version "1.9.0" // Ensure Kotlin is applied
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
}
8+
9+
dependencies {
10+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2")
11+
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2")
12+
implementation("com.networknt:json-schema-validator:1.0.87")
13+
implementation(kotlin("stdlib-jdk8"))
14+
}
15+
16+
kotlin {
17+
jvmToolchain(17)
18+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import com.fasterxml.jackson.databind.JsonNode
2+
import com.fasterxml.jackson.databind.ObjectMapper
3+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
4+
import com.networknt.schema.JsonSchema
5+
import com.networknt.schema.JsonSchemaFactory
6+
import com.networknt.schema.SpecVersion
7+
import com.networknt.schema.ValidationMessage
8+
import org.gradle.api.DefaultTask
9+
import org.gradle.api.file.DirectoryProperty
10+
import org.gradle.api.file.RegularFileProperty
11+
import org.gradle.api.tasks.InputDirectory
12+
import org.gradle.api.tasks.InputFile
13+
import org.gradle.api.tasks.TaskAction
14+
import org.gradle.api.GradleException
15+
16+
abstract class ValidateYamlAgainstSchema : DefaultTask() {
17+
18+
@get:InputDirectory
19+
abstract val yamlDirectory: DirectoryProperty
20+
21+
@get:InputFile
22+
abstract val schemaFile: RegularFileProperty
23+
24+
@TaskAction
25+
fun validateYaml() {
26+
val objectMapper = ObjectMapper(YAMLFactory()) // Registers YAML module
27+
objectMapper.readTree(schemaFile.get().asFile.readText())
28+
val schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909) //Or V7, V201909, etc.
29+
val schemaNode: JsonNode = objectMapper.readTree(schemaFile.asFile.get())
30+
val schema: JsonSchema = schemaFactory.getSchema(schemaNode)
31+
32+
val validationResults = yamlDirectory.asFile.get().walk()
33+
.filter { it.isFile && (it.extension == "yaml" || it.extension == "yml") }
34+
.map { file ->
35+
try {
36+
val yamlNode: JsonNode = objectMapper.readTree(file)
37+
val validationResult: Set<ValidationMessage> = schema.validate(yamlNode)
38+
file to validationResult
39+
} catch (e: Exception) {
40+
println("Fail read or validation failed, path: ${file.absolutePath}, message: ${e.message}")
41+
file to setOf() // Create a validation message for errors
42+
}
43+
}
44+
.toList() // Evaluate and collect all results immediately
45+
46+
val failedValidations = validationResults.filter { it.second.isNotEmpty() }
47+
48+
if (failedValidations.isNotEmpty()) {
49+
val errorMessage = StringBuilder("YAML validation failed for the following files:\n")
50+
failedValidations.forEach { (file, errors) ->
51+
errorMessage.append(" - file://${file}:\n")
52+
errors.forEach { message ->
53+
errorMessage.append(" - ${message.message} (path: ${message.schemaPath})\n")
54+
}
55+
}
56+
throw GradleException(errorMessage.toString())
57+
} else {
58+
println("All YAML files validated successfully.")
59+
}
60+
}
61+
}

docker/data/custom/task/http/http-account-task.yaml

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,32 @@ steps:
44
count:
55
records: 50
66
fields:
7-
- name: "url"
8-
options:
9-
sql: "CONCAT('http://http:80/anything/', content.account_id)"
10-
- name: "method"
11-
static: "PUT"
12-
- name: "content_type"
13-
static: "application/json"
14-
- name: "headers"
15-
type: "array<struct<key: string, value: string>>"
16-
options:
17-
sql: >-
18-
array(
19-
named_struct('key', 'account-id', 'value', content.account_id),
20-
named_struct('key', 'updated', 'value', content.details.updated_by.time)
21-
)
22-
- name: "value"
23-
options:
24-
sql: "to_json(content)"
25-
- name: "content"
7+
- name: "httpUrl"
8+
type: struct
9+
fields:
10+
- name: "url"
11+
static: "http://localhost:80/anything/{id}"
12+
- name: "method"
13+
static: "PUT"
14+
- name: "pathParam"
15+
type: struct
16+
fields:
17+
- name: "id"
18+
options:
19+
sql: "body.account_id"
20+
- name: "httpHeaders"
21+
type: struct
22+
fields:
23+
- name: "Content-Type"
24+
static: "application/json"
25+
- name: "account_id"
26+
options:
27+
sql: "body.account_id"
28+
- name: "updated"
29+
type: "timestamp"
30+
options:
31+
sql: "body.details.updated_by.time"
32+
- name: "httpBody"
2633
type: struct
2734
fields:
2835
- name: "account_id"

docker/data/custom/task/jdbc/postgres/postgres-account-task.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ steps:
1212
regex: "ACC1[0-9]{5,10}"
1313
- name: "account_status"
1414
options:
15-
oneOf:
16-
- "open"
17-
- "closed"
15+
oneOf: ["open", "closed"]
1816
- name: "created_by"
1917
options:
2018
expression: "#{Name.username}"

docker/data/custom/task/kafka/kafka-account-task.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,6 @@ steps:
1313
- name: "value"
1414
options:
1515
sql: "to_json(content)"
16-
- name: "headers"
17-
type: "array<struct<key: string, value: binary>>"
18-
options:
19-
sql: >-
20-
array(
21-
named_struct('key', 'account-id', 'value', to_binary(content.account_id, 'utf-8')),
22-
named_struct('key', 'updated', 'value', to_binary(content.details.updated_by.time, 'utf-8'))
23-
)
2416
# - name: "partition"
2517
# type: "int"
2618
# options:

schema/data-caterer-latest.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,11 @@
728728
"description": "Generate only unique values.",
729729
"default": false
730730
},
731+
"isPrimaryKey": {
732+
"type": "boolean",
733+
"description": "Define the field as part of the primary key.",
734+
"default": false
735+
},
731736
"seed": {
732737
"type": "number",
733738
"description": "Seed for generating consistent random values.",
@@ -1678,7 +1683,8 @@
16781683
}
16791684
}
16801685
},
1681-
"additionalProperties": false
1686+
"required": ["field", "validation"],
1687+
"unevaluatedProperties": false
16821688
},
16831689
"BasicValidation": {
16841690
"type": "object",

0 commit comments

Comments
 (0)