Skip to content

Commit

Permalink
Make PathSegments first class
Browse files Browse the repository at this point in the history
  • Loading branch information
abedra committed Jul 22, 2021
1 parent 2dc309c commit c6b9d44
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 26 deletions.
53 changes: 38 additions & 15 deletions simple_http.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ using ErrorCallback = std::function<void(const std::string&)>;
inline static ErrorCallback NoopErrorCallback = [](auto&){};

using Headers = std::unordered_map<std::string, std::string>;
using PathSegments = std::vector<PathSegment>;
using QueryParameters = std::vector<std::pair<QueryParameterKey, QueryParameterValue>>;

inline static const std::string WHITESPACE = "\n\t\f\v\r ";
Expand Down Expand Up @@ -154,6 +153,33 @@ struct HttpResponse final {
HttpResponseBody body;
};

struct PathSegments final {
PathSegments() = default;
PathSegments(PathSegments& path_segments) : value_(path_segments.value_) { }
PathSegments(PathSegments&& path_segments) : value_(path_segments.value_) { }
explicit PathSegments(std::vector<PathSegment> value) : value_(std::move(value)) { }
PathSegments& operator=(const PathSegments& other) {
this->value_ = other.value_;
return *this;
}

const std::vector<PathSegment>& value() const {
return value_;
}

std::string to_string() const {
std::stringstream ss;

for (const auto &segment : value_) {
ss << "/" << segment;
}

return ss.str();
}
private:
std::vector<PathSegment> value_;
};

struct HttpUrl final {
HttpUrl() = default;

Expand All @@ -174,16 +200,13 @@ struct HttpUrl final {
{ }

[[nodiscard]]
const Protcol & protocol() {
const Protcol & protocol() const {
return protocol_;
}

[[nodiscard]]
std::string value() {
if (value_.empty()) {
value_ = to_string();
}
return value_;
std::string value() const {
return value_.empty() ? to_string() : value_;
}

[[nodiscard]]
Expand All @@ -210,6 +233,11 @@ struct HttpUrl final {
return *this;
}

[[nodiscard]]
const PathSegments& path_segments() const {
return path_segments_;
}

private:
Protcol protocol_;
Host host_;
Expand All @@ -227,14 +255,9 @@ struct HttpUrl final {
}

[[nodiscard]]
std::string to_string() {
std::string to_string() const {
std::stringstream ss;
ss << protocol_ << "://" << host_;
if (!path_segments_.empty()) {
for (const auto& segment : path_segments_) {
ss << "/" << segment;
}
}
ss << protocol_ << "://" << host_ << path_segments_.to_string();

if (!query_parameters_.empty()) {
ss << "?" << query_parameters_[0].first << "=" << query_parameters_[0].second;
Expand Down Expand Up @@ -476,7 +499,7 @@ struct Client final {
}

[[nodiscard]]
std::optional<HttpResponse> execute(HttpUrl url,
std::optional<HttpResponse> execute(const HttpUrl& url,
const CurlHeaderCallback &curl_header_callback,
const CurlSetupCallback &curl_setup_callback,
const Predicate<HttpStatusCode> &successPredicate) {
Expand Down
22 changes: 11 additions & 11 deletions test/integration_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ TEST_CASE("Integration Tests")

SECTION("GET Request")
{
auto maybe_response = client.get(url.with_path_segments({SimpleHttp::PathSegment{"get"}}));
auto maybe_response = client.get(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}));

REQUIRE(maybe_response);

Expand All @@ -25,7 +25,7 @@ TEST_CASE("Integration Tests")

SECTION("Post request")
{
auto maybe_response = client.post(url.with_path_segments({SimpleHttp::PathSegment{"post"}}),
auto maybe_response = client.post(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"post"}}}),
SimpleHttp::HttpRequestBody{R"({"name":"test"})"},
{{"Content-Type", "application/json"}});

Expand All @@ -39,7 +39,7 @@ TEST_CASE("Integration Tests")

SECTION("Put request")
{
auto maybe_response = client.put(url.with_path_segments({SimpleHttp::PathSegment{"put"}}),
auto maybe_response = client.put(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"put"}}}),
SimpleHttp::HttpRequestBody{R"({"update":"test"})"},
{{"Content-Type", "application/json"}});

Expand All @@ -53,23 +53,23 @@ TEST_CASE("Integration Tests")

SECTION("Delete request")
{
auto maybe_response = client.del(url.with_path_segments({SimpleHttp::PathSegment{"delete"}}));
auto maybe_response = client.del(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"delete"}}}));

REQUIRE(maybe_response);
CHECK(maybe_response.value().status == SimpleHttp::OK);
}

SECTION("Head request")
{
auto maybe_response = client.head(url.with_path_segments({SimpleHttp::PathSegment{"get"}}));
auto maybe_response = client.head(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}));

REQUIRE(maybe_response);
CHECK(maybe_response.value().status == SimpleHttp::OK);
}

SECTION("Options request")
{
auto maybe_response = client.options(url.with_path_segments({SimpleHttp::PathSegment{"get"}}));
auto maybe_response = client.options(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}));

REQUIRE(maybe_response);

Expand All @@ -80,7 +80,7 @@ TEST_CASE("Integration Tests")

SECTION("Trace request")
{
auto maybe_response = client.trace(url.with_path_segments({SimpleHttp::PathSegment{"trace"}}));
auto maybe_response = client.trace(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"trace"}}}));

REQUIRE(maybe_response);
CHECK(maybe_response.value().headers.value().at("Content-Type") == "message/http");
Expand All @@ -98,7 +98,7 @@ TEST_CASE("Integration Tests")

SECTION("Post request that expects a 204 NO_CONTENT response")
{
auto maybe_response = client.post(url.with_path_segments({SimpleHttp::PathSegment{"empty_post_response"}}),
auto maybe_response = client.post(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"empty_post_response"}}}),
SimpleHttp::HttpRequestBody{""},
SimpleHttp::eq(SimpleHttp::NO_CONTENT),
{{"Content-Type", "application/json"}});
Expand All @@ -113,7 +113,7 @@ TEST_CASE("Integration Tests")

SECTION("Get request that expects a 405 METHOD_NOT_ALLOWED response")
{
auto maybe_response = client.get(url.with_path_segments({SimpleHttp::PathSegment{"empty_post_response"}}),
auto maybe_response = client.get(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"empty_post_response"}}}),
SimpleHttp::eq(SimpleHttp::METHOD_NOT_ALLOWED));

REQUIRE(maybe_response);
Expand All @@ -122,7 +122,7 @@ TEST_CASE("Integration Tests")

SECTION("Ranged response success predicate")
{
auto maybe_response = client.get(url.with_path_segments({SimpleHttp::PathSegment{"get"}}),
auto maybe_response = client.get(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}),
SimpleHttp::successful());

REQUIRE(maybe_response);
Expand All @@ -134,7 +134,7 @@ TEST_CASE("Integration Tests")

SECTION("Wrap Response")
{
auto maybe_response = client.get(url.with_path_segments({SimpleHttp::PathSegment{"get"}}),
auto maybe_response = client.get(url.with_path_segments(SimpleHttp::PathSegments{{SimpleHttp::PathSegment{"get"}}}),
SimpleHttp::successful());

REQUIRE(maybe_response);
Expand Down
66 changes: 66 additions & 0 deletions test/unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,69 @@ TEST_CASE("Trimming") {
CHECK(SimpleHttp::trim(candidate) == "the \n thing \r works \v in \r\n ways");
}
}

TEST_CASE("PathSegments") {
SECTION("Path segments to_string no segments")
{
CHECK(SimpleHttp::PathSegments{}.to_string().empty());
}

SECTION("Path segments to_string one segment")
{
SimpleHttp::PathSegments segments{{SimpleHttp::PathSegment{"one"}}};
CHECK(segments.to_string() == "/one");
}

SECTION("Path segments to_string multiple segment")
{
SimpleHttp::PathSegments segments{{
SimpleHttp::PathSegment{"one"},
SimpleHttp::PathSegment{"two"}
}};

CHECK(segments.to_string() == "/one/two");
}
}

TEST_CASE("HttpUrl") {
SECTION("path_segments parsed constructor, no segments")
{
SimpleHttp::HttpUrl url{"https://example.com"};
CHECK(url.path_segments().value().empty());
CHECK(url.path_segments().to_string().empty());
}

SECTION("path_segments parsed constructor, one segment")
{
SimpleHttp::HttpUrl url{"https://example.com/one"};
// This exposes the lack of complete parsing when constructed with a string value
CHECK(url.path_segments().value().empty());
CHECK(url.path_segments().to_string().empty());
}

SECTION("path_segments parsed constructor, one segment")
{
SimpleHttp::HttpUrl url{"https://example.com/one/two"};
// This exposes the lack of complete parsing when constructed with a string value
CHECK(url.path_segments().value().empty());
CHECK(url.path_segments().to_string().empty());
}

SECTION("Fully constructed, multiple segments")
{
SimpleHttp::HttpUrl url = SimpleHttp::HttpUrl()
.with_protocol(SimpleHttp::Protcol{"https"})
.with_host(SimpleHttp::Host{"example.com"})
.with_path_segments(SimpleHttp::PathSegments{{
SimpleHttp::PathSegment{"one"},
SimpleHttp::PathSegment{"two"}
}});

CHECK(url.path_segments().to_string() == "/one/two");
}

SECTION("value, when constructed with a string")
{
CHECK(SimpleHttp::HttpUrl{"http://example.com"}.value() == "http://example.com");
}
}

0 comments on commit c6b9d44

Please sign in to comment.