11#include " contract_test_hook.hpp"
22
3+ #include < launchdarkly/detail/serialization/json_primitives.hpp>
4+ #include < launchdarkly/detail/serialization/json_value.hpp>
35#include < launchdarkly/serialization/json_context.hpp>
46#include < launchdarkly/serialization/json_evaluation_reason.hpp>
5- #include < launchdarkly/detail/serialization/json_value.hpp>
6- #include < launchdarkly/detail/serialization/json_primitives.hpp>
77
8- #include < boost/asio/ip/tcp.hpp>
98#include < boost/asio/connect.hpp>
9+ #include < boost/asio/ip/tcp.hpp>
1010#include < boost/asio/post.hpp>
1111#include < boost/beast/core.hpp>
1212#include < boost/beast/http.hpp>
@@ -55,10 +55,8 @@ std::optional<std::string> ContractTestHook::GetErrorForStage(
5555
5656void ContractTestHook::PostCallback (std::string const & stage,
5757 nlohmann::json const & payload) {
58- // Parse the callback URI
5958 auto uri_result = boost::urls::parse_uri (config_.callbackUri );
6059 if (!uri_result) {
61- // Invalid URI, skip callback
6260 return ;
6361 }
6462
@@ -70,57 +68,44 @@ void ContractTestHook::PostCallback(std::string const& stage,
7068 target = " /" ;
7169 }
7270
73- std::string body = payload.dump ();
74-
75- // Create a shared connection and post asynchronously without blocking
76- // This uses fire-and-forget semantics - we don't wait for response
77- auto conn = std::make_shared<beast::tcp_stream>(executor_);
78- auto resolver = std::make_shared<tcp::resolver>(executor_);
79-
80- // Capture everything needed by value in shared_ptrs
81- auto req = std::make_shared<http::request<http::string_body>>();
82- req->method (http::verb::post );
83- req->target (target);
84- req->version (11 );
85- req->set (http::field::host, host);
86- req->set (http::field::user_agent, " cpp-server-sdk-contract-tests" );
87- req->set (http::field::content_type, " application/json" );
88- req->body () = body;
89- req->prepare_payload ();
90-
91- // Start async resolution
92- resolver->async_resolve (host, port,
93- [conn, resolver, req](beast::error_code ec, tcp::resolver::results_type results) {
94- if (ec) return ; // Silently ignore resolution errors
95-
96- // Connect asynchronously
97- conn->async_connect (results,
98- [conn, resolver, req](beast::error_code ec, tcp::resolver::results_type::endpoint_type) {
99- if (ec) return ; // Silently ignore connection errors
100-
101- // Write request asynchronously
102- http::async_write (*conn, *req,
103- [conn, resolver, req](beast::error_code ec, std::size_t ) {
104- if (ec) return ; // Silently ignore write errors
105-
106- // Read response asynchronously (but don't wait for result)
107- auto res = std::make_shared<http::response<http::string_body>>();
108- auto buffer = std::make_shared<beast::flat_buffer>();
109- http::async_read (*conn, *buffer, *res,
110- [conn, resolver, req, res, buffer](beast::error_code ec, std::size_t ) {
111- // Close connection gracefully
112- conn->socket ().shutdown (tcp::socket::shutdown_both, ec);
113- // Cleanup happens automatically via shared_ptr
114- });
115- });
116- });
117- });
71+ // Send synchronously so callbacks from sequential hook invocations arrive
72+ // at the harness in the order the SDK invoked them.
73+ beast::error_code ec;
74+ tcp::resolver resolver (executor_);
75+ auto results = resolver.resolve (host, port, ec);
76+ if (ec) {
77+ return ;
78+ }
79+
80+ beast::tcp_stream stream (executor_);
81+ stream.connect (results, ec);
82+ if (ec) {
83+ return ;
84+ }
85+
86+ http::request<http::string_body> req{http::verb::post , target, 11 };
87+ req.set (http::field::host, host);
88+ req.set (http::field::user_agent, " cpp-server-sdk-contract-tests" );
89+ req.set (http::field::content_type, " application/json" );
90+ req.body () = payload.dump ();
91+ req.prepare_payload ();
92+
93+ http::write (stream, req, ec);
94+ if (ec) {
95+ return ;
96+ }
97+
98+ beast::flat_buffer buffer;
99+ http::response<http::string_body> res;
100+ http::read (stream, buffer, res, ec);
101+
102+ beast::error_code ignore_ec;
103+ stream.socket ().shutdown (tcp::socket::shutdown_both, ignore_ec);
118104}
119105
120106EvaluationSeriesData ContractTestHook::BeforeEvaluation (
121107 EvaluationSeriesContext const & series_context,
122108 EvaluationSeriesData data) {
123-
124109 // Check if we should throw an error for this stage
125110 auto error = GetErrorForStage (" beforeEvaluation" );
126111 if (error) {
@@ -149,12 +134,10 @@ EvaluationSeriesData ContractTestHook::BeforeEvaluation(
149134 // EvaluationSeriesContext
150135 nlohmann::json ctx;
151136 ctx[" flagKey" ] = std::string (series_context.FlagKey ());
152- ctx[" context" ] = nlohmann::json::parse (
153- boost::json::serialize (
154- boost::json::value_from (series_context.EvaluationContext ())));
155- ctx[" defaultValue" ] = nlohmann::json::parse (
156- boost::json::serialize (
157- boost::json::value_from (series_context.DefaultValue ())));
137+ ctx[" context" ] = nlohmann::json::parse (boost::json::serialize (
138+ boost::json::value_from (series_context.EvaluationContext ())));
139+ ctx[" defaultValue" ] = nlohmann::json::parse (boost::json::serialize (
140+ boost::json::value_from (series_context.DefaultValue ())));
158141 ctx[" method" ] = std::string (series_context.Method ());
159142 if (series_context.EnvironmentId ()) {
160143 ctx[" environmentId" ] = std::string (*series_context.EnvironmentId ());
@@ -181,7 +164,6 @@ EvaluationSeriesData ContractTestHook::AfterEvaluation(
181164 EvaluationSeriesContext const & series_context,
182165 EvaluationSeriesData data,
183166 launchdarkly::EvaluationDetail<launchdarkly::Value> const & detail) {
184-
185167 // Check if we should throw an error for this stage
186168 auto error = GetErrorForStage (" afterEvaluation" );
187169 if (error) {
@@ -195,12 +177,10 @@ EvaluationSeriesData ContractTestHook::AfterEvaluation(
195177 // EvaluationSeriesContext
196178 nlohmann::json ctx;
197179 ctx[" flagKey" ] = std::string (series_context.FlagKey ());
198- ctx[" context" ] = nlohmann::json::parse (
199- boost::json::serialize (
200- boost::json::value_from (series_context.EvaluationContext ())));
201- ctx[" defaultValue" ] = nlohmann::json::parse (
202- boost::json::serialize (
203- boost::json::value_from (series_context.DefaultValue ())));
180+ ctx[" context" ] = nlohmann::json::parse (boost::json::serialize (
181+ boost::json::value_from (series_context.EvaluationContext ())));
182+ ctx[" defaultValue" ] = nlohmann::json::parse (boost::json::serialize (
183+ boost::json::value_from (series_context.DefaultValue ())));
204184 ctx[" method" ] = std::string (series_context.Method ());
205185 if (series_context.EnvironmentId ()) {
206186 ctx[" environmentId" ] = std::string (*series_context.EnvironmentId ());
@@ -236,9 +216,7 @@ EvaluationSeriesData ContractTestHook::AfterEvaluation(
236216 return data;
237217}
238218
239- void ContractTestHook::AfterTrack (
240- TrackSeriesContext const & series_context) {
241-
219+ void ContractTestHook::AfterTrack (TrackSeriesContext const & series_context) {
242220 // Check if we should throw an error for this stage
243221 auto error = GetErrorForStage (" afterTrack" );
244222 if (error) {
@@ -251,17 +229,15 @@ void ContractTestHook::AfterTrack(
251229
252230 // TrackSeriesContext
253231 nlohmann::json ctx;
254- ctx[" context" ] = nlohmann::json::parse (
255- boost::json::serialize (
256- boost::json::value_from (series_context.TrackContext ())));
232+ ctx[" context" ] = nlohmann::json::parse (boost::json::serialize (
233+ boost::json::value_from (series_context.TrackContext ())));
257234 ctx[" key" ] = std::string (series_context.Key ());
258235 if (series_context.MetricValue ()) {
259236 ctx[" metricValue" ] = *series_context.MetricValue ();
260237 }
261238 if (series_context.Data ()) {
262- ctx[" data" ] = nlohmann::json::parse (
263- boost::json::serialize (
264- boost::json::value_from (series_context.Data ()->get ())));
239+ ctx[" data" ] = nlohmann::json::parse (boost::json::serialize (
240+ boost::json::value_from (series_context.Data ()->get ())));
265241 }
266242 if (series_context.EnvironmentId ()) {
267243 ctx[" environmentId" ] = std::string (*series_context.EnvironmentId ());
0 commit comments