I want to have two different ways of parameterizin...
# nf-schema
s
I want to have two different ways of parameterizing a pipeline that take different arguments. What I want is to specify the strategy to use (either "mode1" or "mode2") and then depending on what mode is selected require that the user specifies additional arguments specific for that mode. I was experimenting with the constraints and this is what I came up with:
Copy code
{
  "$schema": "<https://json-schema.org/draft/2020-12/schema>",
  "$id": "<file://nextflow_schema.json>",
  "title": "Pipeline Parameters",
  "type": "object",
  "$defs": {
    "strategy": {
      "type": "object",
      "default": "",
      "properties": {
        "mode": {
          "type": "string",
          "default": "mode2",
          "enum": [
            "mode2",
            "mode1"
          ]
        }
      },
      "required": [
        "mode"
      ]
    },
    "mode2_input": {
      "type": "object",
      "default": "",
      "properties": {
        "mode2": {
          "type": "string"
        }
      },
      "required": [
        "mode2"
      ]
    },
    "mode1_input": {
      "type": "object",
      "default": "",
      "properties": {
        "mode1": {
          "type": "string"
        }
      },
      "required": [
        "mode1"
      ]
    }
  },
  "oneOf": [
    {
      "allOf": [
        {
          "$ref": "#/$defs/mode2_input"
        },
        {
          "properties": {
            "mode1": {
              "const": null
            }
          }
        },
        {
          "properties": {
            "mode": {
              "const": "mode2"
            }
          }
        }
      ]
    },
    {
      "allOf": [
        {
          "$ref": "#/$defs/mode1_input"
        },
        {
          "properties": {
            "mode2": {
              "const": null
            }
          }
        },
        {
          "properties": {
            "mode": {
              "const": "mode1"
            }
          }
        }
      ]
    }
  ],
  "allOf": [
    {
      "$ref": "#/$defs/strategy"
    }
  ]
}
Are there any cleaner ways to specify this? When I run it with the correct arguments it validates, but then the arguments are wrong (e.g.
--mode mode1 --mode2 mode2samplesheet
) the validation messages are a little confusing because it prints all the individual failures as a continuous list, despite them belonging to sub-cases of the oneOf clause. It feels like it might be possible with dependentSchemas, but I'm not sure I can make those switch depending on the value of mode? (like with "const" above)
a
Errors for complex schema constructs will always be somewhat confusing/verbose. The errors are as reported by the underlying
jsonschema
library and it really doesn't have a way of knowing which conditional schema you made the error on. It may seem obvious to the user, but how should the library/schema know if you entered the wrong mode options or you actually set the wrong
mode
and passed the correct options for the mode you meant to set
s
That's a good point, I guess I was implicitly assuming the mode flag would take precedence, and then the user would see an error about incorrect requirements for that mode, but the error could be the other way around. I was able to make it a little more on-point by using two boolean flags (with a "oneOf" constraint, so both can't be set) to specify the mode, and then use dependentSchemas to require the mode properties only when the corresponding flag was set.
n
I think you can make this even simpler by using two schemas. The
validateParameters
function can take an optional argument called
parameters_schema
in which you can provide the path to a different schema. You could add some simple check in your pipeline to see what the mode is and pass the correct schema to the function that way. See the docs for more information: https://nextflow-io.github.io/nf-schema/latest/parameters/validation/#validateparameters
s
Oh, that's an interesting idea. When I tried the nfcore linter it didn't like the two-fork schema because not every property was inside an allof clause. Thank you for pointing that out
a
FWIW the linter won't work with certain complex schemas or with the two schema method
Which, again, is partly due to the complexity of checking these things in a fashion that jsonschema wasn't actually meant to describe
s
D'oh, foiled! I thought it would be neat to enforce at the schema level, but honestly it does get a hard to read/verify, so it probably isn't the cleanest way to implement the requirement. Perhaps some extra groovy checks at the top of the script would be the most clear. I was reading this section of the docs site, and this bit about conditional logic made me think it was more expressive than I appreciated: JSON Schema - Conditional schema validation
a
The schemas themselves are highly flexible. But
jsonschema
is primarily concerned with validation of data and less concerned/well-designed with regards to displaying it or generating it imo. I too have thought it would be easy to implement certain tooling around the schema only to be stymied once I delved deeper.
party this 1
s
Can you dive into the validation errors at all as a developer using jsonschema? Do the errors have pointers to the conditions in the schema that cause validation, or it's just the list of error messages that get passed along? I guess it's a young project, hopefully the tooling evolves with it, it does seem like there is opportunity to do more!
n
Yes the default errors contain a way to get the JSON pointers, but we made that a bit more readable for the CLI error. The
validate()
function returns the raw errors if you are interested
👍 1
a
I can tell you from experience that the errors for complex schema are a mess in terms of trying to pick the most likely route the user was aiming for. I actually implement some complex validation on the nextflow level instead of using jsonschema because it gives me better control over the error messages
👍 1
So essentially I use nf-schema/jsonschema for type validation and then custom logic in nextflow channels for e.g. cross-sample validation, complex conditional params etc
s
Thank you
n
Yes that's also the way I do it 😛
s
I wonder if you could give the params map, the schema, and the validation errors to an LLM and it could formulate it into a sensible sentence about what probably went wrong.. kind of silly, but everyone's got AI on the brain these days :)
n
That's something you can probably already do with the current errors 🙂 We'll leave that up to the users of the pipelines to determine how to read the errors