diff --git a/CHANGELOG.md b/CHANGELOG.md index 01759d3..829bea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ## Improvements 1. Improved the `exists` keyword documentation with a warning about an edge case. +2. Updated the error messages. Custom error messages provided in the JSON schema will now be appended to the original error messages instead of overwriting them. # Version 2.1.2 diff --git a/docs/nextflow_schema/nextflow_schema_specification.md b/docs/nextflow_schema/nextflow_schema_specification.md index fa2d9d5..f91ddcd 100644 --- a/docs/nextflow_schema/nextflow_schema_specification.md +++ b/docs/nextflow_schema/nextflow_schema_specification.md @@ -231,7 +231,7 @@ If validation fails, an error message is printed to the terminal, so that the en However, these messages are not always very clear - especially to newcomers. To improve this experience, pipeline developers can set a custom `errorMessage` for a given parameter in a the schema. -If validation fails, this `errorMessage` is printed instead, and the raw JSON schema validation message goes to the Nextflow debug log output. +If validation fails, this `errorMessage` is printed after the original error message to guide the pipeline users to an easier solution. For example, instead of printing: @@ -252,7 +252,7 @@ We can set and get: ``` -* --input (samples.yml): File name must end in '.csv' cannot contain spaces +* --input (samples.yml): "samples.yml" does not match regular expression [^\S+\.csv$] (File name must end in '.csv' cannot contain spaces) ``` ### `deprecated` diff --git a/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy b/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy index 8ced3d7..a0f0d85 100644 --- a/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy +++ b/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy @@ -41,7 +41,7 @@ public class JsonSchemaValidator { log.error("""Failed to load the meta schema: The used schema draft (${draft}) is not correct, please use \"https://json-schema.org/draft/2020-12/schema\" instead. - If you are a pipeline developer, check our migration guide for more information: https://nextflow-io.github.io/nf-schema/latest/migration_guide/ - - If you are a pipeline user, pin the previous version of the plugin (1.1.3) to avoid this error: https://www.nextflow.io/docs/latest/plugins.html#using-plugins, i.e. set `plugins { + - If you are a pipeline user, revert back to nf-validation to avoid this error: https://www.nextflow.io/docs/latest/plugins.html#using-plugins, i.e. set `plugins { id 'nf-validation@1.1.3' }` in your `nextflow.config` file """) @@ -50,11 +50,11 @@ public class JsonSchemaValidator { def Validator.Result result = this.validator.validate(schema, input) def List errors = [] - for (error : result.getErrors()) { + result.getErrors().each { error -> def String errorString = error.getError() // Skip double error in the parameter schema if (errorString.startsWith("Value does not match against the schemas at indexes") && validationType == "parameter") { - continue + return } def String instanceLocation = error.getInstanceLocation() @@ -68,38 +68,37 @@ public class JsonSchemaValidator { } // Change some error messages to make them more clear - if (customError == "") { - def String keyword = error.getKeyword() - if (keyword == "required") { - def Matcher matcher = errorString =~ ~/\[\[([^\[\]]*)\]\]$/ - def String missingKeywords = matcher.findAll().flatten().last() - customError = "Missing required ${validationType}(s): ${missingKeywords}" - } + def String keyword = error.getKeyword() + if (keyword == "required") { + def Matcher matcher = errorString =~ ~/\[\[([^\[\]]*)\]\]$/ + def String missingKeywords = matcher.findAll().flatten().last() + errorString = "Missing required ${validationType}(s): ${missingKeywords}" } def List locationList = instanceLocation.split("/").findAll { it != "" } as List + def String printableError = "${validationType == 'field' ? '->' : '*'} ${errorString}" as String if (locationList.size() > 0 && Utils.isInteger(locationList[0]) && validationType == "field") { def Integer entryInteger = locationList[0] as Integer def String entryString = "Entry ${entryInteger + 1}" as String - def String fieldError = "" + def String fieldError = "${errorString}" as String if(locationList.size() > 1) { - fieldError = "Error for ${validationType} '${locationList[1..-1].join("/")}' (${value}): ${customError ?: errorString}" - } else { - fieldError = "${customError ?: errorString}" as String + fieldError = "Error for ${validationType} '${locationList[1..-1].join("/")}' (${value}): ${errorString}" } - errors.add("-> ${entryString}: ${fieldError}" as String) + printableError = "-> ${entryString}: ${fieldError}" as String } else if (validationType == "parameter") { def String fieldName = locationList.join(".") if(fieldName != "") { - errors.add("* --${fieldName} (${value}): ${customError ?: errorString}" as String) - } else { - errors.add("* ${customError ?: errorString}" as String) + printableError = "* --${fieldName} (${value}): ${errorString}" as String } - } else { - errors.add("-> ${customError ?: errorString}" as String) } + if(customError != "") { + printableError = printableError + " (${customError})" + } + + errors.add(printableError) + } return errors } diff --git a/plugins/nf-schema/src/test/nextflow/validation/ValidateParametersTest.groovy b/plugins/nf-schema/src/test/nextflow/validation/ValidateParametersTest.groovy index 8636ac0..a640791 100644 --- a/plugins/nf-schema/src/test/nextflow/validation/ValidateParametersTest.groovy +++ b/plugins/nf-schema/src/test/nextflow/validation/ValidateParametersTest.groovy @@ -238,12 +238,12 @@ class ValidateParametersTest extends Dsl2Spec{ errorMessages[0] == "\033[0;31mThe following invalid input values have been detected:" errorMessages[1] == "" errorMessages[2] == "* --input (src/testResources/wrong.csv): Validation of file failed:" - errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded'" + errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Expected any of [[forward, reverse, unstranded]] (Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded')" errorMessages[4] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" does not match regular expression [^\\S+\\.f(ast)?q\\.gz\$]" errorMessages[5] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" is longer than 0 characters" - errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): Value does not match against any of the schemas (FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz')" errorMessages[7] == "\t-> Entry 1: Missing required field(s): sample" - errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): Sample name must be provided and cannot contain spaces" + errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): \"test 2\" does not match regular expression [^\\S+\$] (Sample name must be provided and cannot contain spaces)" !stdout } @@ -272,12 +272,12 @@ class ValidateParametersTest extends Dsl2Spec{ errorMessages[0] == "\033[0;31mThe following invalid input values have been detected:" errorMessages[1] == "" errorMessages[2] == "* --input (src/testResources/wrong.tsv): Validation of file failed:" - errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded'" + errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Expected any of [[forward, reverse, unstranded]] (Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded')" errorMessages[4] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" does not match regular expression [^\\S+\\.f(ast)?q\\.gz\$]" errorMessages[5] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" is longer than 0 characters" - errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): Value does not match against any of the schemas (FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz')" errorMessages[7] == "\t-> Entry 1: Missing required field(s): sample" - errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): Sample name must be provided and cannot contain spaces" + errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): \"test 2\" does not match regular expression [^\\S+\$] (Sample name must be provided and cannot contain spaces)" !stdout } @@ -306,12 +306,12 @@ class ValidateParametersTest extends Dsl2Spec{ errorMessages[0] == "\033[0;31mThe following invalid input values have been detected:" errorMessages[1] == "" errorMessages[2] == "* --input (src/testResources/wrong.yaml): Validation of file failed:" - errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded'" + errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Expected any of [[forward, reverse, unstranded]] (Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded')" errorMessages[4] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" does not match regular expression [^\\S+\\.f(ast)?q\\.gz\$]" errorMessages[5] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" is longer than 0 characters" - errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): Value does not match against any of the schemas (FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz')" errorMessages[7] == "\t-> Entry 1: Missing required field(s): sample" - errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): Sample name must be provided and cannot contain spaces" + errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): \"test 2\" does not match regular expression [^\\S+\$] (Sample name must be provided and cannot contain spaces)" !stdout } @@ -340,12 +340,12 @@ class ValidateParametersTest extends Dsl2Spec{ errorMessages[0] == "\033[0;31mThe following invalid input values have been detected:" errorMessages[1] == "" errorMessages[2] == "* --input (src/testResources/wrong.json): Validation of file failed:" - errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded'" + errorMessages[3] == "\t-> Entry 1: Error for field 'strandedness' (weird): Expected any of [[forward, reverse, unstranded]] (Strandedness must be provided and be one of 'forward', 'reverse' or 'unstranded')" errorMessages[4] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" does not match regular expression [^\\S+\\.f(ast)?q\\.gz\$]" errorMessages[5] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): \"test1_fastq2.fasta\" is longer than 0 characters" - errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + errorMessages[6] == "\t-> Entry 1: Error for field 'fastq_2' (test1_fastq2.fasta): Value does not match against any of the schemas (FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz')" errorMessages[7] == "\t-> Entry 1: Missing required field(s): sample" - errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): Sample name must be provided and cannot contain spaces" + errorMessages[8] == "\t-> Entry 2: Error for field 'sample' (test 2): \"test 2\" does not match regular expression [^\\S+\$] (Sample name must be provided and cannot contain spaces)" !stdout } @@ -868,13 +868,11 @@ class ValidateParametersTest extends Dsl2Spec{ then: def error = thrown(SchemaValidationException) - error.message == """The following invalid input values have been detected: - -* --input (src/testResources/samplesheet_wrong_pattern.csv): Validation of file failed: -\t-> Entry 1: Error for field 'fastq_1' (test1_fastq1.txt): FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz' -\t-> Entry 2: Error for field 'fastq_1' (test2_fastq1.txt): FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz' - -""" + def errorMessage = error.message.tokenize("\n") + errorMessage[0] == "The following invalid input values have been detected:" + errorMessage[1] == "* --input (src/testResources/samplesheet_wrong_pattern.csv): Validation of file failed:" + errorMessage[2] == "\t-> Entry 1: Error for field 'fastq_1' (test1_fastq1.txt): \"test1_fastq1.txt\" does not match regular expression [^\\S+\\.f(ast)?q\\.gz\$] (FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz')" + errorMessage[3] == "\t-> Entry 2: Error for field 'fastq_1' (test2_fastq1.txt): \"test2_fastq1.txt\" does not match regular expression [^\\S+\\.f(ast)?q\\.gz\$] (FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz')" !stdout }