From bdd74d3b5ee0a1737d8de05751a3206402dce76c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:27:57 +0000 Subject: [PATCH 1/7] Initial plan From 391ce076c23cd947b7e8645ac0dd0b4ba0cf515f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:34:27 +0000 Subject: [PATCH 2/7] Add /config REST endpoint to fetch agent configuration Co-authored-by: robot-ranger <93950632+robot-ranger@users.noreply.github.com> --- src/mtconnect/sink/rest_sink/rest_service.cpp | 82 +++++++++++++++++++ src/mtconnect/sink/rest_sink/rest_service.hpp | 2 + 2 files changed, 84 insertions(+) diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 1f01a8445..709c0d86a 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -118,6 +118,7 @@ namespace mtconnect { createAssetRoutings(); createProbeRoutings(); createPutObservationRoutings(); + createConfigRoutings(); createFileRoutings(); m_server->addCommands(); @@ -435,6 +436,87 @@ namespace mtconnect { session->writeResponse(std::move(response)); } + void RestService::createConfigRoutings() + { + using namespace rest_sink; + auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { + auto pretty = request->parameter("pretty").value_or(false); + + // Build JSON response with agent configuration + std::stringstream json; + json << "{"; + + bool first = true; + for (const auto& [key, value] : m_options) + { + if (!first) + json << ","; + first = false; + + json << "\"" << key << "\":"; + + // Handle different variant types + std::visit([&json](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + json << "null"; + } + else if constexpr (std::is_same_v) + { + json << (arg ? "true" : "false"); + } + else if constexpr (std::is_same_v) + { + json << arg; + } + else if constexpr (std::is_same_v) + { + json << arg; + } + else if constexpr (std::is_same_v) + { + json << "\"" << arg << "\""; + } + else if constexpr (std::is_same_v) + { + json << arg.count(); + } + else if constexpr (std::is_same_v) + { + json << arg.count(); + } + else if constexpr (std::is_same_v) + { + json << "["; + bool firstItem = true; + for (const auto& item : arg) + { + if (!firstItem) + json << ","; + firstItem = false; + json << "\"" << item << "\""; + } + json << "]"; + } + }, value); + } + + json << "}"; + + ResponsePtr response = make_unique( + rest_sink::status::ok, json.str(), "application/json"); + respond(session, std::move(response), request->m_requestId); + return true; + }; + + m_server + ->addRouting({boost::beast::http::verb::get, "/config?pretty={bool:false}", handler}) + .document("Agent configuration request", + "Returns the current agent configuration as JSON") + .command("config"); + } + void RestService::createFileRoutings() { using namespace rest_sink; diff --git a/src/mtconnect/sink/rest_sink/rest_service.hpp b/src/mtconnect/sink/rest_sink/rest_service.hpp index 17437f2fb..5c317bf89 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.hpp +++ b/src/mtconnect/sink/rest_sink/rest_service.hpp @@ -346,6 +346,8 @@ namespace mtconnect { void createAssetRoutings(); + void createConfigRoutings(); + // Current Data Collection std::string fetchCurrentData(const printer::Printer *printer, const FilterSetOpt &filterSet, const std::optional &at, bool pretty = false, From 4ea86a378c44ae08c6a9696b10261686e2df1631 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:36:26 +0000 Subject: [PATCH 3/7] Add test for /config endpoint Co-authored-by: robot-ranger <93950632+robot-ranger@users.noreply.github.com> --- test_package/agent_test.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 5039741e1..e98fdd537 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -2622,3 +2622,29 @@ TEST_F(AgentTest, should_initialize_observaton_to_initial_value_when_available) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "0"); } } + +TEST_F(AgentTest, should_return_agent_configuration) +{ + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false); + + // Test the /config endpoint + auto session = m_agentTestHelper->m_server->createSession(m_agentTestHelper->m_context); + auto request = make_unique("/config", boost::beast::http::verb::get); + + auto routing = m_agentTestHelper->m_server->getRouting(request->m_path, request->m_verb); + ASSERT_TRUE(routing); + + routing->m_handler(session, std::move(request)); + + // Verify response is JSON + ASSERT_EQ(session->m_mimeType, "application/json"); + ASSERT_EQ(session->m_code, boost::beast::http::status::ok); + + // Parse JSON response + auto json = nlohmann::json::parse(session->m_body); + + // Verify key configuration values are present + ASSERT_TRUE(json.contains("Port")); + ASSERT_TRUE(json.contains("BufferSize")); + ASSERT_TRUE(json.contains("SchemaVersion")); +} From e1c3b6b901d1aff620c5b9a5a76bfb1c20f29972 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:37:45 +0000 Subject: [PATCH 4/7] Fix JSON serialization using nlohmann::json library for proper escaping Co-authored-by: robot-ranger <93950632+robot-ranger@users.noreply.github.com> --- src/mtconnect/sink/rest_sink/rest_service.cpp | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 709c0d86a..c0c5212a8 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -19,6 +19,8 @@ #include +#include + #include "error.hpp" #include "mtconnect/configuration/config_options.hpp" #include "mtconnect/entity/xml_parser.hpp" @@ -439,79 +441,60 @@ namespace mtconnect { void RestService::createConfigRoutings() { using namespace rest_sink; + using json = nlohmann::json; + auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { - auto pretty = request->parameter("pretty").value_or(false); - // Build JSON response with agent configuration - std::stringstream json; - json << "{"; + json config; - bool first = true; for (const auto& [key, value] : m_options) { - if (!first) - json << ","; - first = false; - - json << "\"" << key << "\":"; - // Handle different variant types - std::visit([&json](auto&& arg) { + std::visit([&config, &key](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { - json << "null"; + config[key] = nullptr; } else if constexpr (std::is_same_v) { - json << (arg ? "true" : "false"); + config[key] = arg; } else if constexpr (std::is_same_v) { - json << arg; + config[key] = arg; } else if constexpr (std::is_same_v) { - json << arg; + config[key] = arg; } else if constexpr (std::is_same_v) { - json << "\"" << arg << "\""; + config[key] = arg; } else if constexpr (std::is_same_v) { - json << arg.count(); + config[key] = arg.count(); } else if constexpr (std::is_same_v) { - json << arg.count(); + config[key] = arg.count(); } else if constexpr (std::is_same_v) { - json << "["; - bool firstItem = true; - for (const auto& item : arg) - { - if (!firstItem) - json << ","; - firstItem = false; - json << "\"" << item << "\""; - } - json << "]"; + config[key] = arg; } }, value); } - json << "}"; - ResponsePtr response = make_unique( - rest_sink::status::ok, json.str(), "application/json"); + rest_sink::status::ok, config.dump(), "application/json"); respond(session, std::move(response), request->m_requestId); return true; }; m_server - ->addRouting({boost::beast::http::verb::get, "/config?pretty={bool:false}", handler}) + ->addRouting({boost::beast::http::verb::get, "/config", handler}) .document("Agent configuration request", "Returns the current agent configuration as JSON") .command("config"); From b1492c81405e9fb3fa9edc89f2807b41b614f610 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:39:48 +0000 Subject: [PATCH 5/7] Fix lambda capture and add static assertion for variant exhaustiveness Co-authored-by: robot-ranger <93950632+robot-ranger@users.noreply.github.com> --- src/mtconnect/sink/rest_sink/rest_service.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index c0c5212a8..76a13b72b 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -443,7 +443,7 @@ namespace mtconnect { using namespace rest_sink; using json = nlohmann::json; - auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { + auto handler = [this](SessionPtr session, const RequestPtr request) -> bool { // Build JSON response with agent configuration json config; @@ -484,6 +484,20 @@ namespace mtconnect { { config[key] = arg; } + else + { + // If this static assertion fails, a new type has been added to ConfigOption + // and needs to be handled in the visitor + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v, + "Unhandled type in ConfigOption variant"); + } }, value); } From 9d0d155ce28c5f5472388f3c85fc44955580f134 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:41:26 +0000 Subject: [PATCH 6/7] Add explicit include and fix static assertion with template-dependent false Co-authored-by: robot-ranger <93950632+robot-ranger@users.noreply.github.com> --- src/mtconnect/sink/rest_sink/rest_service.cpp | 17 ++++++----------- test_package/agent_test.cpp | 2 ++ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 76a13b72b..596e83b90 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -486,17 +486,12 @@ namespace mtconnect { } else { - // If this static assertion fails, a new type has been added to ConfigOption - // and needs to be handled in the visitor - static_assert(std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v, - "Unhandled type in ConfigOption variant"); + // If compilation fails here, a new type has been added to ConfigOption + // and needs to be handled above + []() + { + static_assert(flag, "Unhandled type in ConfigOption variant"); + }(); } }, value); } diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index e98fdd537..f9b6106ea 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -29,6 +29,8 @@ #include #include +#include + #include "agent_test_helper.hpp" #include "mtconnect/agent.hpp" #include "mtconnect/asset/file_asset.hpp" From 07e4e79eb86bc621a71567b510e06bfd3e271509 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:42:45 +0000 Subject: [PATCH 7/7] Final implementation of /config endpoint Co-authored-by: robot-ranger <93950632+robot-ranger@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 000000000..945c9b46d --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file