Skip to content

Add a standalone configuration validator (#312) - #388

Open
facontidavide wants to merge 3 commits into
PickNikRobotics:mainfrom
facontidavide:feature/standalone-validator
Open

facontidavide wants to merge 3 commits into
PickNikRobotics:mainfrom
facontidavide:feature/standalone-validator

Conversation

@facontidavide

@facontidavide facontidavide commented Sep 17, 2026

Copy link
Copy Markdown

@tylerjw hello !

I hope you don't mind too much the AI generated PR.
I found myself needing this and I would like to contribute upstream, so that more people may benefit from it.

let me know what you think

Description

Add generate_parameter_library_validate, a CLI that checks a ROS 2 parameter configuration file against one or more parameter definition files. It needs no ROS 2 installation, no colcon workspace and no running node, so it can run as a CI step or on the laptop of somebody configuring a robot.

The tool reuses the existing parser and the validators in python_validators.py, so the rules it applies are the ones the generated code applies at runtime. It reports a value whose type does not match the declared type, a value rejected by a built-in validator, and a parameter declared without a default_value that the configuration does not set. --strict also reports parameters that no definition declares, with a suggestion when the name is close to a declared one, and parameters absent from the configuration that will take their default.

Custom validator functions are C++ and cannot run outside a build, so they are reported as skipped. read_only describes runtime behaviour and is out of scope.

CodeGenVariableBase now keeps the size of a fixed size type on the instance. It was computed and discarded, and the validator needs it to check array lengths.

Davide Faconti and others added 3 commits September 17, 2026 12:41
Add generate_parameter_library_validate, a CLI that checks a ROS 2 parameter
configuration file against one or more parameter definition files. It needs no
ROS 2 installation, no colcon workspace and no running node, so it can run as a
CI step or on the laptop of somebody configuring a robot.

The tool reuses the existing parser and the validators in python_validators.py,
so the rules it applies are the ones the generated code applies at runtime. It
reports a value whose type does not match the declared type, a value rejected by
a built-in validator, and a parameter declared without a default_value that the
configuration does not set. --strict also reports parameters that no definition
declares, with a suggestion when the name is close to a declared one, and
parameters absent from the configuration that will take their default.

Custom validator functions are C++ and cannot run outside a build, so they are
reported as skipped. read_only describes runtime behaviour and is out of scope.

CodeGenVariableBase now keeps the size of a fixed size type on the instance. It
was computed and discarded, and the validator needs it to check array lengths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- run_validations returns a severity with each message instead of the caller
  deciding it by looking for a phrase in the text. A validator that rejects a
  value is an error; a validator that cannot run here, because it is C++ or
  because it raised, is a warning, since the value may be correct and only the
  check is missing. The exception branch was previously reported as an error.
- Parse only the definitions whose namespace a configuration refers to, and
  report the ones left out so that skipping stays visible. With 50 definitions
  and a configuration matching one of them this goes from 538 ms to 122 ms.
- Bound the cost of the "did you mean" suggestions, which compare a name
  against every declared parameter. A configuration that has drifted wholesale
  is not helped by a list of guesses.
- CodeGenFixedVariable sets fixed_size itself rather than the base class
  decoding the subclass's tuple layout.
- One helper for the dotted name join that was repeated at five call sites,
  Diagnostic as a dataclass, and drop two attributes nothing read.
- Tests pin the severity rather than the wording, cover a validator that
  raises, and cover a definition that no section refers to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each of these was a silent wrong answer on a configuration shape the project's
own fixtures and readme use, verified against ROS 2 Jazzy where the question was
about runtime behaviour.

- A file whose only section is `/**` was validated against nothing and exited 0.
  It is now checked like any other section, which is the usual shape when the
  node name is not pinned.
- Parameters of type `none` were reported as missing or wrongly typed. The
  generator declares nothing for them, so they are skipped, and the subtree
  below one is not reported as unknown.
- A mapped parameter whose key list is not set in the configuration was skipped,
  and under --strict its keys were reported as unknown. The node expands the map
  over the effective value, so the declared default is used when the
  configuration sets nothing.
- A section named with a leading slash or under a namespace matched no
  definition. Section names are reduced to the node name before matching.
- --strict reported `use_sim_time` and the other parameters a node declares for
  itself as unknown.
- Scalars are now read the way rcl_yaml_param_parser reads them: `1e5` is a
  double, which PyYAML calls a string, and the value is converted before the
  validators run so they are not handed text. The quoting style is not
  preserved by the loader, so a word ROS 2 reads as a bool is a warning.
- An empty sequence is reported, because ROS 2 gives the node
  PARAMETER_NOT_SET rather than an empty array.
- Two definitions with the same root element silently discarded the first.
- A single definition was applied to sections that clearly belong to other
  nodes; it is now only used when no section matches any definition.
- A missing or malformed file printed a traceback. All diagnostics go to one
  stream so that their order is kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@facontidavide
facontidavide marked this pull request as ready for review September 17, 2026 11:11
@tylerjw

tylerjw commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@facontidavide I don't mind at all. I'll have my bob review this and will try it out myself. I don't have permissions to merge things here anymore but I'm happy to review stuff.

@tylerjw tylerjw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @facontidavide — I'm Bob, Tyler's AI assistant, posting this review with Tyler's explicit permission.

I reproduced three cases where the validator exits successfully despite a configuration that fails at runtime; the inline comments include details. The validator and mapped-parameter tests pass (38 tests). I couldn't collect the full Python suite because ament_index_python is unavailable in this environment.

Comment on lines +154 to +155
if reads_as_number(value, expected):
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve quoting when resolving numeric types

A configuration containing rate: "2.0" passes this check for a parameter declared as double: reads_as_number() accepts the string and coerce_value() converts it to a float. I reproduced the CLI returning 0. ROS 2 preserves quoted scalars as strings, so the node rejects that override instead. This also affects quoted integers and array elements. Please retain scalar style/tag information while loading YAML and resolve the ROS type independently of the expected declaration, so unquoted scientific notation remains supported without accepting quoted numbers.

Reference: https://github.com/ros2/rcl/blob/rolling/rcl_yaml_param_parser/src/parse.c#L227-L248

f'{parameter.default_value}',
)
)
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Validate the effective default when a parameter is omitted

This continue skips all validation for omitted parameters that have a default. For example, a string_array declared with default_value: [] and validation: {not_empty<>: null} passes the CLI with my_node: {ros__parameters: {}} (exit 0), although the generated runtime initialization validates the effective value and rejects the empty array. A numeric default outside its declared bounds has the same problem. Please run the applicable validators on the effective default before continuing; the strict-mode warning alone does not detect the startup failure.

Comment on lines +428 to +430
for nested_key, nested_value in value.items():
if isinstance(nested_value, dict) and ROS_PARAMETERS_KEY in nested_value:
sections[f'{key}/{nested_key}'] = nested_value[ROS_PARAMETERS_KEY] or {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Discover node sections recursively through namespaces

Only the first two mapping levels are searched for ros__parameters. A valid ROS parameter file nested as robot -> arm -> my_node -> ros__parameters is therefore treated as an unnamed plain parameter tree. I reproduced rate: -5.0 under that section returning exit 0 with a definition that has default_value: 1.0 and gt<>: [0.0]; the actual override is never checked. In strict mode it instead produces unknown-parameter errors for the flattened namespace. Please recurse through namespace mappings until reaching each ros__parameters section.

ROS's parser accumulates namespace components until it reaches that key: https://github.com/ros2/rcl/blob/rolling/rcl_yaml_param_parser/src/parse.c#L902-L938

@christophfroehlich christophfroehlich linked an issue Sep 18, 2026 that may be closed by this pull request

@christophfroehlich christophfroehlich left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this one!

Should we directly use this in this repo for integration testing the existing configuration files?

@Nitschi has originally proposed this, could you have a look please and leave a review here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Standalone validator tool

3 participants