Skip to content

Commit 26915ab

Browse files
committed
fix(gateway): allow slashes in configuration parameter IDs
ROS2 parameters like qos_overrides./parameter_events.publisher.depth contain slashes in their names. cpp-httplib decodes percent-encoded URLs (%2F -> /), so the route regex must use (.+) instead of ([^/]+) to match the full parameter name. This is consistent with the existing pattern for topic routes.
1 parent 5d5a60b commit 26915ab

File tree

2 files changed

+31
-13
lines changed

2 files changed

+31
-13
lines changed

src/ros2_medkit_gateway/src/http/rest_server.cpp

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -284,19 +284,21 @@ void RESTServer::setup_routes() {
284284
});
285285

286286
// App configurations - get specific
287-
srv->Get((api_path("/apps") + R"(/([^/]+)/configurations/([^/]+)$)"),
287+
// Use (.+) for config_id to accept slashes from percent-encoded URLs (%2F -> /)
288+
// ROS2 parameters like qos_overrides./parameter_events.publisher.depth contain slashes
289+
srv->Get((api_path("/apps") + R"(/([^/]+)/configurations/(.+)$)"),
288290
[this](const httplib::Request & req, httplib::Response & res) {
289291
config_handlers_->handle_get_configuration(req, res);
290292
});
291293

292294
// App configurations - set
293-
srv->Put((api_path("/apps") + R"(/([^/]+)/configurations/([^/]+)$)"),
295+
srv->Put((api_path("/apps") + R"(/([^/]+)/configurations/(.+)$)"),
294296
[this](const httplib::Request & req, httplib::Response & res) {
295297
config_handlers_->handle_set_configuration(req, res);
296298
});
297299

298300
// App configurations - delete single
299-
srv->Delete((api_path("/apps") + R"(/([^/]+)/configurations/([^/]+)$)"),
301+
srv->Delete((api_path("/apps") + R"(/([^/]+)/configurations/(.+)$)"),
300302
[this](const httplib::Request & req, httplib::Response & res) {
301303
config_handlers_->handle_delete_configuration(req, res);
302304
});
@@ -391,17 +393,17 @@ void RESTServer::setup_routes() {
391393
config_handlers_->handle_list_configurations(req, res);
392394
});
393395

394-
srv->Get((api_path("/functions") + R"(/([^/]+)/configurations/([^/]+)$)"),
396+
srv->Get((api_path("/functions") + R"(/([^/]+)/configurations/(.+)$)"),
395397
[this](const httplib::Request & req, httplib::Response & res) {
396398
config_handlers_->handle_get_configuration(req, res);
397399
});
398400

399-
srv->Put((api_path("/functions") + R"(/([^/]+)/configurations/([^/]+)$)"),
401+
srv->Put((api_path("/functions") + R"(/([^/]+)/configurations/(.+)$)"),
400402
[this](const httplib::Request & req, httplib::Response & res) {
401403
config_handlers_->handle_set_configuration(req, res);
402404
});
403405

404-
srv->Delete((api_path("/functions") + R"(/([^/]+)/configurations/([^/]+)$)"),
406+
srv->Delete((api_path("/functions") + R"(/([^/]+)/configurations/(.+)$)"),
405407
[this](const httplib::Request & req, httplib::Response & res) {
406408
config_handlers_->handle_delete_configuration(req, res);
407409
});
@@ -526,17 +528,17 @@ void RESTServer::setup_routes() {
526528
config_handlers_->handle_list_configurations(req, res);
527529
});
528530

529-
srv->Get((api_path("/areas") + R"(/([^/]+)/configurations/([^/]+)$)"),
531+
srv->Get((api_path("/areas") + R"(/([^/]+)/configurations/(.+)$)"),
530532
[this](const httplib::Request & req, httplib::Response & res) {
531533
config_handlers_->handle_get_configuration(req, res);
532534
});
533535

534-
srv->Put((api_path("/areas") + R"(/([^/]+)/configurations/([^/]+)$)"),
536+
srv->Put((api_path("/areas") + R"(/([^/]+)/configurations/(.+)$)"),
535537
[this](const httplib::Request & req, httplib::Response & res) {
536538
config_handlers_->handle_set_configuration(req, res);
537539
});
538540

539-
srv->Delete((api_path("/areas") + R"(/([^/]+)/configurations/([^/]+)$)"),
541+
srv->Delete((api_path("/areas") + R"(/([^/]+)/configurations/(.+)$)"),
540542
[this](const httplib::Request & req, httplib::Response & res) {
541543
config_handlers_->handle_delete_configuration(req, res);
542544
});
@@ -670,19 +672,19 @@ void RESTServer::setup_routes() {
670672
});
671673

672674
// Get specific configuration (parameter) - register before general route
673-
srv->Get((api_path("/components") + R"(/([^/]+)/configurations/([^/]+)$)"),
675+
srv->Get((api_path("/components") + R"(/([^/]+)/configurations/(.+)$)"),
674676
[this](const httplib::Request & req, httplib::Response & res) {
675677
config_handlers_->handle_get_configuration(req, res);
676678
});
677679

678680
// Set configuration (parameter)
679-
srv->Put((api_path("/components") + R"(/([^/]+)/configurations/([^/]+)$)"),
681+
srv->Put((api_path("/components") + R"(/([^/]+)/configurations/(.+)$)"),
680682
[this](const httplib::Request & req, httplib::Response & res) {
681683
config_handlers_->handle_set_configuration(req, res);
682684
});
683685

684686
// Delete (reset) single configuration to default value
685-
srv->Delete((api_path("/components") + R"(/([^/]+)/configurations/([^/]+)$)"),
687+
srv->Delete((api_path("/components") + R"(/([^/]+)/configurations/(.+)$)"),
686688
[this](const httplib::Request & req, httplib::Response & res) {
687689
config_handlers_->handle_delete_configuration(req, res);
688690
});

src/ros2_medkit_gateway/test/test_configuration_manager.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ TEST_F(TestConfigurationManager, test_concurrent_parameter_operations_no_executo
378378
for (int op = 0; op < kOpsPerThread; ++op) {
379379
try {
380380
// Mix different operations to stress test serialization
381-
switch ((i + op) % 4) {
381+
switch ((i + op) % 6) {
382382
case 0: {
383383
auto result = config_manager_->list_parameters("/test_config_manager_node");
384384
if (result.success) {
@@ -408,6 +408,22 @@ TEST_F(TestConfigurationManager, test_concurrent_parameter_operations_no_executo
408408
}
409409
break;
410410
}
411+
case 4: {
412+
auto result = config_manager_->reset_parameter("/test_config_manager_node", "concurrent_test_int");
413+
if (result.success) {
414+
success_count++;
415+
}
416+
break;
417+
}
418+
case 5: {
419+
auto result = config_manager_->reset_all_parameters("/test_config_manager_node");
420+
// reset_all_parameters returns success=false if some params are read-only,
421+
// but data is still populated - count as success if no exception
422+
if (result.success || result.data.contains("reset_count")) {
423+
success_count++;
424+
}
425+
break;
426+
}
411427
}
412428
} catch (const std::exception & e) {
413429
// This should NOT happen - executor conflicts would throw here

0 commit comments

Comments
 (0)