Skip to content

fix(gateway): reconstruct nested plugin config from dotted ROS params#518

Merged
mfaferek93 merged 3 commits into
mainfrom
fix/gateway-nested-plugin-config
Jul 12, 2026
Merged

fix(gateway): reconstruct nested plugin config from dotted ROS params#518
mfaferek93 merged 3 commits into
mainfrom
fix/gateway-nested-plugin-config

Conversation

@mfaferek93

Copy link
Copy Markdown
Collaborator

Summary

extract_plugin_config() built a flat JSON object keyed by the dotted parameter remainder. For a ROS parameter plugins.<plugin>.native_alarms.enabled it produced {"native_alarms.enabled": true}. Every plugin, however, reads its config as a nested object (config.contains("native_alarms"), config["native_alarms"]["enabled"]), so the lookup always missed.

Effect: the entire plugins.<name>.* ROS-param surface was silently dead for any nested key, across every plugin - native_alarms + discovery, discovery/auto_browse/auto_alarms, severity_bands, and so on. Only single-level keys, node_map YAML, and flat env vars worked; configuring a plugin's nested blocks through gateway parameters had no effect and raised no error.

Fix

Reconstruct a nested JSON object from the dotted keys: after stripping the plugins.<plugin>. prefix, split the remainder on . and build nested objects. Preserved end to end:

  • single-level keys stay flat (no change to existing flat config),
  • both parameter sources (NodeOptions parameter_overrides and the --params-file YAML via list_parameters),
  • array/list leaves kept verbatim,
  • the read-only .path key stays excluded.

A key used both as a leaf and an intermediate (e.g. discovery: true and discovery.enabled: true) resolves to the nested object deterministically and warns, never crashing.

The extraction helpers moved into param_utils.hpp so the behavior is unit-testable.

Testing

test_plugin_config gains coverage locking the nested shape: 2-level, 3-level (severity_bands), an array leaf, .path exclusion, and a leaf/intermediate collision. These assertions fail against the old flat implementation. Built and ran the gateway gtests in a ros:jazzy container: test_plugin_config passes (1/1, all cases). clang-format-18 clean.

Copilot AI review requested due to automatic review settings July 10, 2026 20:16

Copilot AI 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.

Pull request overview

This PR fixes plugin configuration extraction in ros2_medkit_gateway so ROS parameters under plugins.<name>.* correctly reconstruct nested JSON objects from dotted keys (instead of producing a flat JSON map that plugins never match), and adds unit tests to lock in the expected nested shape.

Changes:

  • Move extract_plugin_config() (and its helpers) into param_utils.hpp and rebuild nested JSON config by splitting dotted keys into object hierarchies.
  • Remove the old flat extraction implementation from gateway_node.cpp and use the shared helper.
  • Extend test_plugin_config with cases covering 2-/3-level nesting, array leaves, .path exclusion, and leaf/intermediate collisions.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/ros2_medkit_gateway/test/test_plugin_config.cpp Adds unit coverage asserting nested JSON reconstruction and collision behavior for plugin config extraction.
src/ros2_medkit_gateway/src/gateway_node.cpp Removes the old flat extract_plugin_config() implementation and uses the shared helper from param_utils.hpp.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/param_utils.hpp Implements nested insertion for dotted keys and exposes extract_plugin_config() for reuse/testing.

Comment thread src/ros2_medkit_gateway/test/test_plugin_config.cpp
Comment thread src/ros2_medkit_gateway/test/test_plugin_config.cpp

@bburda bburda 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.

Test coverage gaps (outside the diff):

  • Nested reconstruction is only tested through the NodeOptions parameter_overrides source. Both extract_plugin_config tests set overrides, so config is non-empty and the function returns before the --params-file path runs (declare_plugin_params_from_yaml -> list_parameters -> the second insert_nested_param loop). That YAML path is the production one. The existing YAML tests call declare_plugin_params_from_yaml directly and only check flat single-level keys; none call extract_plugin_config. So a regression in the second source (the list_prefix without trailing dot, the substr, the second loop) would produce flat or empty config in production and no test would fail. Worth adding a test that loads nested keys from a --params-file, builds a node with no parameter_overrides, calls extract_plugin_config, and asserts the nested object (with one array leaf).

  • The leaf/intermediate collision test only covers one order: scalar first (discovery), then the nested key (discovery.enabled). That path hits the intermediate branch at param_utils.hpp:96-108. The reverse order (nested key first, then the scalar) goes through the leaf guard at param_utils.hpp:113-119, which no test exercises, even though the docstring says the result is the same in either order. Worth adding the reversed case.

@mfaferek93

Copy link
Copy Markdown
Collaborator Author

Addressed the review-body test-coverage gaps in 3e1c35f:

  • YAML source untested: right, both extract_plugin_config tests set overrides and return at source 1, so the --params-file path (declare_plugin_params_from_yaml -> list_parameters -> second insert loop) was never checked. Added ExtractPluginConfigReconstructsNestedFromYaml: loads nested keys from a --params-file, node has no parameter_overrides, calls extract_plugin_config, asserts the 2-/3-level nesting plus an array leaf and .path exclusion.
  • Collision only one order: right, scalar-first hit the intermediate branch, the leaf guard was never exercised. Added ExtractPluginConfigLeafIntermediateCollisionReversed (nested key first, scalar second).

Built ros2_medkit_gateway and ran ctest -R test_plugin_config in a clean ros:jazzy container: 7/7 pass.

@mfaferek93
mfaferek93 force-pushed the fix/gateway-nested-plugin-config branch from 3e1c35f to af0baaf Compare July 12, 2026 14:19
extract_plugin_config built a flat JSON object keyed by the dotted
parameter remainder, so plugins.<name>.native_alarms.enabled became
{"native_alarms.enabled": true}. Plugins read nested objects
(config["native_alarms"]["enabled"]), so every nested key on the
plugins.<name>.* param surface was silently ignored across all plugins.

Split the dotted remainder into nested objects; single-level keys,
array leaves, both param sources and the .path exclusion are preserved.
An ASSERT_* abort before the trailing rclcpp::shutdown() left rclcpp
initialized and corrupted later tests in the process. Introduce a
ScopedRclcpp RAII guard that shuts down during stack unwinding and apply
it to all five tests.
Add extract_plugin_config coverage for the --params-file source (source 2)
so a regression in the production YAML path cannot pass silently, and a
reversed leaf/intermediate collision that exercises the leaf guard.
@mfaferek93
mfaferek93 force-pushed the fix/gateway-nested-plugin-config branch from af0baaf to 285c733 Compare July 12, 2026 17:11
@mfaferek93
mfaferek93 merged commit d891027 into main Jul 12, 2026
12 checks passed
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.

3 participants