Skip to content

Commit 308852d

Browse files
committed
Make QueryParameters real
1 parent ce8a34c commit 308852d

File tree

4 files changed

+114
-21
lines changed

4 files changed

+114
-21
lines changed

simple_http.hpp

+34-13
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ using ErrorCallback = std::function<void(const std::string&)>;
9191
inline static ErrorCallback NoopErrorCallback = [](auto&){};
9292

9393
using Headers = std::unordered_map<std::string, std::string>;
94-
using QueryParameters = std::vector<std::pair<QueryParameterKey, QueryParameterValue>>;
9594

9695
inline static const std::string WHITESPACE = "\n\t\f\v\r ";
9796

@@ -119,6 +118,34 @@ inline static std::vector<std::string> vec(const std::string& candidate, const c
119118
return container;
120119
}
121120

121+
struct QueryParameters final {
122+
QueryParameters() = default;
123+
124+
explicit QueryParameters(std::vector<std::pair<QueryParameterKey , QueryParameterValue>> values)
125+
: values_(std::move(values))
126+
{ }
127+
128+
[[nodiscard]]
129+
const std::vector<std::pair<QueryParameterKey , QueryParameterValue>> &values() const {
130+
return values_;
131+
}
132+
133+
[[nodiscard]]
134+
std::string to_string() const {
135+
std::stringstream ss;
136+
if (!values_.empty()) {
137+
ss << "?" << values_[0].first << "=" << values_[0].second;
138+
for (long unsigned int i = 1; i < values_.size(); ++i) {
139+
ss << "&" << values_[i].first << "=" << values_[i].second;
140+
}
141+
}
142+
return ss.str();
143+
}
144+
145+
private:
146+
std::vector<std::pair<QueryParameterKey, QueryParameterValue>> values_;
147+
};
148+
122149
struct HttpResponseHeaders final {
123150
explicit HttpResponseHeaders(Headers headers) : headers_(std::move(headers)) {}
124151
explicit HttpResponseHeaders(const std::string &header_string) : headers_(parse(header_string)) {}
@@ -163,10 +190,12 @@ struct PathSegments final {
163190
return *this;
164191
}
165192

193+
[[nodiscard]]
166194
const std::vector<PathSegment>& value() const {
167195
return value_;
168196
}
169197

198+
[[nodiscard]]
170199
std::string to_string() const {
171200
std::stringstream ss;
172201

@@ -222,8 +251,8 @@ struct HttpUrl final {
222251
}
223252

224253
[[nodiscard]]
225-
HttpUrl& with_path_segments(PathSegments path_segments) {
226-
path_segments_ = std::move(path_segments);
254+
HttpUrl& with_path_segments(const PathSegments& path_segments) {
255+
path_segments_ = path_segments;
227256
return *this;
228257
}
229258

@@ -257,15 +286,7 @@ struct HttpUrl final {
257286
[[nodiscard]]
258287
std::string to_string() const {
259288
std::stringstream ss;
260-
ss << protocol_ << "://" << host_ << path_segments_.to_string();
261-
262-
if (!query_parameters_.empty()) {
263-
ss << "?" << query_parameters_[0].first << "=" << query_parameters_[0].second;
264-
for (long unsigned int i = 1; i < query_parameters_.size(); ++i) {
265-
ss << "&" << query_parameters_[i].first << "=" << query_parameters_[i].second;
266-
}
267-
}
268-
289+
ss << protocol_ << "://" << host_ << path_segments_.to_string() << query_parameters_.to_string();
269290
return trim(ss.str());
270291
}
271292
};
@@ -386,7 +407,7 @@ inline static Predicate<HttpStatusCode> server_error() {
386407

387408
template<class A>
388409
A wrap_response(std::optional<HttpResponse> response,
389-
std::function<A(const std::optional<HttpResponse> &response)> wrapperFn) {
410+
std::function<A(const std::optional<HttpResponse> &response)> wrapperFn) {
390411
return wrapperFn(response);
391412
}
392413

test/integration_tests.cpp

+44-8
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,44 @@ TEST_CASE("Integration Tests")
2222

2323
CHECK(keys["get"] == "ok");
2424
}
25+
26+
SECTION("GET request with single query parameter")
27+
{
28+
SimpleHttp::HttpUrl &httpUrl = url
29+
.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get_hello"}}})
30+
.with_query_parameters(SimpleHttp::QueryParameters{
31+
{{SimpleHttp::QueryParameterKey{"name"}, SimpleHttp::QueryParameterValue{"test"}}}
32+
});
33+
auto maybe_response = client.get(httpUrl);
34+
35+
REQUIRE(maybe_response);
36+
37+
SimpleHttp::HttpResponse response = maybe_response.value();
38+
auto keys = nlohmann::json::parse(response.body.value());
39+
40+
CHECK(keys["hello"] == "test");
41+
}
42+
43+
SECTION("GET request with multiple query parameters")
44+
{
45+
SimpleHttp::HttpUrl &httpUrl = url
46+
.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get_full"}}})
47+
.with_query_parameters(SimpleHttp::QueryParameters{{
48+
{SimpleHttp::QueryParameterKey{"first"}, SimpleHttp::QueryParameterValue{"simple"}},
49+
{SimpleHttp::QueryParameterKey{"last"}, SimpleHttp::QueryParameterValue{"http"}}
50+
}});
51+
auto maybe_response = client.get(httpUrl);
52+
53+
REQUIRE(maybe_response);
54+
55+
SimpleHttp::HttpResponse response = maybe_response.value();
56+
auto keys = nlohmann::json::parse(response.body.value());
57+
58+
CHECK(keys["first"] == "simple");
59+
CHECK(keys["last"] == "http");
60+
}
2561

26-
SECTION("Post request")
62+
SECTION("POST request")
2763
{
2864
auto maybe_response = client.post(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"post"}}}),
2965
SimpleHttp::HttpRequestBody{R"({"name":"test"})"},
@@ -37,7 +73,7 @@ TEST_CASE("Integration Tests")
3773
CHECK(keys["hello"] == "test");
3874
}
3975

40-
SECTION("Put request")
76+
SECTION("PUT request")
4177
{
4278
auto maybe_response = client.put(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"put"}}}),
4379
SimpleHttp::HttpRequestBody{R"({"update":"test"})"},
@@ -51,23 +87,23 @@ TEST_CASE("Integration Tests")
5187
CHECK(keys["update"] == "test");
5288
}
5389

54-
SECTION("Delete request")
90+
SECTION("DELETE request")
5591
{
5692
auto maybe_response = client.del(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"delete"}}}));
5793

5894
REQUIRE(maybe_response);
5995
CHECK(maybe_response.value().status == SimpleHttp::OK);
6096
}
6197

62-
SECTION("Head request")
98+
SECTION("HEAD request")
6399
{
64100
auto maybe_response = client.head(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}));
65101

66102
REQUIRE(maybe_response);
67103
CHECK(maybe_response.value().status == SimpleHttp::OK);
68104
}
69105

70-
SECTION("Options request")
106+
SECTION("OPTIONS request")
71107
{
72108
auto maybe_response = client.options(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}));
73109

@@ -78,7 +114,7 @@ TEST_CASE("Integration Tests")
78114
Catch::UnorderedEquals(std::vector<std::string>{"HEAD", "OPTIONS", "GET"}));
79115
}
80116

81-
SECTION("Trace request")
117+
SECTION("TRACE request")
82118
{
83119
auto maybe_response = client.trace(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"trace"}}}));
84120

@@ -96,7 +132,7 @@ TEST_CASE("Integration Tests")
96132
CHECK(error == "Error: Unsupported protocol");
97133
}
98134

99-
SECTION("Post request that expects a 204 NO_CONTENT response")
135+
SECTION("POST request that expects a 204 NO_CONTENT response")
100136
{
101137
auto maybe_response = client.post(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"empty_post_response"}}}),
102138
SimpleHttp::HttpRequestBody{""},
@@ -111,7 +147,7 @@ TEST_CASE("Integration Tests")
111147
CHECK(response.body.value().empty());
112148
}
113149

114-
SECTION("Get request that expects a 405 METHOD_NOT_ALLOWED response")
150+
SECTION("GET request that expects a 405 METHOD_NOT_ALLOWED response")
115151
{
116152
auto maybe_response = client.get(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"empty_post_response"}}}),
117153
SimpleHttp::eq(SimpleHttp::METHOD_NOT_ALLOWED));

test/support/server/server.py

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
def get():
1010
return json.dumps({'get': 'ok'})
1111

12+
@app.route('/get_hello')
13+
def get_hello():
14+
return json.dumps({'hello': request.args.get('name')})
15+
16+
@app.route('/get_full')
17+
def get_full():
18+
return json.dumps({
19+
'first': request.args.get('first'),
20+
'last': request.args.get('last')
21+
})
22+
1223
@app.route('/post', methods = ['POST'])
1324
def post():
1425
data = request.get_json(silent=True)

test/unit_tests.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,29 @@ TEST_CASE("HttpUrl") {
180180
{
181181
CHECK(SimpleHttp::HttpUrl{"http://example.com"}.value() == "http://example.com");
182182
}
183+
}
184+
185+
TEST_CASE("QueryParameters")
186+
{
187+
SECTION("Empty") {
188+
SimpleHttp::QueryParameters parameters;
189+
CHECK(parameters.to_string().empty());
190+
}
191+
192+
SECTION("Single")
193+
{
194+
SimpleHttp::QueryParameters parameters{{
195+
{SimpleHttp::QueryParameterKey{"first"}, SimpleHttp::QueryParameterValue{"simple"}},
196+
}};
197+
CHECK(parameters.to_string() == "?first=simple");
198+
}
199+
200+
SECTION("Multiple")
201+
{
202+
SimpleHttp::QueryParameters parameters{{
203+
{SimpleHttp::QueryParameterKey{"first"}, SimpleHttp::QueryParameterValue{"simple"}},
204+
{SimpleHttp::QueryParameterKey{"last"}, SimpleHttp::QueryParameterValue{"http"}}
205+
}};
206+
CHECK(parameters.to_string() == "?first=simple&last=http");
207+
}
183208
}

0 commit comments

Comments
 (0)