From d7d98d0efd120d94573d7559ab779359e742700f Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 10:05:03 +0100 Subject: [PATCH 01/13] fix(parameters): validate the value of Channel and Value params instead of hanging validateParameters serialised session.params wholesale. A typed Channel or Value param holds a live dataflow object, and reading it blocks forever, so any pipeline declaring one hung at the start of parameter validation. A dataflow-valued param is replaced by the value it was created from (given on the command line, else set in the params config scope), also when nested in a record param, so a samplesheet path is still validated against its schema. A param without such a value is left out. Fixes #229 Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + .../parameters/ParameterValidator.groovy | 42 +++++- .../validation/ValidateParametersTest.groovy | 123 ++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0602875..88832e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This version contains some breaking changes to the nf-schema API. See the [migra 1. Fixed an issue where the summary creation functions would fail if the default of a parameter was set in the schema, but not in the pipeline. 2. Fixed an issue where parameters with the `Path` type containing a remote file would show the wrong file path in the summary. +3. Fixed an issue where `validateParameters()` would hang when a parameter holds a `Channel` or `Value` (e.g. a typed `Channel<...>` param). The value the parameter was created from (given on the command line, otherwise set in the `params` config scope) is validated in its place, also when the dataflow value is nested in a record parameter. A parameter without such a value is left out. # Version 2.8.0 diff --git a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy index 34d4ee2b..7e133a9d 100644 --- a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy +++ b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy @@ -124,7 +124,7 @@ class ParameterValidator { final Map options = [:], Session session ) { - Map params = initialiseExpectedParams(session.params) + Map params = replaceDataflowParams(initialiseExpectedParams(session.params), session) String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema as String @@ -214,6 +214,46 @@ class ParameterValidator { private List getWarnings() { return warnings } + // + // Matched by package because the dataflow classes are not on the plugin's compile classpath + // + private static boolean isDataflowValue(Object value) { + String className = value?.getClass()?.name ?: '' + return className.startsWith('groovyx.gpars.dataflow.') || className.startsWith('nextflow.dataflow.') + } + + // + // Channel and Value params hold live dataflow objects, and serialising them blocks forever. + // The value they were created from (given on the command line, else set in the config) is + // validated in their place, and a param without one is left out. Params nested in a record + // (e.g. the params of an included pipeline) are handled the same way. + // + private Map replaceDataflowParams(Map params, Session session) { + return replaceDataflowValues(params, session.cliParams, session.config?.params) as Map + } + + private Object replaceDataflowValues(Object value, Object cliValue, Object configValue) { + if (isDataflowValue(value)) { + Object source = cliValue != null ? cliValue : configValue + return source != null && !isDataflowValue(source) ? source : null + } + if (value instanceof Map) { + Map result = [:] + (value as Map).each { Object name, Object entry -> + Object replaced = replaceDataflowValues( + entry, + cliValue instanceof Map ? (cliValue as Map)[name] : null, + configValue instanceof Map ? (configValue as Map)[name] : null + ) + if (replaced != null || !isDataflowValue(entry)) { + result[name] = replaced + } + } + return result + } + return value + } + // // Initialise expected params if not present // diff --git a/src/test/groovy/nextflow/validation/ValidateParametersTest.groovy b/src/test/groovy/nextflow/validation/ValidateParametersTest.groovy index 365da4df..c7856c8b 100644 --- a/src/test/groovy/nextflow/validation/ValidateParametersTest.groovy +++ b/src/test/groovy/nextflow/validation/ValidateParametersTest.groovy @@ -5,6 +5,7 @@ package nextflow.validation import static test.ScriptHelper.runScript import groovy.transform.CompileDynamic +import spock.lang.Timeout import java.nio.file.Path @@ -106,6 +107,128 @@ class ValidateParametersTest extends Dsl2Spec { !stdout } + @Timeout(60) + void 'should not block on a param that holds a dataflow value'() { + given: + String schema = Path.of('src/testResources/nextflow_schema.json').toAbsolutePath() + String script = """ + include { validateParameters } from 'plugin/nf-schema' + workflow { + params.input = new groovyx.gpars.dataflow.DataflowVariable() + params.outdir = 'src/testResources/testDir' + validateParameters(parameters_schema: '${schema}') + } + """ + + when: + Map opts = ['config': ['validation': [ + 'monochromeLogs': true + ]]] + runScript(opts, script) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message == '''The following invalid input values have been detected: + +* Missing required parameter(s): input + +''' + } + + @Timeout(60) + void 'should accept the config value of a param that holds a dataflow value'() { + given: + String schema = Path.of('src/testResources/nextflow_schema.json').toAbsolutePath() + String script = """ + include { validateParameters } from 'plugin/nf-schema' + workflow { + params.input = new groovyx.gpars.dataflow.DataflowVariable() + params.outdir = 'src/testResources/testDir' + validateParameters(parameters_schema: '${schema}') + } + """ + + when: + Map opts = ['config': [ + 'validation': ['monochromeLogs': true], + 'params': ['input': 'src/testResources/correct.csv'] + ]] + runScript(opts, script) + + then: + noExceptionThrown() + } + + @Timeout(60) + void 'should reject an invalid config value of a param that holds a dataflow value'() { + given: + String schema = Path.of('src/testResources/nextflow_schema.json').toAbsolutePath() + String script = """ + include { validateParameters } from 'plugin/nf-schema' + workflow { + params.input = new groovyx.gpars.dataflow.DataflowVariable() + params.outdir = 'src/testResources/testDir' + validateParameters(parameters_schema: '${schema}') + } + """ + + when: + Map opts = ['config': [ + 'validation': ['monochromeLogs': true], + 'params': ['input': 'src/testResources/correct.txt'] + ]] + runScript(opts, script) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--input (src/testResources/correct.txt)') + } + + @Timeout(60) + void 'should accept the config value of a dataflow value nested in a record param'() { + given: + String script = """ + include { validateParameters } from 'plugin/nf-schema' + workflow { + params.map = [ is: [ so: [ deep: new groovyx.gpars.dataflow.DataflowVariable() ] ] ] + validateParameters(parameters_schema: 'src/testResources/nextflow_schema_nested_parameters.json') + } + """ + + when: + Map opts = ['config': [ + 'validation': ['monochromeLogs': true], + 'params': ['map': ['is': ['so': ['deep': true]]]] + ]] + runScript(opts, script) + + then: + noExceptionThrown() + } + + @Timeout(60) + void 'should reject an invalid config value of a dataflow value nested in a record param'() { + given: + String script = """ + include { validateParameters } from 'plugin/nf-schema' + workflow { + params.map = [ is: [ so: [ deep: new groovyx.gpars.dataflow.DataflowVariable() ] ] ] + validateParameters(parameters_schema: 'src/testResources/nextflow_schema_nested_parameters.json') + } + """ + + when: + Map opts = ['config': [ + 'validation': ['monochromeLogs': true], + 'params': ['map': ['is': ['so': ['deep': 'maybe']]]] + ]] + runScript(opts, script) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--map.is.so.deep') + } + void 'should validate a schema with no arguments'() { given: File schemaSource = new File('src/testResources/nextflow_schema.json') From fca4bf6b621a4a2ac4afd52dcc8bdc91baf026f1 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 10:12:46 +0100 Subject: [PATCH 02/13] test(parameters): cover the command line path, precedence and typed Channel/Value wrappers; fix groovy lint findings Co-Authored-By: Claude Sonnet 5.5 --- .../parameters/ParameterValidator.groovy | 14 +- .../ValidateDataflowParamsTest.groovy | 182 ++++++++++++++++++ 2 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy diff --git a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy index 7e133a9d..922e08bb 100644 --- a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy +++ b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy @@ -210,10 +210,6 @@ class ParameterValidator { log.debug 'Finishing parameters validation' } - private List getErrors() { return errors } - - private List getWarnings() { return warnings } - // // Matched by package because the dataflow classes are not on the plugin's compile classpath // @@ -222,6 +218,10 @@ class ParameterValidator { return className.startsWith('groovyx.gpars.dataflow.') || className.startsWith('nextflow.dataflow.') } + private List getErrors() { return errors } + + private List getWarnings() { return warnings } + // // Channel and Value params hold live dataflow objects, and serialising them blocks forever. // The value they were created from (given on the command line, else set in the config) is @@ -237,13 +237,13 @@ class ParameterValidator { Object source = cliValue != null ? cliValue : configValue return source != null && !isDataflowValue(source) ? source : null } - if (value instanceof Map) { + if (value in Map) { Map result = [:] (value as Map).each { Object name, Object entry -> Object replaced = replaceDataflowValues( entry, - cliValue instanceof Map ? (cliValue as Map)[name] : null, - configValue instanceof Map ? (configValue as Map)[name] : null + cliValue in Map ? (cliValue as Map)[name] : null, + configValue in Map ? (configValue as Map)[name] : null ) if (replaced != null || !isDataflowValue(entry)) { result[name] = replaced diff --git a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy new file mode 100644 index 00000000..3a9a961b --- /dev/null +++ b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy @@ -0,0 +1,182 @@ +/* groovylint-disable LineLength, MethodName */ +package nextflow.validation + +import groovy.transform.CompileDynamic +import groovyx.gpars.dataflow.DataflowQueue +import groovyx.gpars.dataflow.DataflowVariable +import nextflow.Session +import nextflow.dataflow.ChannelImpl +import nextflow.dataflow.ValueImpl +import nextflow.validation.config.ValidationConfig +import nextflow.validation.exceptions.SchemaValidationException +import nextflow.validation.parameters.ParameterValidator +import spock.lang.Specification +import spock.lang.Timeout + +import java.nio.file.Path + +/** + * Validation of params that hold a dataflow value (a typed `Channel` or `Value` param), where the + * value given on the command line or in the config is validated in place of the dataflow object. + * The session is mocked so that the command line params can be set directly. + */ +@CompileDynamic +@Timeout(60) +class ValidateDataflowParamsTest extends Specification { + + private static final String SCHEMA = 'src/testResources/nextflow_schema.json' + private static final String NESTED_SCHEMA = 'src/testResources/nextflow_schema_nested_parameters.json' + + void 'should accept a valid command line value for a dataflow param'() { + given: + Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.csv'], [:]) + + when: + validate(session, SCHEMA) + + then: + noExceptionThrown() + } + + void 'should reject an invalid command line value for a dataflow param'() { + given: + Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.txt'], [:]) + + when: + validate(session, SCHEMA) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--input (src/testResources/correct.txt)') + } + + void 'should validate the command line value over the config value - invalid command line value'() { + given: + Session session = mockSession( + topLevelParams(new DataflowVariable()), + [input: 'src/testResources/correct.txt'], + [input: 'src/testResources/correct.csv'] + ) + + when: + validate(session, SCHEMA) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--input (src/testResources/correct.txt)') + } + + void 'should validate the command line value over the config value - valid command line value'() { + given: + Session session = mockSession( + topLevelParams(new DataflowVariable()), + [input: 'src/testResources/correct.csv'], + [input: 'src/testResources/correct.txt'] + ) + + when: + validate(session, SCHEMA) + + then: + noExceptionThrown() + } + + void 'should accept a valid command line value for a Value param'() { + given: + Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.csv'], [:]) + + when: + validate(session, SCHEMA) + + then: + noExceptionThrown() + } + + void 'should reject an invalid command line value for a Value param'() { + given: + Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.txt'], [:]) + + when: + validate(session, SCHEMA) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--input (src/testResources/correct.txt)') + } + + void 'should accept a valid command line value for a Channel param'() { + given: + Session session = mockSession(topLevelParams(new ChannelImpl(new DataflowQueue())), [input: 'src/testResources/correct.csv'], [:]) + + when: + validate(session, SCHEMA) + + then: + noExceptionThrown() + } + + void 'should reject an invalid command line value for a Channel param'() { + given: + Session session = mockSession(topLevelParams(new ChannelImpl(new DataflowQueue())), [input: 'src/testResources/correct.txt'], [:]) + + when: + validate(session, SCHEMA) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--input (src/testResources/correct.txt)') + } + + void 'should accept a valid command line value for a dataflow value nested in a record param'() { + given: + Session session = mockSession( + [map: [is: [so: [deep: new ValueImpl(new DataflowVariable())]]]], + [map: [is: [so: [deep: true]]]], + [:] + ) + + when: + validate(session, NESTED_SCHEMA) + + then: + noExceptionThrown() + } + + void 'should reject an invalid command line value for a dataflow value nested in a record param'() { + given: + Session session = mockSession( + [map: [is: [so: [deep: new ValueImpl(new DataflowVariable())]]]], + [map: [is: [so: [deep: 'maybe']]]], + [:] + ) + + when: + validate(session, NESTED_SCHEMA) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--map.is.so.deep') + } + + private Session mockSession(Map params, Map cliParams, Map configParams) { + Session session = Mock(Session) + session.params >> params + session.cliParams >> cliParams + session.config >> [params: configParams] + session.baseDir >> Path.of('.').toAbsolutePath() + return session + } + + private void validate(Session session, String schema) { + ValidationConfig config = new ValidationConfig([monochromeLogs: true], session) + new ParameterValidator(config).validateParametersMap( + [parameters_schema: Path.of(schema).toAbsolutePath().toString()], + session + ) + } + + private Map topLevelParams(Object input) { + return [input: input, outdir: 'src/testResources/testDir'] + } + +} From ccbd62cfae9f26b76b204478ef36362960c75f07 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 10:19:56 +0100 Subject: [PATCH 03/13] fix(summary): leave dataflow params out of the parameter summary A Channel or Value param printed as its object name in the parameter summary. The summary now uses the value the param was created from, like the validation does, and leaves out a param without one. The helper moves to Common so both share it. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 +- .../validation/ValidationExtension.groovy | 3 +- .../parameters/ParameterValidator.groovy | 47 +++------------- .../nextflow/validation/utils/Common.groovy | 36 +++++++++++++ .../validation/ParamsSummaryLogTest.groovy | 53 +++++++++++++++++++ 5 files changed, 98 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88832e1b..bd2d222a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ This version contains some breaking changes to the nf-schema API. See the [migra 1. Fixed an issue where the summary creation functions would fail if the default of a parameter was set in the schema, but not in the pipeline. 2. Fixed an issue where parameters with the `Path` type containing a remote file would show the wrong file path in the summary. -3. Fixed an issue where `validateParameters()` would hang when a parameter holds a `Channel` or `Value` (e.g. a typed `Channel<...>` param). The value the parameter was created from (given on the command line, otherwise set in the `params` config scope) is validated in its place, also when the dataflow value is nested in a record parameter. A parameter without such a value is left out. +3. Fixed an issue where `validateParameters()` would hang, and the params summary would print an object name, when a parameter holds a `Channel` or `Value` (e.g. a typed `Channel<...>` param). The value the parameter was created from (given on the command line, otherwise set in the `params` config scope) is validated and printed in its place, also when the dataflow value is nested in a record parameter. A parameter without such a value is left out. # Version 2.8.0 diff --git a/src/main/groovy/nextflow/validation/ValidationExtension.groovy b/src/main/groovy/nextflow/validation/ValidationExtension.groovy index a763d2b9..99d982ec 100644 --- a/src/main/groovy/nextflow/validation/ValidationExtension.groovy +++ b/src/main/groovy/nextflow/validation/ValidationExtension.groovy @@ -3,6 +3,7 @@ package nextflow.validation import static nextflow.validation.utils.Colors.getLogColors import static nextflow.validation.utils.Common.getBasePath import static nextflow.validation.utils.Common.getLongestKeyLength +import static nextflow.validation.utils.Common.replaceDataflowParams import groovy.json.JsonBuilder import groovy.util.logging.Slf4j @@ -197,7 +198,7 @@ class ValidationExtension extends PluginExtensionPoint { options, session.workflowMetadata, session.baseDir, - session.params + replaceDataflowParams(session.params, session.cliParams, session.config?.params) ) } diff --git a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy index 922e08bb..b1fdd97c 100644 --- a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy +++ b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy @@ -1,5 +1,6 @@ package nextflow.validation.parameters +import static nextflow.validation.utils.Common.replaceDataflowParams import static nextflow.NF.isSyntaxParserV2 import static nextflow.validation.utils.Colors.getLogColors @@ -124,7 +125,11 @@ class ParameterValidator { final Map options = [:], Session session ) { - Map params = replaceDataflowParams(initialiseExpectedParams(session.params), session) + Map params = replaceDataflowParams( + initialiseExpectedParams(session.params), + session.cliParams, + session.config?.params + ) String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema as String @@ -210,50 +215,10 @@ class ParameterValidator { log.debug 'Finishing parameters validation' } - // - // Matched by package because the dataflow classes are not on the plugin's compile classpath - // - private static boolean isDataflowValue(Object value) { - String className = value?.getClass()?.name ?: '' - return className.startsWith('groovyx.gpars.dataflow.') || className.startsWith('nextflow.dataflow.') - } - private List getErrors() { return errors } private List getWarnings() { return warnings } - // - // Channel and Value params hold live dataflow objects, and serialising them blocks forever. - // The value they were created from (given on the command line, else set in the config) is - // validated in their place, and a param without one is left out. Params nested in a record - // (e.g. the params of an included pipeline) are handled the same way. - // - private Map replaceDataflowParams(Map params, Session session) { - return replaceDataflowValues(params, session.cliParams, session.config?.params) as Map - } - - private Object replaceDataflowValues(Object value, Object cliValue, Object configValue) { - if (isDataflowValue(value)) { - Object source = cliValue != null ? cliValue : configValue - return source != null && !isDataflowValue(source) ? source : null - } - if (value in Map) { - Map result = [:] - (value as Map).each { Object name, Object entry -> - Object replaced = replaceDataflowValues( - entry, - cliValue in Map ? (cliValue as Map)[name] : null, - configValue in Map ? (configValue as Map)[name] : null - ) - if (replaced != null || !isDataflowValue(entry)) { - result[name] = replaced - } - } - return result - } - return value - } - // // Initialise expected params if not present // diff --git a/src/main/groovy/nextflow/validation/utils/Common.groovy b/src/main/groovy/nextflow/validation/utils/Common.groovy index 2fa80d94..b592ce21 100644 --- a/src/main/groovy/nextflow/validation/utils/Common.groovy +++ b/src/main/groovy/nextflow/validation/utils/Common.groovy @@ -124,4 +124,40 @@ public class Common { } } + // Matched by package because the dataflow classes are not on the plugin's compile classpath + static boolean isDataflowValue(Object value) { + String className = value?.getClass()?.name ?: '' + return className.startsWith('groovyx.gpars.dataflow.') || className.startsWith('nextflow.dataflow.') + } + + // Channel and Value params hold live dataflow objects: reading them blocks, and they print as object + // names. The value they were created from (given on the command line, else set in the config) is used + // in their place, and a param without one is left out. Params nested in a record (e.g. the params of an + // included pipeline) are handled the same way. + static Map replaceDataflowParams(Map params, Object cliParams, Object configParams) { + return replaceDataflowValues(params, cliParams, configParams) as Map + } + + private static Object replaceDataflowValues(Object value, Object cliValue, Object configValue) { + if (isDataflowValue(value)) { + Object source = cliValue != null ? cliValue : configValue + return source != null && !isDataflowValue(source) ? source : null + } + if (value in Map) { + Map result = [:] + (value as Map).each { Object name, Object entry -> + Object replaced = replaceDataflowValues( + entry, + cliValue in Map ? (cliValue as Map)[name] : null, + configValue in Map ? (configValue as Map)[name] : null + ) + if (replaced != null || !isDataflowValue(entry)) { + result[name] = replaced + } + } + return result + } + return value + } + } diff --git a/src/test/groovy/nextflow/validation/ParamsSummaryLogTest.groovy b/src/test/groovy/nextflow/validation/ParamsSummaryLogTest.groovy index 2e8cd35b..19f689ec 100644 --- a/src/test/groovy/nextflow/validation/ParamsSummaryLogTest.groovy +++ b/src/test/groovy/nextflow/validation/ParamsSummaryLogTest.groovy @@ -14,6 +14,7 @@ import nextflow.plugin.extension.PluginExtensionProvider import org.junit.Rule import org.pf4j.PluginDescriptorFinder import spock.lang.Shared +import spock.lang.Timeout import test.Dsl2Spec import test.OutputCapture @@ -112,6 +113,58 @@ class ParamsSummaryLogTest extends Dsl2Spec { stdout ==~ /.*outdir : outDir.*/ } + @Timeout(60) + void 'should leave a param that holds a dataflow value out of the params summary'() { + given: + String schema = Path.of('src/testResources/nextflow_schema.json').toAbsolutePath() + String script = """ + include { paramsSummaryLog } from 'plugin/nf-schema' + workflow { + params.outdir = 'outDir' + params.input = new groovyx.gpars.dataflow.DataflowVariable() + log.info paramsSummaryLog(parameters_schema: '${schema}') + } + """ + + when: + Map opts = ['config': ['validation': ['monochromeLogs': true]]] + runScript(opts, script) + String stdout = capture + + then: + noExceptionThrown() + stdout ==~ /(?s).*outdir : outDir.*/ + !stdout.contains('DataflowVariable') + !stdout.contains('input ') + } + + @Timeout(60) + void 'should print the config value of a param that holds a dataflow value in the params summary'() { + given: + String schema = Path.of('src/testResources/nextflow_schema.json').toAbsolutePath() + String script = """ + include { paramsSummaryLog } from 'plugin/nf-schema' + workflow { + params.outdir = 'outDir' + params.input = new groovyx.gpars.dataflow.DataflowVariable() + log.info paramsSummaryLog(parameters_schema: '${schema}') + } + """ + + when: + Map opts = ['config': [ + 'validation': ['monochromeLogs': true], + 'params': ['input': 'src/testResources/correct.csv'] + ]] + runScript(opts, script) + String stdout = capture + + then: + noExceptionThrown() + stdout ==~ /(?s).*input : src\/testResources\/correct.csv.*/ + !stdout.contains('DataflowVariable') + } + void 'should print params summary - nested parameters'() { given: String schema = Path.of('src/testResources/nextflow_schema_nested_parameters.json').toAbsolutePath() From e7546d9fa92816950dbfabe65ed00ddb3277fa95 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 10:50:06 +0100 Subject: [PATCH 04/13] fix(parameters): read the dataflow param value from the config params only For a typed Channel or Value param, session.params holds the dataflow object, and the value it was created from is in the params scope of the config, which also holds the values given on the command line and in a params file. The separate lookup in the command line params is not needed. Move the CHANGELOG entry to a new version section. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 7 +- .../validation/ValidationExtension.groovy | 2 +- .../parameters/ParameterValidator.groovy | 1 - .../nextflow/validation/utils/Common.groovy | 22 +++--- .../ValidateDataflowParamsTest.groovy | 75 ++++++------------- 5 files changed, 37 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2d222a..6bc1bebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # nextflow-io/nf-schema: Changelog +# Version 3.0.1 + +## Bug fixes + +1. Fixed an issue where `validateParameters()` would hang, and the params summary would print an object name, when a parameter holds a `Channel` or `Value` (e.g. a typed `Channel<...>` param). The value the parameter was created from (from the `params` config scope, which also holds the values given on the command line and in a params file) is validated and printed in its place, also when the dataflow value is nested in a record parameter. A parameter without such a value is left out. + # Version 3.0.0 This version contains some breaking changes to the nf-schema API. See the [migration guide](https://nextflow-io.github.io/nf-schema/3.0.0/3_0_0_migration_guide) for more information. @@ -23,7 +29,6 @@ This version contains some breaking changes to the nf-schema API. See the [migra 1. Fixed an issue where the summary creation functions would fail if the default of a parameter was set in the schema, but not in the pipeline. 2. Fixed an issue where parameters with the `Path` type containing a remote file would show the wrong file path in the summary. -3. Fixed an issue where `validateParameters()` would hang, and the params summary would print an object name, when a parameter holds a `Channel` or `Value` (e.g. a typed `Channel<...>` param). The value the parameter was created from (given on the command line, otherwise set in the `params` config scope) is validated and printed in its place, also when the dataflow value is nested in a record parameter. A parameter without such a value is left out. # Version 2.8.0 diff --git a/src/main/groovy/nextflow/validation/ValidationExtension.groovy b/src/main/groovy/nextflow/validation/ValidationExtension.groovy index 99d982ec..e2c0221f 100644 --- a/src/main/groovy/nextflow/validation/ValidationExtension.groovy +++ b/src/main/groovy/nextflow/validation/ValidationExtension.groovy @@ -198,7 +198,7 @@ class ValidationExtension extends PluginExtensionPoint { options, session.workflowMetadata, session.baseDir, - replaceDataflowParams(session.params, session.cliParams, session.config?.params) + replaceDataflowParams(session.params, session.config?.params) ) } diff --git a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy index b1fdd97c..b78f8939 100644 --- a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy +++ b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy @@ -127,7 +127,6 @@ class ParameterValidator { ) { Map params = replaceDataflowParams( initialiseExpectedParams(session.params), - session.cliParams, session.config?.params ) String schemaFilename = options?.containsKey('parameters_schema') ? diff --git a/src/main/groovy/nextflow/validation/utils/Common.groovy b/src/main/groovy/nextflow/validation/utils/Common.groovy index b592ce21..e75dcd17 100644 --- a/src/main/groovy/nextflow/validation/utils/Common.groovy +++ b/src/main/groovy/nextflow/validation/utils/Common.groovy @@ -131,26 +131,22 @@ public class Common { } // Channel and Value params hold live dataflow objects: reading them blocks, and they print as object - // names. The value they were created from (given on the command line, else set in the config) is used - // in their place, and a param without one is left out. Params nested in a record (e.g. the params of an - // included pipeline) are handled the same way. - static Map replaceDataflowParams(Map params, Object cliParams, Object configParams) { - return replaceDataflowValues(params, cliParams, configParams) as Map + // names. The value they were created from is used in their place, and a param without one is left out. + // That value is found in the params scope of the config, which also holds the values given on the + // command line and in a params file. Params nested in a record (e.g. the params of an included pipeline) + // are handled the same way. + static Map replaceDataflowParams(Map params, Object configParams) { + return replaceDataflowValues(params, configParams) as Map } - private static Object replaceDataflowValues(Object value, Object cliValue, Object configValue) { + private static Object replaceDataflowValues(Object value, Object configValue) { if (isDataflowValue(value)) { - Object source = cliValue != null ? cliValue : configValue - return source != null && !isDataflowValue(source) ? source : null + return configValue != null && !isDataflowValue(configValue) ? configValue : null } if (value in Map) { Map result = [:] (value as Map).each { Object name, Object entry -> - Object replaced = replaceDataflowValues( - entry, - cliValue in Map ? (cliValue as Map)[name] : null, - configValue in Map ? (configValue as Map)[name] : null - ) + Object replaced = replaceDataflowValues(entry, configValue in Map ? (configValue as Map)[name] : null) if (replaced != null || !isDataflowValue(entry)) { result[name] = replaced } diff --git a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy index 3a9a961b..72de7e9d 100644 --- a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy +++ b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy @@ -16,9 +16,10 @@ import spock.lang.Timeout import java.nio.file.Path /** - * Validation of params that hold a dataflow value (a typed `Channel` or `Value` param), where the - * value given on the command line or in the config is validated in place of the dataflow object. - * The session is mocked so that the command line params can be set directly. + * Validation of params that hold a dataflow value (a typed `Channel` or `Value` param), where the value + * the param was created from is validated in place of the dataflow object. Nextflow keeps that value in + * the params scope of the config, which also holds the values given on the command line and in a params + * file. The session is mocked so that the config params can be set directly. */ @CompileDynamic @Timeout(60) @@ -27,9 +28,9 @@ class ValidateDataflowParamsTest extends Specification { private static final String SCHEMA = 'src/testResources/nextflow_schema.json' private static final String NESTED_SCHEMA = 'src/testResources/nextflow_schema_nested_parameters.json' - void 'should accept a valid command line value for a dataflow param'() { + void 'should accept a valid value of a dataflow param'() { given: - Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.csv'], [:]) + Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.csv']) when: validate(session, SCHEMA) @@ -38,9 +39,9 @@ class ValidateDataflowParamsTest extends Specification { noExceptionThrown() } - void 'should reject an invalid command line value for a dataflow param'() { + void 'should reject an invalid value of a dataflow param'() { given: - Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.txt'], [:]) + Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.txt']) when: validate(session, SCHEMA) @@ -50,40 +51,9 @@ class ValidateDataflowParamsTest extends Specification { error.message.contains('--input (src/testResources/correct.txt)') } - void 'should validate the command line value over the config value - invalid command line value'() { + void 'should accept a valid value of a Value param'() { given: - Session session = mockSession( - topLevelParams(new DataflowVariable()), - [input: 'src/testResources/correct.txt'], - [input: 'src/testResources/correct.csv'] - ) - - when: - validate(session, SCHEMA) - - then: - SchemaValidationException error = thrown(SchemaValidationException) - error.message.contains('--input (src/testResources/correct.txt)') - } - - void 'should validate the command line value over the config value - valid command line value'() { - given: - Session session = mockSession( - topLevelParams(new DataflowVariable()), - [input: 'src/testResources/correct.csv'], - [input: 'src/testResources/correct.txt'] - ) - - when: - validate(session, SCHEMA) - - then: - noExceptionThrown() - } - - void 'should accept a valid command line value for a Value param'() { - given: - Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.csv'], [:]) + Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.csv']) when: validate(session, SCHEMA) @@ -92,9 +62,9 @@ class ValidateDataflowParamsTest extends Specification { noExceptionThrown() } - void 'should reject an invalid command line value for a Value param'() { + void 'should reject an invalid value of a Value param'() { given: - Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.txt'], [:]) + Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.txt']) when: validate(session, SCHEMA) @@ -104,9 +74,9 @@ class ValidateDataflowParamsTest extends Specification { error.message.contains('--input (src/testResources/correct.txt)') } - void 'should accept a valid command line value for a Channel param'() { + void 'should accept a valid value of a Channel param'() { given: - Session session = mockSession(topLevelParams(new ChannelImpl(new DataflowQueue())), [input: 'src/testResources/correct.csv'], [:]) + Session session = mockSession(topLevelParams(new ChannelImpl(new DataflowQueue())), [input: 'src/testResources/correct.csv']) when: validate(session, SCHEMA) @@ -115,9 +85,9 @@ class ValidateDataflowParamsTest extends Specification { noExceptionThrown() } - void 'should reject an invalid command line value for a Channel param'() { + void 'should reject an invalid value of a Channel param'() { given: - Session session = mockSession(topLevelParams(new ChannelImpl(new DataflowQueue())), [input: 'src/testResources/correct.txt'], [:]) + Session session = mockSession(topLevelParams(new ChannelImpl(new DataflowQueue())), [input: 'src/testResources/correct.txt']) when: validate(session, SCHEMA) @@ -127,12 +97,11 @@ class ValidateDataflowParamsTest extends Specification { error.message.contains('--input (src/testResources/correct.txt)') } - void 'should accept a valid command line value for a dataflow value nested in a record param'() { + void 'should accept a valid value of a dataflow value nested in a record param'() { given: Session session = mockSession( [map: [is: [so: [deep: new ValueImpl(new DataflowVariable())]]]], - [map: [is: [so: [deep: true]]]], - [:] + [map: [is: [so: [deep: true]]]] ) when: @@ -142,12 +111,11 @@ class ValidateDataflowParamsTest extends Specification { noExceptionThrown() } - void 'should reject an invalid command line value for a dataflow value nested in a record param'() { + void 'should reject an invalid value of a dataflow value nested in a record param'() { given: Session session = mockSession( [map: [is: [so: [deep: new ValueImpl(new DataflowVariable())]]]], - [map: [is: [so: [deep: 'maybe']]]], - [:] + [map: [is: [so: [deep: 'maybe']]]] ) when: @@ -158,10 +126,9 @@ class ValidateDataflowParamsTest extends Specification { error.message.contains('--map.is.so.deep') } - private Session mockSession(Map params, Map cliParams, Map configParams) { + private Session mockSession(Map params, Map configParams) { Session session = Mock(Session) session.params >> params - session.cliParams >> cliParams session.config >> [params: configParams] session.baseDir >> Path.of('.').toAbsolutePath() return session From 52c4e0f6577aa98f38e857a3963e10e22c12bff7 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 10:51:39 +0100 Subject: [PATCH 05/13] docs: use the next minor version for the CHANGELOG entry Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc1bebd..04d79a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # nextflow-io/nf-schema: Changelog -# Version 3.0.1 +# Version 3.1.0 ## Bug fixes From 545b4a27be26154141d061b5d43da41292c01d20 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 10:55:43 +0100 Subject: [PATCH 06/13] fix(parameters): detect dataflow params by class instead of package name The plugin requires Nextflow 26.04.0 or later, which has the typed Channel and Value classes, so they can be checked directly. Co-Authored-By: Claude Sonnet 5.5 --- .../groovy/nextflow/validation/utils/Common.groovy | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/groovy/nextflow/validation/utils/Common.groovy b/src/main/groovy/nextflow/validation/utils/Common.groovy index e75dcd17..cbe816ab 100644 --- a/src/main/groovy/nextflow/validation/utils/Common.groovy +++ b/src/main/groovy/nextflow/validation/utils/Common.groovy @@ -1,5 +1,9 @@ package nextflow.validation.utils +import groovyx.gpars.dataflow.DataflowReadChannel +import groovyx.gpars.dataflow.DataflowWriteChannel +import nextflow.dataflow.ChannelImpl +import nextflow.dataflow.ValueImpl import org.json.JSONObject import org.json.JSONArray import org.json.JSONPointer @@ -124,10 +128,11 @@ public class Common { } } - // Matched by package because the dataflow classes are not on the plugin's compile classpath + // The values a Channel or Value param holds: the typed wrappers and the dataflow channels and variables + // underneath them static boolean isDataflowValue(Object value) { - String className = value?.getClass()?.name ?: '' - return className.startsWith('groovyx.gpars.dataflow.') || className.startsWith('nextflow.dataflow.') + return value in ChannelImpl || value in ValueImpl || + value in DataflowReadChannel || value in DataflowWriteChannel } // Channel and Value params hold live dataflow objects: reading them blocks, and they print as object From 2d3883631be3c8469411807500ece17e1fc4b772 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 15:15:10 +0100 Subject: [PATCH 07/13] chore: bump the version to 3.1.0 Co-Authored-By: Claude Sonnet 5.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 599e40a9..50ff4de8 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher:6.1.1' } -version = '3.0.0' +version = '3.1.0' nextflowPlugin { nextflowVersion = '26.04.0' From 123b70d9e59c84ba5dba1393bba0ffdfb3c26e20 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 15:28:16 +0100 Subject: [PATCH 08/13] refactor(parameters): replace the dataflow params in the Map converter The Map converter of the JSON generator replaces the dataflow params of the root map, next to the casting of the command line values, so the conversions are in the same place. The converter is registered also when the command line values are not cast. Co-Authored-By: Claude Sonnet 5.5 --- .../parameters/ParameterValidator.groovy | 28 +++++++++---------- .../ValidateDataflowParamsTest.groovy | 27 ++++++++++++++++-- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy index b78f8939..77a975d0 100644 --- a/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy +++ b/src/main/groovy/nextflow/validation/parameters/ParameterValidator.groovy @@ -125,10 +125,7 @@ class ParameterValidator { final Map options = [:], Session session ) { - Map params = replaceDataflowParams( - initialiseExpectedParams(session.params), - session.config?.params - ) + Map params = initialiseExpectedParams(session.params) String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema as String @@ -146,17 +143,18 @@ class ParameterValidator { .addConverter(MemoryUnit) { MemoryUnit memory -> memory.toBytes() } .addConverter(VersionNumber) { VersionNumber version -> version.toString() } - // Cast parameters provided via the CLI to their respective types. - // This is a temporary workaround until static typing is introduced in Nextflow, - // in which case we can rely on the static type system to do the casting for us. - // This mimics the type casting behaviour of syntax parser V1 so shouldn't introduce any breaking changes. - if (castCliParams) { - List cliParams = (session.cliParams?.keySet()?.toList()*.toString() ?: []) as List - generatorOptions.addConverter(Map) { Map map -> - map.collectEntries { k, v -> - // Only cast parameters that were explicitly provided via the CLI - return (cliParams.contains(k) && v in String) ? [k, parseParamValue(v as String)] : [k, v] - } + // The generator calls this converter for the params map before it reads any entry, so the dataflow + // params are replaced once on the root map and the nested maps it reaches afterwards hold none. + // Parameters provided via the CLI are cast here too (a temporary workaround until static typing is + // introduced in Nextflow), which mimics the type casting behaviour of syntax parser V1. + List cliParams = castCliParams ? + (session.cliParams?.keySet()?.toList()*.toString() ?: []) as List : + [] + generatorOptions.addConverter(Map) { Map map -> + Map level = map.is(params) ? replaceDataflowParams(map, session.config?.params) : map + return level.collectEntries { Object k, Object v -> + // Only cast parameters that were explicitly provided via the CLI + (cliParams.contains(k) && v in String) ? [k, parseParamValue(v as String)] : [k, v] } } diff --git a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy index 72de7e9d..5cb71fbc 100644 --- a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy +++ b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy @@ -97,6 +97,29 @@ class ValidateDataflowParamsTest extends Specification { error.message.contains('--input (src/testResources/correct.txt)') } + void 'should accept a valid value of a dataflow param when the command line values are not cast'() { + given: + Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.csv']) + + when: + validate(session, SCHEMA, [cast_cli_params: false]) + + then: + noExceptionThrown() + } + + void 'should reject an invalid value of a dataflow param when the command line values are not cast'() { + given: + Session session = mockSession(topLevelParams(new ValueImpl(new DataflowVariable())), [input: 'src/testResources/correct.txt']) + + when: + validate(session, SCHEMA, [cast_cli_params: false]) + + then: + SchemaValidationException error = thrown(SchemaValidationException) + error.message.contains('--input (src/testResources/correct.txt)') + } + void 'should accept a valid value of a dataflow value nested in a record param'() { given: Session session = mockSession( @@ -134,10 +157,10 @@ class ValidateDataflowParamsTest extends Specification { return session } - private void validate(Session session, String schema) { + private void validate(Session session, String schema, Map options = [:]) { ValidationConfig config = new ValidationConfig([monochromeLogs: true], session) new ParameterValidator(config).validateParametersMap( - [parameters_schema: Path.of(schema).toAbsolutePath().toString()], + [parameters_schema: Path.of(schema).toAbsolutePath().toString()] + options, session ) } From 163165a9ac1810105b857a4e36e00af492f3c18e Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 2 Oct 2026 18:55:07 +0100 Subject: [PATCH 09/13] docs: describe the parameters that are a Channel or a Value Co-Authored-By: Claude Sonnet 5.5 --- docs/parameters/validation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/parameters/validation.md b/docs/parameters/validation.md index ba713de7..82b63a21 100644 --- a/docs/parameters/validation.md +++ b/docs/parameters/validation.md @@ -143,3 +143,9 @@ For example, providing an integer as a string will no longer fail validation. It attempts to cast a temporary copy of the params only, during the validation step. To enable lenient validation mode, set `validation.lenientMode = true` in your configuration file. + +## Parameters that are a `Channel` or a `Value` + +With typed parameters, a parameter can be declared as a `Channel` or a `Value` (see the [typed parameters](https://github.com/nextflow-io/nextflow/blob/master/docs/typed-parameters.mdx) documentation of Nextflow). For example, a `Channel` parameter takes the path of a samplesheet, which Nextflow loads as a channel, and a pipeline that is included in another pipeline can be given a channel by the including pipeline instead. + +These parameters hold a dataflow object while the pipeline runs, so there is no value to validate in them. `validateParameters()` validates the value that the parameter was created from instead. Nextflow keeps it in the `params` scope of the configuration, which also holds the values given on the command line and in a params file. A parameter that has no such value, such as one that is given by the dataflow of an including pipeline, is not validated. The same value is shown by [`paramsSummaryLog()` and `paramsSummaryMap()`](summary_log.md), and a parameter without one is left out of the summary. From d945c9a66e05b321267b8567041c245b92b93656 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Mon, 5 Oct 2026 15:01:47 +0100 Subject: [PATCH 10/13] test(parameters): cast the command line value of a dataflow param Also set the mocked session as the global session, which the schema evaluators read, so the spec does not depend on another spec having created one first. Co-Authored-By: Claude Opus 5.5 --- .../ValidateDataflowParamsTest.groovy | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy index 5cb71fbc..0dd48b9b 100644 --- a/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy +++ b/src/test/groovy/nextflow/validation/ValidateDataflowParamsTest.groovy @@ -4,6 +4,8 @@ package nextflow.validation import groovy.transform.CompileDynamic import groovyx.gpars.dataflow.DataflowQueue import groovyx.gpars.dataflow.DataflowVariable +import nextflow.Global +import nextflow.ISession import nextflow.Session import nextflow.dataflow.ChannelImpl import nextflow.dataflow.ValueImpl @@ -28,6 +30,16 @@ class ValidateDataflowParamsTest extends Specification { private static final String SCHEMA = 'src/testResources/nextflow_schema.json' private static final String NESTED_SCHEMA = 'src/testResources/nextflow_schema_nested_parameters.json' + private ISession previousSession + + void setup() { + previousSession = Global.session + } + + void cleanup() { + Global.session = previousSession + } + void 'should accept a valid value of a dataflow param'() { given: Session session = mockSession(topLevelParams(new DataflowVariable()), [input: 'src/testResources/correct.csv']) @@ -149,11 +161,26 @@ class ValidateDataflowParamsTest extends Specification { error.message.contains('--map.is.so.deep') } - private Session mockSession(Map params, Map configParams) { + void 'should cast the command line value of a dataflow param'() { + given: + Map cliParams = [map: [is: [so: [deep: 'true']]]] + Session session = mockSession([map: [is: [so: [deep: new ValueImpl(new DataflowVariable())]]]], cliParams, cliParams) + + when: + validate(session, NESTED_SCHEMA, [cast_cli_params: true]) + + then: + noExceptionThrown() + } + + private Session mockSession(Map params, Map configParams, Map cliParams = null) { Session session = Mock(Session) session.params >> params + session.cliParams >> cliParams session.config >> [params: configParams] session.baseDir >> Path.of('.').toAbsolutePath() + // the schema evaluators read the session from Global + Global.session = session return session } From df0a0cbc3f17b7480f7bac1f13b21fcd52b6c806 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Mon, 5 Oct 2026 15:02:13 +0100 Subject: [PATCH 11/13] docs: link the replacement of dataflow params to its planned successor Co-Authored-By: Claude Opus 5.5 --- src/main/groovy/nextflow/validation/utils/Common.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/groovy/nextflow/validation/utils/Common.groovy b/src/main/groovy/nextflow/validation/utils/Common.groovy index 5d60ad93..57ede7a4 100644 --- a/src/main/groovy/nextflow/validation/utils/Common.groovy +++ b/src/main/groovy/nextflow/validation/utils/Common.groovy @@ -152,6 +152,8 @@ public class Common { // That value is found in the params scope of the config, which also holds the values given on the // command line and in a params file. Params nested in a record (e.g. the params of an included pipeline) // are handled the same way. + // TODO use `session.params.toPlainMap()` instead once it is in a Nextflow release + // (https://github.com/nextflow-io/nextflow/pull/7759), see https://github.com/nextflow-io/nf-schema/pull/230 static Map replaceDataflowParams(Map params, Object configParams) { return replaceDataflowValues(params, configParams) as Map } From e86c650ee79ea2f6b29def99400f12fb55b2574b Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Mon, 5 Oct 2026 15:02:54 +0100 Subject: [PATCH 12/13] docs: word the planned successor of the dataflow param replacement as a proposal Co-Authored-By: Claude Opus 5.5 --- src/main/groovy/nextflow/validation/utils/Common.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/groovy/nextflow/validation/utils/Common.groovy b/src/main/groovy/nextflow/validation/utils/Common.groovy index 57ede7a4..45641f13 100644 --- a/src/main/groovy/nextflow/validation/utils/Common.groovy +++ b/src/main/groovy/nextflow/validation/utils/Common.groovy @@ -152,8 +152,9 @@ public class Common { // That value is found in the params scope of the config, which also holds the values given on the // command line and in a params file. Params nested in a record (e.g. the params of an included pipeline) // are handled the same way. - // TODO use `session.params.toPlainMap()` instead once it is in a Nextflow release - // (https://github.com/nextflow-io/nextflow/pull/7759), see https://github.com/nextflow-io/nf-schema/pull/230 + // TODO if Nextflow gains a core way to get these plain values (proposed in + // https://github.com/nextflow-io/nextflow/pull/7759), consider using it here instead, + // see https://github.com/nextflow-io/nf-schema/pull/230 static Map replaceDataflowParams(Map params, Object configParams) { return replaceDataflowValues(params, configParams) as Map } From e804b6bf0f3380775024477acbfee697c8e63217 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Mon, 5 Oct 2026 15:03:09 +0100 Subject: [PATCH 13/13] docs: link the dataflow param replacement to the Nextflow issue Co-Authored-By: Claude Opus 5.5 --- src/main/groovy/nextflow/validation/utils/Common.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/groovy/nextflow/validation/utils/Common.groovy b/src/main/groovy/nextflow/validation/utils/Common.groovy index 45641f13..168564b5 100644 --- a/src/main/groovy/nextflow/validation/utils/Common.groovy +++ b/src/main/groovy/nextflow/validation/utils/Common.groovy @@ -152,8 +152,8 @@ public class Common { // That value is found in the params scope of the config, which also holds the values given on the // command line and in a params file. Params nested in a record (e.g. the params of an included pipeline) // are handled the same way. - // TODO if Nextflow gains a core way to get these plain values (proposed in - // https://github.com/nextflow-io/nextflow/pull/7759), consider using it here instead, + // TODO if Nextflow gains a core way to get these plain values + // (https://github.com/nextflow-io/nextflow/issues/7758), consider using it here instead, // see https://github.com/nextflow-io/nf-schema/pull/230 static Map replaceDataflowParams(Map params, Object configParams) { return replaceDataflowValues(params, configParams) as Map