Skip to content

Commit 6027327

Browse files
committed
Detect JSON Schema draft from the loaded schema [ci fast]
Replace the hardcoded SpecVersion.VersionFlag.V202012 with SpecVersionDetector.detect, so the validator follows whatever draft the schema declares via $schema and aborts with a clear message if the draft is missing or unsupported. Per review feedback on #7094. Decompose validate() into self-contained helpers (parseSchema, detectSpecVersion, buildSchema, loadMeta), each with its own contextual error handling. Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
1 parent 369963f commit 6027327

2 files changed

Lines changed: 57 additions & 13 deletions

File tree

modules/nextflow/src/main/groovy/nextflow/module/ModuleSchemaValidator.groovy

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
2525
import com.networknt.schema.JsonSchema
2626
import com.networknt.schema.JsonSchemaFactory
2727
import com.networknt.schema.SpecVersion
28+
import com.networknt.schema.SpecVersionDetector
2829
import com.networknt.schema.ValidationMessage
2930
import groovy.transform.CompileStatic
3031
import groovy.util.logging.Slf4j
@@ -54,31 +55,54 @@ class ModuleSchemaValidator {
5455
* @return List of validation error messages, empty if the spec is valid
5556
*/
5657
static List<String> validate(Path metaYaml, String schemaLocation) {
57-
final schemaText = loadSchema(schemaLocation)
58-
final factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012)
59-
final JsonSchema schema
58+
final schemaNode = parseSchema(loadSchema(schemaLocation), schemaLocation)
59+
final specVersion = detectSpecVersion(schemaNode, schemaLocation)
60+
final schema = buildSchema(schemaNode, specVersion, schemaLocation)
61+
final metaNode = loadMeta(metaYaml)
62+
final Set<ValidationMessage> messages = schema.validate(metaNode)
63+
return messages.collect { it.message }.toList()
64+
}
65+
66+
static List<String> validate(Path metaYaml) {
67+
return validate(metaYaml, DEFAULT_SCHEMA_URL)
68+
}
69+
70+
private static JsonNode parseSchema(String schemaText, String schemaLocation) {
6071
try {
61-
schema = factory.getSchema(schemaText)
72+
return JSON_MAPPER.readTree(schemaText)
6273
}
6374
catch( Exception e ) {
6475
throw new AbortOperationException("Invalid module schema at '${schemaLocation}': ${e.message}", e)
6576
}
77+
}
6678

67-
Object yamlData
68-
try( final stream = Files.newInputStream(metaYaml) ) {
69-
yamlData = new Yaml().load(stream)
79+
private static SpecVersion.VersionFlag detectSpecVersion(JsonNode schemaNode, String schemaLocation) {
80+
try {
81+
return SpecVersionDetector.detect(schemaNode)
7082
}
7183
catch( Exception e ) {
72-
throw new AbortOperationException("Failed to read module spec '${metaYaml}': ${e.message}", e)
84+
throw new AbortOperationException(
85+
"Cannot determine JSON Schema draft for '${schemaLocation}': ${e.message}. " +
86+
"The schema must declare a supported \$schema (e.g. https://json-schema.org/draft/2020-12/schema).", e)
7387
}
88+
}
7489

75-
final JsonNode node = JSON_MAPPER.valueToTree(yamlData)
76-
final Set<ValidationMessage> messages = schema.validate(node)
77-
return messages.collect { it.message }.toList()
90+
private static JsonSchema buildSchema(JsonNode schemaNode, SpecVersion.VersionFlag specVersion, String schemaLocation) {
91+
try {
92+
return JsonSchemaFactory.getInstance(specVersion).getSchema(schemaNode)
93+
}
94+
catch( Exception e ) {
95+
throw new AbortOperationException("Invalid module schema at '${schemaLocation}': ${e.message}", e)
96+
}
7897
}
7998

80-
static List<String> validate(Path metaYaml) {
81-
return validate(metaYaml, DEFAULT_SCHEMA_URL)
99+
private static JsonNode loadMeta(Path metaYaml) {
100+
try( final stream = Files.newInputStream(metaYaml) ) {
101+
return JSON_MAPPER.valueToTree(new Yaml().load(stream))
102+
}
103+
catch( Exception e ) {
104+
throw new AbortOperationException("Failed to read module spec '${metaYaml}': ${e.message}", e)
105+
}
82106
}
83107

84108
/**

modules/nextflow/src/test/groovy/nextflow/module/ModuleSchemaValidatorTest.groovy

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,24 @@ class ModuleSchemaValidatorTest extends Specification {
144144
def e = thrown(AbortOperationException)
145145
e.message.contains('Failed to load module schema')
146146
}
147+
148+
def 'should hard-fail when the schema does not declare a supported draft' () {
149+
given:
150+
def schema = writeSchema('''\
151+
{
152+
"type": "object",
153+
"properties": {
154+
"name": { "type": "string" }
155+
}
156+
}
157+
'''.stripIndent())
158+
def meta = writeMeta('name: x\n')
159+
160+
when:
161+
ModuleSchemaValidator.validate(meta, schema.toString())
162+
163+
then:
164+
def e = thrown(AbortOperationException)
165+
e.message.contains('Cannot determine JSON Schema draft')
166+
}
147167
}

0 commit comments

Comments
 (0)