fix(gateway): non-404 parameter errors win over NOT_FOUND across nodes#540
Merged
Conversation
The multi-node GET probe overwrote last_result on every failure, so a NOT_FOUND from a later node masked a timeout/unavailable from an earlier one and the client got 404 instead of 503. Fold failures through ParameterErrorAccumulator so the surfaced error is order independent.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes multi-node parameter GET error precedence in ros2_medkit_gateway so that non-404 failures (e.g., TIMEOUT / SERVICE_UNAVAILABLE) deterministically override NOT_FOUND regardless of backing-node iteration order, addressing issue #539.
Changes:
- Introduce
handlers::ParameterErrorAccumulatorto fold per-nodeget_parameter()failures with “non-404 wins over 404” precedence. - Update
ConfigHandlers::get_configuration()to use the accumulator instead of “last failure wins”. - Add a new GTest target to pin the 503 outcome for TIMEOUT + NOT_FOUND in both iteration orders.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/ros2_medkit_gateway/src/http/handlers/config_handlers.cpp | Uses ParameterErrorAccumulator during multi-node GET probing so non-404 failures cannot be masked by later 404s. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/config_handlers.hpp | Defines ParameterErrorClassification and ParameterErrorAccumulator used to enforce error precedence. |
| src/ros2_medkit_gateway/test/test_parameter_error_precedence.cpp | Adds regression tests validating precedence behavior independent of node iteration order. |
| src/ros2_medkit_gateway/CMakeLists.txt | Registers and enables the new test_parameter_error_precedence target. |
bburda
reviewed
Jul 17, 2026
Collaborator
There was a problem hiding this comment.
Additional findings outside the diff:
- The only new test drives ParameterErrorAccumulator in isolation; nothing exercises get_configuration's multi-node branch, so the four unit tests stay green even if the handler wiring regressed to the old overwrite. The precedence only fires on GET /{aggregated-entity}/configurations/{param} (no app: prefix) with two or more backing nodes where one is unavailable and another lacks the parameter. An integration test on that path in both node orders would prove the fix.
- Related: the accumulator and ParameterErrorClassification were lifted into the public handler header only so that test can drive them directly (no other translation unit uses them). Keeping the accumulator in the .cpp anonymous namespace and covering precedence through a handler-level test would tighten the public surface and close the proof gap.
- Semantics (deliberate): there is no signal distinguishing "absent on all reachable nodes, one unreachable" from a bare 503, so a client probing whether a knob exists on a partially-down aggregated entity gets a retryable 503 for a parameter that may be terminally absent.
- Pre-existing, out of scope: list_configurations returns 200 and silently drops a down backing node, while get /{param} for a parameter only on that node now returns 503. And the string fallback in classify_parameter_error (the error_code == NONE path) is dead on the shipped transport; if it were reached, the transport's real "did not respond" / "not currently set" messages do not contain its match substrings, so it would misclassify to 400.
Among distinct non-404 failures the last one folded won, so a partially-broken aggregated entity surfaced 503 or 500 depending on node iteration order. Rank 503 > other 5xx > non-404 4xx > 404 and keep the first failure at the winning rank.
Replace the accumulator-only unit tests with ConfigHandlers::get_configuration tests against a live GatewayNode (unavailable, unresponsive and reachable backing nodes, both iteration orders) and move ParameterErrorAccumulator back into the .cpp anonymous namespace so no test-only surface leaks from the header.
bburda
approved these changes
Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GET /{entity-path}/configurations/{param} probes every backing node and overwrote last_result on each failure, so node iteration order decided the surfaced error: a NOT_FOUND from one node masked a timeout/unavailable from another and the client saw 404 instead of 503.
Failures now fold through ParameterErrorAccumulator: non-404 errors win and a 404 never replaces a held non-404. New gtest pins the 503 outcome for TIMEOUT plus NOT_FOUND in both iteration orders.
Tested: colcon build + colcon test for ros2_medkit_gateway in the jazzy image (2506 tests, 0 failures);
Closes #539