From c2789daac8e991fa23472a62129dbc0423897d85 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 27 Jul 2026 13:11:25 -0300 Subject: [PATCH 1/6] Add parameters to the POST/command-router/executions endpoint --- .../http_api/CommandRouterHttpAPI.cc | 158 ++++++++++++++++++ .../http_api/CommandRouterHttpAPI.h | 9 + src/tests/cpp/command_router_http_api_test.cc | 71 ++++++++ 3 files changed, 238 insertions(+) diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc index f2b7c77d..448c01c4 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc @@ -7,6 +7,7 @@ #include "BusCommandRouterProcessor.h" #include "BusCommandRouterProxy.h" #include "BusCommandRouterProxyStreamPoller.h" +#include "SystemParametersSingleton.h" #define LOG_LEVEL INFO_LEVEL #include "Logger.h" @@ -150,6 +151,44 @@ void CommandRouterHttpAPI::setup_routes() { return; } + if (body.contains("parameters")) { + if (!body["parameters"].is_object()) { + this->set_json_response( + response, 400, {{"error", "Invalid parameters: expected object"}}); + return; + } + + LOG_INFO("CommandRouter HTTP API setting parameters for command=" << command_text); + + for (const auto& [key, value] : body["parameters"].items()) { + string validation_error; + + const optional args = + this->build_set_param_arg(key, value, validation_error); + if (!args.has_value()) { + this->set_json_response(response, 400, {{"error", validation_error}}); + return; + } + + string router_error; + const PollStreamResult poll_result = this->execute_router_command( + "set", + *args, + nullptr, + nullptr, + [&](const string& message) { router_error = message; }, + nullptr); + if (!poll_result.ok) { + LOG_ERROR("CommandRouter HTTP API setting parameter failed for command=" + << command_text << " key=" << key << " args=" << *args + << " error=" << router_error); + this->set_json_response(response, 500, {{"error", router_error}}); + return; + } + } + LOG_INFO("CommandRouter HTTP API parameters set for command=" << command_text); + } + if (this->is_sync_command_type(command_type)) { LOG_INFO("CommandRouter HTTP API sync execution type=" << command_type); @@ -495,3 +534,122 @@ void CommandRouterHttpAPI::set_json_response(httplib::Response& response, int st string content = body.dump(); response.set_content(content, "application/json"); } + +optional CommandRouterHttpAPI::build_set_param_arg(const string& key, + const json& value, + string& error_message) const { + const Properties& known_params = + SystemParametersSingleton::get_instance()->get_command_router_params(); + + const auto param_it = known_params.find(key); + if (param_it == known_params.end()) { + error_message = "Unknown parameter: '" + key + "'"; + return nullopt; + } + + const auto fail = [&](string message) -> optional { + error_message = std::move(message); + return nullopt; + }; + + optional formatted_value; + + if (holds_alternative(param_it->second)) { + if (value.is_boolean()) { + formatted_value = value.get() ? "true" : "false"; + } else if (value.is_string()) { + const string& text = value.get(); + if (text == "true" || text == "false") { + formatted_value = text; + } else { + return fail("Parameter '" + key + "' expects bool (true, false, 1, or 0)"); + } + } else if (value.is_number_integer()) { + const long long number = value.get(); + if (number == 0) { + formatted_value = "false"; + } else if (number == 1) { + formatted_value = "true"; + } else { + return fail("Parameter '" + key + "' expects bool (true, false, 1, or 0)"); + } + } else { + return fail("Parameter '" + key + "' expects bool (true, false, 1, or 0)"); + } + } else if (holds_alternative(param_it->second)) { + if (value.is_number_unsigned()) { + formatted_value = std::to_string(value.get()); + } else if (value.is_number_integer()) { + const long long number = value.get(); + if (number < 0) { + return fail("Parameter '" + key + "' expects unsigned integer"); + } + formatted_value = std::to_string(number); + } else if (value.is_string()) { + const string& text = value.get(); + const bool all_digits = + !text.empty() && + all_of(text.begin(), text.end(), [](unsigned char c) { return isdigit(c); }); + if (!all_digits) { + return fail("Parameter '" + key + "' expects unsigned integer"); + } + formatted_value = text; + } else { + return fail("Parameter '" + key + "' expects unsigned integer"); + } + } else if (holds_alternative(param_it->second)) { + if (value.is_number_integer()) { + formatted_value = std::to_string(value.get()); + } else if (value.is_string()) { + try { + size_t consumed = 0; + const long parsed = stol(value.get(), &consumed); + if (consumed != value.get().size()) { + return fail("Parameter '" + key + "' expects integer"); + } + formatted_value = std::to_string(parsed); + } catch (const exception&) { + return fail("Parameter '" + key + "' expects integer"); + } + } else { + return fail("Parameter '" + key + "' expects integer"); + } + } else if (holds_alternative(param_it->second)) { + if (value.is_number()) { + formatted_value = value.dump(); + } else if (value.is_string()) { + try { + size_t consumed = 0; + stod(value.get(), &consumed); + if (consumed != value.get().size()) { + return fail("Parameter '" + key + "' expects number"); + } + formatted_value = value.get(); + } catch (const exception&) { + return fail("Parameter '" + key + "' expects number"); + } + } else { + return fail("Parameter '" + key + "' expects number"); + } + + if (key == "attention_focus_strictness") { + const double strictness = value.is_number() ? value.get() : stod(*formatted_value); + if (strictness < 0.0 || strictness > 1.0) { + return fail("Parameter '" + key + "' expects a value in range [0.0, 1.0]"); + } + } + } else if (holds_alternative(param_it->second)) { + if (!value.is_string()) { + return fail("Parameter '" + key + "' expects string"); + } + const string& text = value.get(); + if (text.empty()) { + return fail("Parameter '" + key + "' expects non-empty string"); + } + formatted_value = text; + } else { + return fail("Parameter '" + key + "' has unsupported type"); + } + + return "param " + key + " " + *formatted_value; +} diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.h b/src/agents/command_router/http_api/CommandRouterHttpAPI.h index f7ff3ec2..29101986 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.h +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -133,6 +134,14 @@ class CommandRouterHttpAPI : public processor::Processor, public processor::Thre /** @brief Set response status and JSON body. */ void set_json_response(httplib::Response& res, int status_code, const json& payload); + + /** + * @brief Validate a router parameter value and build `param `. + * @return The set-command arg on success; nullopt with error_message on failure. + */ + optional build_set_param_arg(const string& key, + const json& value, + string& error_message) const; }; } // namespace command_router diff --git a/src/tests/cpp/command_router_http_api_test.cc b/src/tests/cpp/command_router_http_api_test.cc index b7567618..d0209ea0 100644 --- a/src/tests/cpp/command_router_http_api_test.cc +++ b/src/tests/cpp/command_router_http_api_test.cc @@ -44,6 +44,14 @@ json make_execution_body(const string& command_type = "query", return {{"command_type", command_type}, {"command_text", command_text}}; } +json make_execution_body_with_parameters(const json& parameters, + const string& command_type = "query", + const string& command_text = "(Similarity \"human\" %V)") { + return {{"command_type", command_type}, + {"command_text", command_text}, + {"parameters", parameters}}; +} + class HangingQueryForwardProxy : public BusCommandProxy { public: void pack_command_line_args() override {} @@ -631,6 +639,69 @@ TEST_F(CommandRouterHttpAPITest, set_param_rejects_unknown_key) { EXPECT_NE(payload["error"].get().find("Unknown parameter"), string::npos); } +TEST_F(CommandRouterHttpAPITest, execution_parameters_accepts_valid_scalar_values) { + auto create = client().Post( + "/command-router/executions", + make_execution_body_with_parameters({{"populate_metta_mapping", true}, + {"use_metta_as_query_tokens", "true"}, + {"max_answers", 1}, + {"count_flag", 1}}) + .dump(), + "application/json"); + ASSERT_TRUE(create); + EXPECT_EQ(create->status, 202); + + auto get_res = client().Post( + "/command-router/executions", make_execution_body("get", "params").dump(), "application/json"); + ASSERT_TRUE(get_res); + ASSERT_EQ(get_res->status, 200); + + const string params = json::parse(get_res->body)["result"].get(); + EXPECT_NE(params.find("populate_metta_mapping: true"), string::npos); + EXPECT_NE(params.find("use_metta_as_query_tokens: true"), string::npos); + EXPECT_NE(params.find("max_answers: 1"), string::npos); + EXPECT_NE(params.find("count_flag: true"), string::npos); +} + +TEST_F(CommandRouterHttpAPITest, execution_parameters_rejects_invalid_values) { + auto unknown_key = client().Post( + "/command-router/executions", + make_execution_body_with_parameters({{"unknown_key", true}}).dump(), + "application/json"); + ASSERT_TRUE(unknown_key); + EXPECT_EQ(unknown_key->status, 400); + EXPECT_NE(json::parse(unknown_key->body)["error"].get().find("Unknown parameter"), + string::npos); + + auto wrong_type = client().Post( + "/command-router/executions", + make_execution_body_with_parameters({{"max_answers", "not_a_number"}}).dump(), + "application/json"); + ASSERT_TRUE(wrong_type); + EXPECT_EQ(wrong_type->status, 400); + EXPECT_NE(json::parse(wrong_type->body)["error"].get().find("unsigned integer"), + string::npos); + + auto out_of_range = client().Post( + "/command-router/executions", + make_execution_body_with_parameters({{"attention_focus_strictness", 2.0}}).dump(), + "application/json"); + ASSERT_TRUE(out_of_range); + EXPECT_EQ(out_of_range->status, 400); + EXPECT_NE(json::parse(out_of_range->body)["error"].get().find("[0.0, 1.0]"), string::npos); + + auto not_object = client().Post( + "/command-router/executions", + json({{"command_type", "query"}, + {"command_text", "(Similarity \"human\" %V)"}, + {"parameters", json::array({1, 2, 3})}}) + .dump(), + "application/json"); + ASSERT_TRUE(not_object); + EXPECT_EQ(not_object->status, 400); + EXPECT_NE(json::parse(not_object->body)["error"].get().find("expected object"), string::npos); +} + TEST_F(CommandRouterHttpAPITest, create_execution_rejects_invalid_requests) { auto bad_json = client().Post("/command-router/executions", "{bad", "application/json"); ASSERT_TRUE(bad_json); From eb5b76780e60614ac56830a99aeb0204d566b3e1 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 27 Jul 2026 13:19:14 -0300 Subject: [PATCH 2/6] more explicit code --- .../http_api/CommandRouterHttpAPI.cc | 2 +- src/tests/cpp/command_router_http_api_test.cc | 59 +++++++++---------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc index 448c01c4..70274351 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc @@ -173,7 +173,7 @@ void CommandRouterHttpAPI::setup_routes() { string router_error; const PollStreamResult poll_result = this->execute_router_command( "set", - *args, + args.value(), nullptr, nullptr, [&](const string& message) { router_error = message; }, diff --git a/src/tests/cpp/command_router_http_api_test.cc b/src/tests/cpp/command_router_http_api_test.cc index d0209ea0..470a043e 100644 --- a/src/tests/cpp/command_router_http_api_test.cc +++ b/src/tests/cpp/command_router_http_api_test.cc @@ -47,9 +47,7 @@ json make_execution_body(const string& command_type = "query", json make_execution_body_with_parameters(const json& parameters, const string& command_type = "query", const string& command_text = "(Similarity \"human\" %V)") { - return {{"command_type", command_type}, - {"command_text", command_text}, - {"parameters", parameters}}; + return {{"command_type", command_type}, {"command_text", command_text}, {"parameters", parameters}}; } class HangingQueryForwardProxy : public BusCommandProxy { @@ -640,14 +638,14 @@ TEST_F(CommandRouterHttpAPITest, set_param_rejects_unknown_key) { } TEST_F(CommandRouterHttpAPITest, execution_parameters_accepts_valid_scalar_values) { - auto create = client().Post( - "/command-router/executions", - make_execution_body_with_parameters({{"populate_metta_mapping", true}, - {"use_metta_as_query_tokens", "true"}, - {"max_answers", 1}, - {"count_flag", 1}}) - .dump(), - "application/json"); + auto create = + client().Post("/command-router/executions", + make_execution_body_with_parameters({{"populate_metta_mapping", true}, + {"use_metta_as_query_tokens", "true"}, + {"max_answers", 1}, + {"count_flag", 1}}) + .dump(), + "application/json"); ASSERT_TRUE(create); EXPECT_EQ(create->status, 202); @@ -664,42 +662,41 @@ TEST_F(CommandRouterHttpAPITest, execution_parameters_accepts_valid_scalar_value } TEST_F(CommandRouterHttpAPITest, execution_parameters_rejects_invalid_values) { - auto unknown_key = client().Post( - "/command-router/executions", - make_execution_body_with_parameters({{"unknown_key", true}}).dump(), - "application/json"); + auto unknown_key = client().Post("/command-router/executions", + make_execution_body_with_parameters({{"unknown_key", true}}).dump(), + "application/json"); ASSERT_TRUE(unknown_key); EXPECT_EQ(unknown_key->status, 400); EXPECT_NE(json::parse(unknown_key->body)["error"].get().find("Unknown parameter"), string::npos); - auto wrong_type = client().Post( - "/command-router/executions", - make_execution_body_with_parameters({{"max_answers", "not_a_number"}}).dump(), - "application/json"); + auto wrong_type = + client().Post("/command-router/executions", + make_execution_body_with_parameters({{"max_answers", "not_a_number"}}).dump(), + "application/json"); ASSERT_TRUE(wrong_type); EXPECT_EQ(wrong_type->status, 400); EXPECT_NE(json::parse(wrong_type->body)["error"].get().find("unsigned integer"), string::npos); - auto out_of_range = client().Post( - "/command-router/executions", - make_execution_body_with_parameters({{"attention_focus_strictness", 2.0}}).dump(), - "application/json"); + auto out_of_range = + client().Post("/command-router/executions", + make_execution_body_with_parameters({{"attention_focus_strictness", 2.0}}).dump(), + "application/json"); ASSERT_TRUE(out_of_range); EXPECT_EQ(out_of_range->status, 400); EXPECT_NE(json::parse(out_of_range->body)["error"].get().find("[0.0, 1.0]"), string::npos); - auto not_object = client().Post( - "/command-router/executions", - json({{"command_type", "query"}, - {"command_text", "(Similarity \"human\" %V)"}, - {"parameters", json::array({1, 2, 3})}}) - .dump(), - "application/json"); + auto not_object = client().Post("/command-router/executions", + json({{"command_type", "query"}, + {"command_text", "(Similarity \"human\" %V)"}, + {"parameters", json::array({1, 2, 3})}}) + .dump(), + "application/json"); ASSERT_TRUE(not_object); EXPECT_EQ(not_object->status, 400); - EXPECT_NE(json::parse(not_object->body)["error"].get().find("expected object"), string::npos); + EXPECT_NE(json::parse(not_object->body)["error"].get().find("expected object"), + string::npos); } TEST_F(CommandRouterHttpAPITest, create_execution_rejects_invalid_requests) { From 39cf6db3257adcd47c788b0857d0d8fc6602ec3c Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 27 Jul 2026 13:24:52 -0300 Subject: [PATCH 3/6] more explicit code --- src/agents/command_router/http_api/CommandRouterHttpAPI.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc index 70274351..7fff7a11 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc @@ -180,7 +180,7 @@ void CommandRouterHttpAPI::setup_routes() { nullptr); if (!poll_result.ok) { LOG_ERROR("CommandRouter HTTP API setting parameter failed for command=" - << command_text << " key=" << key << " args=" << *args + << command_text << " key=" << key << " args=" << args.value() << " error=" << router_error); this->set_json_response(response, 500, {{"error", router_error}}); return; @@ -633,7 +633,8 @@ optional CommandRouterHttpAPI::build_set_param_arg(const string& key, } if (key == "attention_focus_strictness") { - const double strictness = value.is_number() ? value.get() : stod(*formatted_value); + const double strictness = + value.is_number() ? value.get() : stod(formatted_value.value()); if (strictness < 0.0 || strictness > 1.0) { return fail("Parameter '" + key + "' expects a value in range [0.0, 1.0]"); } @@ -651,5 +652,5 @@ optional CommandRouterHttpAPI::build_set_param_arg(const string& key, return fail("Parameter '" + key + "' has unsupported type"); } - return "param " + key + " " + *formatted_value; + return "param " + key + " " + formatted_value.value(); } From 60c1ee8e6d308c37eedd5b90bc39225763f36bdb Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 27 Jul 2026 15:00:45 -0300 Subject: [PATCH 4/6] Improve HTTP API parameter validation and fix related tests --- .../http_api/CommandRouterHttpAPI.cc | 56 ++++++++++++------- .../http_api/CommandRouterHttpAPI.h | 3 +- src/tests/cpp/command_router_http_api_test.cc | 7 ++- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc index 7fff7a11..31e9ec88 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc @@ -160,27 +160,31 @@ void CommandRouterHttpAPI::setup_routes() { LOG_INFO("CommandRouter HTTP API setting parameters for command=" << command_text); + Properties known_params = + SystemParametersSingleton::get_instance()->get_command_router_params(); + vector> set_args; for (const auto& [key, value] : body["parameters"].items()) { string validation_error; - const optional args = - this->build_set_param_arg(key, value, validation_error); + this->build_set_param_arg(known_params, key, value, validation_error); if (!args.has_value()) { this->set_json_response(response, 400, {{"error", validation_error}}); return; } - + set_args.emplace_back(key, args.value()); + } + for (const auto& [key, args] : set_args) { string router_error; const PollStreamResult poll_result = this->execute_router_command( "set", - args.value(), + args, nullptr, nullptr, [&](const string& message) { router_error = message; }, nullptr); if (!poll_result.ok) { LOG_ERROR("CommandRouter HTTP API setting parameter failed for command=" - << command_text << " key=" << key << " args=" << args.value() + << command_text << " key=" << key << " args=" << args << " error=" << router_error); this->set_json_response(response, 500, {{"error", router_error}}); return; @@ -535,13 +539,11 @@ void CommandRouterHttpAPI::set_json_response(httplib::Response& response, int st response.set_content(content, "application/json"); } -optional CommandRouterHttpAPI::build_set_param_arg(const string& key, +optional CommandRouterHttpAPI::build_set_param_arg(Properties& known_params, + const string& key, const json& value, string& error_message) const { - const Properties& known_params = - SystemParametersSingleton::get_instance()->get_command_router_params(); - - const auto param_it = known_params.find(key); + auto param_it = known_params.find(key); if (param_it == known_params.end()) { error_message = "Unknown parameter: '" + key + "'"; return nullopt; @@ -561,6 +563,10 @@ optional CommandRouterHttpAPI::build_set_param_arg(const string& key, const string& text = value.get(); if (text == "true" || text == "false") { formatted_value = text; + } else if (text == "1") { + formatted_value = "true"; + } else if (text == "0") { + formatted_value = "false"; } else { return fail("Parameter '" + key + "' expects bool (true, false, 1, or 0)"); } @@ -577,11 +583,18 @@ optional CommandRouterHttpAPI::build_set_param_arg(const string& key, return fail("Parameter '" + key + "' expects bool (true, false, 1, or 0)"); } } else if (holds_alternative(param_it->second)) { + const auto fits_uint = [](unsigned long long number) { + return static_cast(number) == number; + }; if (value.is_number_unsigned()) { - formatted_value = std::to_string(value.get()); + const unsigned long long number = value.get(); + if (!fits_uint(number)) { + return fail("Parameter '" + key + "' expects unsigned integer"); + } + formatted_value = std::to_string(number); } else if (value.is_number_integer()) { const long long number = value.get(); - if (number < 0) { + if (number < 0 || !fits_uint(static_cast(number))) { return fail("Parameter '" + key + "' expects unsigned integer"); } formatted_value = std::to_string(number); @@ -593,7 +606,16 @@ optional CommandRouterHttpAPI::build_set_param_arg(const string& key, if (!all_digits) { return fail("Parameter '" + key + "' expects unsigned integer"); } - formatted_value = text; + try { + size_t consumed = 0; + const unsigned long long parsed = stoull(text, &consumed); + if (consumed != text.size() || !fits_uint(parsed)) { + return fail("Parameter '" + key + "' expects unsigned integer"); + } + formatted_value = text; + } catch (const exception&) { + return fail("Parameter '" + key + "' expects unsigned integer"); + } } else { return fail("Parameter '" + key + "' expects unsigned integer"); } @@ -631,14 +653,6 @@ optional CommandRouterHttpAPI::build_set_param_arg(const string& key, } else { return fail("Parameter '" + key + "' expects number"); } - - if (key == "attention_focus_strictness") { - const double strictness = - value.is_number() ? value.get() : stod(formatted_value.value()); - if (strictness < 0.0 || strictness > 1.0) { - return fail("Parameter '" + key + "' expects a value in range [0.0, 1.0]"); - } - } } else if (holds_alternative(param_it->second)) { if (!value.is_string()) { return fail("Parameter '" + key + "' expects string"); diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.h b/src/agents/command_router/http_api/CommandRouterHttpAPI.h index 29101986..915115c4 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.h +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.h @@ -139,7 +139,8 @@ class CommandRouterHttpAPI : public processor::Processor, public processor::Thre * @brief Validate a router parameter value and build `param `. * @return The set-command arg on success; nullopt with error_message on failure. */ - optional build_set_param_arg(const string& key, + optional build_set_param_arg(Properties& known_params, + const string& key, const json& value, string& error_message) const; }; diff --git a/src/tests/cpp/command_router_http_api_test.cc b/src/tests/cpp/command_router_http_api_test.cc index 470a043e..c5c1e95b 100644 --- a/src/tests/cpp/command_router_http_api_test.cc +++ b/src/tests/cpp/command_router_http_api_test.cc @@ -121,7 +121,7 @@ class HttpAPIServerFixture { httplib::Client make_client(int port) const { httplib::Client client(TEST_HOST, port); client.set_connection_timeout(2); - client.set_read_timeout(5); + client.set_read_timeout(15); return client; } @@ -681,11 +681,12 @@ TEST_F(CommandRouterHttpAPITest, execution_parameters_rejects_invalid_values) { auto out_of_range = client().Post("/command-router/executions", - make_execution_body_with_parameters({{"attention_focus_strictness", 2.0}}).dump(), + make_execution_body_with_parameters({{"max_answers", 4294967296}}).dump(), "application/json"); ASSERT_TRUE(out_of_range); EXPECT_EQ(out_of_range->status, 400); - EXPECT_NE(json::parse(out_of_range->body)["error"].get().find("[0.0, 1.0]"), string::npos); + EXPECT_NE(json::parse(out_of_range->body)["error"].get().find("unsigned integer"), + string::npos); auto not_object = client().Post("/command-router/executions", json({{"command_type", "query"}, From bc897ed1b838bd69f64da66bb16ccf4b386b28b1 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 27 Jul 2026 20:01:36 -0300 Subject: [PATCH 5/6] Refactor the code for greater simplicity --- .../http_api/CommandRouterHttpAPI.cc | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc index 31e9ec88..575279cc 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc @@ -583,41 +583,36 @@ optional CommandRouterHttpAPI::build_set_param_arg(Properties& known_par return fail("Parameter '" + key + "' expects bool (true, false, 1, or 0)"); } } else if (holds_alternative(param_it->second)) { + const string uint_error = "Parameter '" + key + "' expects unsigned integer"; const auto fits_uint = [](unsigned long long number) { return static_cast(number) == number; }; if (value.is_number_unsigned()) { const unsigned long long number = value.get(); if (!fits_uint(number)) { - return fail("Parameter '" + key + "' expects unsigned integer"); + return fail(uint_error); } formatted_value = std::to_string(number); } else if (value.is_number_integer()) { const long long number = value.get(); if (number < 0 || !fits_uint(static_cast(number))) { - return fail("Parameter '" + key + "' expects unsigned integer"); + return fail(uint_error); } formatted_value = std::to_string(number); } else if (value.is_string()) { const string& text = value.get(); - const bool all_digits = - !text.empty() && - all_of(text.begin(), text.end(), [](unsigned char c) { return isdigit(c); }); - if (!all_digits) { - return fail("Parameter '" + key + "' expects unsigned integer"); - } try { size_t consumed = 0; const unsigned long long parsed = stoull(text, &consumed); if (consumed != text.size() || !fits_uint(parsed)) { - return fail("Parameter '" + key + "' expects unsigned integer"); + return fail(uint_error); } formatted_value = text; } catch (const exception&) { - return fail("Parameter '" + key + "' expects unsigned integer"); + return fail(uint_error); } } else { - return fail("Parameter '" + key + "' expects unsigned integer"); + return fail(uint_error); } } else if (holds_alternative(param_it->second)) { if (value.is_number_integer()) { From 198855a07fcd0d006f89c5397077eeb9b836f44a Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Tue, 28 Jul 2026 09:17:44 -0300 Subject: [PATCH 6/6] Validade all_digits before stoull --- src/agents/command_router/http_api/CommandRouterHttpAPI.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc index 575279cc..95e8d354 100644 --- a/src/agents/command_router/http_api/CommandRouterHttpAPI.cc +++ b/src/agents/command_router/http_api/CommandRouterHttpAPI.cc @@ -601,6 +601,12 @@ optional CommandRouterHttpAPI::build_set_param_arg(Properties& known_par formatted_value = std::to_string(number); } else if (value.is_string()) { const string& text = value.get(); + const bool all_digits = + !text.empty() && + all_of(text.begin(), text.end(), [](unsigned char c) { return isdigit(c); }); + if (!all_digits) { + return fail(uint_error); + } try { size_t consumed = 0; const unsigned long long parsed = stoull(text, &consumed);