Skip to content

Commit 3d2c610

Browse files
committed
Simplify Unix socket API with client-level configuration
Replace per-method Unix socket overloads with a single client.unix_socket(path) configuration that applies transparently to all HTTP methods. This eliminates overload ambiguity and unifies the TCP/Unix socket interface. Extend Unix socket tests to cover all HTTP methods (PUT, DELETE, PATCH, HEAD, OPTIONS) using the client API directly. Add PATCH schema validation test.
1 parent 05f2132 commit 3d2c610

5 files changed

Lines changed: 92 additions & 167 deletions

File tree

tests/integration/schema_validation_test.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ struct SchemaTestFixture {
6868
}}
6969
});
7070

71+
// PATCH with schema
72+
server.patch("/api/users/:id", [](nlohmann::json& json, http::response& res) {
73+
res.json({{"patched", true}});
74+
}).schema({
75+
{"type", "object"},
76+
{"properties", {
77+
{"name", {{"type", "string"}}},
78+
{"email", {{"type", "string"}}}
79+
}}
80+
});
81+
7182
// Schema with enum validation
7283
server.post("/api/status", [](nlohmann::json& json, http::response& res) {
7384
res.json({{"status", json["status"]}});
@@ -257,3 +268,21 @@ TEST_CASE("Schema validation - PUT with schema", "[server][schema]") {
257268
REQUIRE(res.status_code() == 400);
258269
}
259270
}
271+
272+
TEST_CASE("Schema validation - PATCH with schema", "[server][schema]") {
273+
SchemaTestFixture fixture;
274+
http::client client;
275+
276+
SECTION("valid") {
277+
auto res = client.patch(fixture.base_url + "/api/users/123",
278+
R"({"name":"Patched"})", "application/json");
279+
REQUIRE(res.ok());
280+
REQUIRE(res.json()["patched"] == true);
281+
}
282+
283+
SECTION("wrong type") {
284+
auto res = client.patch(fixture.base_url + "/api/users/123",
285+
R"({"name":789})", "application/json");
286+
REQUIRE(res.status_code() == 400);
287+
}
288+
}

tests/integration/unix_socket_server_test.cpp

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,17 @@ TEST_CASE("Unix Socket GET request/response", "[unix][server][get][integration]"
113113

114114
fixture.start_server();
115115
http::client client;
116-
client.timeout(10s);
116+
client.timeout(10s).unix_socket(fixture.socket_path);
117117

118118
SECTION("Simple GET returns 200 with JSON body") {
119-
auto response = client.get(fixture.url("/hello"), fixture.socket_path);
119+
auto response = client.get(fixture.url("/hello"));
120120
REQUIRE(response.ok());
121121
auto json = response.json();
122122
REQUIRE(json["message"] == "hello from unix socket");
123123
}
124124

125125
SECTION("GET with path parameter") {
126-
auto response = client.get(fixture.url("/greet/world"), fixture.socket_path);
126+
auto response = client.get(fixture.url("/greet/world"));
127127
REQUIRE(response.ok());
128128
auto json = response.json();
129129
REQUIRE(json["greeting"] == "hello world");
@@ -145,11 +145,11 @@ TEST_CASE("Unix Socket POST with JSON body", "[unix][server][post][integration]"
145145

146146
fixture.start_server();
147147
http::client client;
148-
client.timeout(10s);
148+
client.timeout(10s).unix_socket(fixture.socket_path);
149149

150150
SECTION("POST JSON body is echoed back") {
151151
std::string body = R"({"name": "unix_test", "value": 42})";
152-
auto response = client.post(fixture.url("/echo-json"), fixture.socket_path,
152+
auto response = client.post(fixture.url("/echo-json"),
153153
body, "application/json");
154154
REQUIRE(response.ok());
155155
auto json = response.json();
@@ -183,45 +183,73 @@ TEST_CASE("Unix Socket multiple HTTP methods", "[unix][server][methods][integrat
183183
res.json({{"method", "DELETE"}});
184184
});
185185

186+
server.patch("/resource", [](http::request& req, http::response& res) {
187+
res.json({{"method", "PATCH"}, {"body", req.body()}});
188+
});
189+
190+
server.head("/resource", [](http::response& res) {
191+
res.status(http::http_response::status::ok);
192+
res.header("X-Method", "HEAD");
193+
res.send("");
194+
});
195+
196+
server.options("/resource", [](http::response& res) {
197+
res.header("Allow", "GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS");
198+
res.send("");
199+
});
200+
186201
fixture.start_server();
187202
http::client client;
188-
client.timeout(10s);
203+
client.timeout(10s).unix_socket(fixture.socket_path);
189204

190205
SECTION("GET on /resource") {
191-
auto response = client.get(fixture.url("/resource"), fixture.socket_path);
206+
auto response = client.get(fixture.url("/resource"));
192207
REQUIRE(response.ok());
193208
REQUIRE(response.json()["method"] == "GET");
194209
}
195210

196211
SECTION("POST on /resource") {
197-
auto response = client.post(fixture.url("/resource"), fixture.socket_path,
212+
auto response = client.post(fixture.url("/resource"),
198213
"", "text/plain");
199214
REQUIRE(response.ok());
200215
REQUIRE(response.json()["method"] == "POST");
201216
}
202217

203-
SECTION("PUT on /resource via generic send") {
204-
auto request = std::make_shared<http::http_request>();
205-
request->set_method(http::method::PUT);
206-
request->set_url(fixture.url("/resource"));
207-
request->set_unix_socket(fixture.socket_path);
208-
request->set_content("test body", "text/plain");
209-
auto response = client.send(request);
218+
SECTION("PUT on /resource") {
219+
auto response = client.put(fixture.url("/resource"),
220+
"test body", "text/plain");
210221
REQUIRE(response.ok());
211222
auto json = response.json();
212223
REQUIRE(json["method"] == "PUT");
213224
REQUIRE(json["body"] == "test body");
214225
}
215226

216-
SECTION("DELETE on /resource via generic send") {
217-
auto request = std::make_shared<http::http_request>();
218-
request->set_method(http::method::DELETE);
219-
request->set_url(fixture.url("/resource"));
220-
request->set_unix_socket(fixture.socket_path);
221-
auto response = client.send(request);
227+
SECTION("DELETE on /resource") {
228+
auto response = client.del(fixture.url("/resource"));
222229
REQUIRE(response.ok());
223230
REQUIRE(response.json()["method"] == "DELETE");
224231
}
232+
233+
SECTION("PATCH on /resource") {
234+
auto response = client.patch(fixture.url("/resource"),
235+
"patch body", "text/plain");
236+
REQUIRE(response.ok());
237+
auto json = response.json();
238+
REQUIRE(json["method"] == "PATCH");
239+
REQUIRE(json["body"] == "patch body");
240+
}
241+
242+
SECTION("HEAD on /resource") {
243+
auto response = client.head(fixture.url("/resource"));
244+
REQUIRE(response.ok());
245+
REQUIRE(response.header("X-Method") == "HEAD");
246+
}
247+
248+
SECTION("OPTIONS on /resource") {
249+
auto response = client.options(fixture.url("/resource"));
250+
REQUIRE(response.ok());
251+
REQUIRE(response.header("Allow").find("GET") != std::string::npos);
252+
}
225253
}
226254

227255
// ============================================================================
@@ -240,11 +268,11 @@ TEST_CASE("Unix Socket multiple sequential requests", "[unix][server][sequential
240268

241269
fixture.start_server();
242270
http::client client;
243-
client.timeout(10s);
271+
client.timeout(10s).unix_socket(fixture.socket_path);
244272

245273
SECTION("Multiple sequential requests succeed") {
246274
for (int i = 1; i <= 5; ++i) {
247-
auto response = client.get(fixture.url("/count"), fixture.socket_path);
275+
auto response = client.get(fixture.url("/count"));
248276
REQUIRE(response.ok());
249277
REQUIRE(response.json()["count"] == i);
250278
}
@@ -268,11 +296,11 @@ TEST_CASE("Unix Socket custom headers", "[unix][server][headers][integration]")
268296

269297
fixture.start_server();
270298
http::client client;
271-
client.timeout(10s);
299+
client.timeout(10s).unix_socket(fixture.socket_path);
272300

273301
SECTION("Send and receive custom headers") {
274302
http::headers_map headers = {{"X-Custom-Input", "ping"}};
275-
auto response = client.get(fixture.url("/headers"), fixture.socket_path, headers);
303+
auto response = client.get(fixture.url("/headers"), headers);
276304
REQUIRE(response.ok());
277305
REQUIRE(response.json()["received_header"] == "ping");
278306
REQUIRE(response.header("X-Custom-Output") == "pong");
@@ -293,16 +321,16 @@ TEST_CASE("Unix Socket not found handler", "[unix][server][notfound][integration
293321

294322
fixture.start_server();
295323
http::client client;
296-
client.timeout(10s);
324+
client.timeout(10s).unix_socket(fixture.socket_path);
297325

298326
SECTION("Request to existing route returns 200") {
299-
auto response = client.get(fixture.url("/exists"), fixture.socket_path);
327+
auto response = client.get(fixture.url("/exists"));
300328
REQUIRE(response.ok());
301329
REQUIRE(response.json()["found"] == true);
302330
}
303331

304332
SECTION("Request to non-existent route returns 404") {
305-
auto response = client.get(fixture.url("/does-not-exist"), fixture.socket_path);
333+
auto response = client.get(fixture.url("/does-not-exist"));
306334
REQUIRE(response.status() == 404);
307335
}
308336
}
@@ -325,10 +353,10 @@ TEST_CASE("Unix Socket large response body", "[unix][server][large][integration]
325353

326354
fixture.start_server();
327355
http::client client;
328-
client.timeout(30s);
356+
client.timeout(30s).unix_socket(fixture.socket_path);
329357

330358
SECTION("Large response body arrives intact") {
331-
auto response = client.get(fixture.url("/large"), fixture.socket_path);
359+
auto response = client.get(fixture.url("/large"));
332360
REQUIRE(response.ok());
333361
REQUIRE(response.body().size() == large_size);
334362
REQUIRE(response.body() == large_body);

thinger/http/client/client.hpp

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -93,41 +93,6 @@ class client : public http_client_base {
9393
return exec(http_client_base::options(url, std::move(headers)));
9494
}
9595

96-
// Unix socket variants
97-
client_response get(const std::string& url, const std::string& unix_socket, headers_map headers = {}) {
98-
return exec(http_client_base::get(url, unix_socket, std::move(headers)));
99-
}
100-
101-
client_response post(const std::string& url, const std::string& unix_socket,
102-
std::string body, std::string content_type,
103-
headers_map headers = {}) {
104-
return exec(http_client_base::post(url, unix_socket, std::move(body), std::move(content_type), std::move(headers)));
105-
}
106-
107-
client_response put(const std::string& url, const std::string& unix_socket,
108-
std::string body, std::string content_type,
109-
headers_map headers = {}) {
110-
return exec(http_client_base::put(url, unix_socket, std::move(body), std::move(content_type), std::move(headers)));
111-
}
112-
113-
client_response patch(const std::string& url, const std::string& unix_socket,
114-
std::string body, std::string content_type,
115-
headers_map headers = {}) {
116-
return exec(http_client_base::patch(url, unix_socket, std::move(body), std::move(content_type), std::move(headers)));
117-
}
118-
119-
client_response del(const std::string& url, const std::string& unix_socket, headers_map headers = {}) {
120-
return exec(http_client_base::del(url, unix_socket, std::move(headers)));
121-
}
122-
123-
client_response head(const std::string& url, const std::string& unix_socket, headers_map headers = {}) {
124-
return exec(http_client_base::head(url, unix_socket, std::move(headers)));
125-
}
126-
127-
client_response options(const std::string& url, const std::string& unix_socket, headers_map headers = {}) {
128-
return exec(http_client_base::options(url, unix_socket, std::move(headers)));
129-
}
130-
13196
// Generic send with custom request
13297
client_response send(std::shared_ptr<http_request> request) {
13398
return exec(http_client_base::send(std::move(request)));

thinger/http/client/http_client_base.cpp

Lines changed: 3 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,9 @@ std::shared_ptr<http_request> http_client_base::create_request(method m, const s
2424
auto request = std::make_shared<http_request>();
2525
request->set_method(m);
2626
request->set_url(url);
27-
apply_default_headers(request);
28-
return request;
29-
}
30-
31-
std::shared_ptr<http_request> http_client_base::create_request(method m, const std::string& url,
32-
const std::string& unix_socket) {
33-
auto request = std::make_shared<http_request>();
34-
request->set_method(m);
35-
request->set_url(url);
36-
request->set_unix_socket(unix_socket);
27+
if (!unix_socket_.empty()) {
28+
request->set_unix_socket(unix_socket_);
29+
}
3730
apply_default_headers(request);
3831
return request;
3932
}
@@ -176,82 +169,6 @@ awaitable<client_response> http_client_base::options(const std::string& url, hea
176169
co_return co_await send(request);
177170
}
178171

179-
// Unix socket variants
180-
awaitable<client_response> http_client_base::get(const std::string& url, const std::string& unix_socket,
181-
headers_map headers) {
182-
auto request = create_request(method::GET, url, unix_socket);
183-
for (const auto& [key, value] : headers) {
184-
request->add_header(key, value);
185-
}
186-
co_return co_await send(request);
187-
}
188-
189-
awaitable<client_response> http_client_base::post(const std::string& url, const std::string& unix_socket,
190-
std::string body, std::string content_type,
191-
headers_map headers) {
192-
auto request = create_request(method::POST, url, unix_socket);
193-
if (!body.empty()) {
194-
request->set_content(std::move(body), std::move(content_type));
195-
}
196-
for (const auto& [key, value] : headers) {
197-
request->add_header(key, value);
198-
}
199-
co_return co_await send(request);
200-
}
201-
202-
awaitable<client_response> http_client_base::put(const std::string& url, const std::string& unix_socket,
203-
std::string body, std::string content_type,
204-
headers_map headers) {
205-
auto request = create_request(method::PUT, url, unix_socket);
206-
if (!body.empty()) {
207-
request->set_content(std::move(body), std::move(content_type));
208-
}
209-
for (const auto& [key, value] : headers) {
210-
request->add_header(key, value);
211-
}
212-
co_return co_await send(request);
213-
}
214-
215-
awaitable<client_response> http_client_base::patch(const std::string& url, const std::string& unix_socket,
216-
std::string body, std::string content_type,
217-
headers_map headers) {
218-
auto request = create_request(method::PATCH, url, unix_socket);
219-
if (!body.empty()) {
220-
request->set_content(std::move(body), std::move(content_type));
221-
}
222-
for (const auto& [key, value] : headers) {
223-
request->add_header(key, value);
224-
}
225-
co_return co_await send(request);
226-
}
227-
228-
awaitable<client_response> http_client_base::del(const std::string& url, const std::string& unix_socket,
229-
headers_map headers) {
230-
auto request = create_request(method::DELETE, url, unix_socket);
231-
for (const auto& [key, value] : headers) {
232-
request->add_header(key, value);
233-
}
234-
co_return co_await send(request);
235-
}
236-
237-
awaitable<client_response> http_client_base::head(const std::string& url, const std::string& unix_socket,
238-
headers_map headers) {
239-
auto request = create_request(method::HEAD, url, unix_socket);
240-
for (const auto& [key, value] : headers) {
241-
request->add_header(key, value);
242-
}
243-
co_return co_await send(request);
244-
}
245-
246-
awaitable<client_response> http_client_base::options(const std::string& url, const std::string& unix_socket,
247-
headers_map headers) {
248-
auto request = create_request(method::OPTIONS, url, unix_socket);
249-
for (const auto& [key, value] : headers) {
250-
request->add_header(key, value);
251-
}
252-
co_return co_await send(request);
253-
}
254-
255172
awaitable<client_response> http_client_base::send(std::shared_ptr<http_request> request) {
256173
auto connection = get_or_create_connection(request);
257174
co_return co_await send_with_redirects(std::move(request), connection, 0);

0 commit comments

Comments
 (0)