Skip to content

Commit be95b53

Browse files
authored
Expose path and query parser (#150)
* Exposed functions to parse paths and queries * Extended query refactoring to JSON transformation
1 parent d49ec10 commit be95b53

File tree

11 files changed

+294
-128
lines changed

11 files changed

+294
-128
lines changed

include/skyr/v2/core/parse_path.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2020 Glyn Matthews.
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE_1_0.txt or copy at
4+
// http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#ifndef SKYR_V2_CORE_PARSE_QUERY_HPP
7+
#define SKYR_V2_CORE_PARSE_QUERY_HPP
8+
9+
#include <string>
10+
#include <vector>
11+
#include <skyr/v2/core/parse.hpp>
12+
13+
namespace skyr::inline v2 {
14+
///
15+
/// \param path
16+
/// \param validation_error
17+
/// \return
18+
inline auto parse_path(
19+
std::string_view path, bool *validation_error) -> tl::expected<std::vector<std::string>, url_parse_errc> {
20+
auto url = details::basic_parse(path, validation_error, nullptr, nullptr, url_parse_state::path_start);
21+
if (url) {
22+
return url.value().path;
23+
}
24+
return tl::make_unexpected(url.error());
25+
}
26+
27+
///
28+
/// \param path
29+
/// \return
30+
inline auto parse_path(
31+
std::string_view path) -> tl::expected<std::vector<std::string>, url_parse_errc> {
32+
[[maybe_unused]] bool validation_error = false;
33+
return parse_path(path, &validation_error);
34+
}
35+
} // namespace skyr::inline v2
36+
37+
#endif // SKYR_V2_CORE_PARSE_QUERY_HPP

include/skyr/v2/core/parse_query.hpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020 Glyn Matthews.
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// (See accompanying file LICENSE_1_0.txt or copy at
4+
// http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#ifndef SKYR_V2_CORE_PARSE_QUERY_HPP
7+
#define SKYR_V2_CORE_PARSE_QUERY_HPP
8+
9+
#include <string>
10+
#include <vector>
11+
#include <range/v3/view/split_when.hpp>
12+
#include <range/v3/view/transform.hpp>
13+
#include <skyr/v2/core/parse.hpp>
14+
#include <skyr/v2/percent_encoding/percent_decode.hpp>
15+
16+
namespace skyr::inline v2 {
17+
///
18+
struct query_parameter {
19+
std::string name;
20+
std::optional<std::string> value;
21+
22+
/// Constructor
23+
query_parameter() = default;
24+
25+
/// Constructor
26+
/// \param name The parameter name
27+
query_parameter(std::string name) : name(std::move(name)) {}
28+
29+
/// Constructor
30+
/// \param name The parameter name
31+
/// \param value The parameter value
32+
query_parameter(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {}
33+
};
34+
35+
///
36+
/// \param query
37+
/// \param validation_error
38+
/// \return
39+
inline auto parse_query(
40+
std::string_view query, bool *validation_error) -> tl::expected<std::vector<query_parameter>, url_parse_errc> {
41+
if (!query.empty() && (query.front() == '?')) {
42+
query.remove_prefix(1);
43+
}
44+
45+
auto url = details::basic_parse(query, validation_error, nullptr, nullptr, url_parse_state::query);
46+
if (url) {
47+
static constexpr auto is_separator = [](auto c) { return c == '&' || c == ';'; };
48+
49+
static constexpr auto to_nvp = [](auto &&param) -> query_parameter {
50+
if (ranges::empty(param)) {
51+
return {};
52+
}
53+
54+
auto element = std::string_view(std::addressof(*std::begin(param)), ranges::distance(param));
55+
auto delim = element.find_first_of('=');
56+
if (delim != std::string_view::npos) {
57+
return {std::string(element.substr(0, delim)), std::string(element.substr(delim + 1))};
58+
} else {
59+
return {std::string(element)};
60+
}
61+
};
62+
63+
std::vector<query_parameter> parameters{};
64+
if (url.value().query) {
65+
for (auto &&parameter :
66+
url.value().query.value() | ranges::views::split_when(is_separator) | ranges::views::transform(to_nvp)) {
67+
parameters.emplace_back(parameter);
68+
}
69+
}
70+
return parameters;
71+
}
72+
return tl::make_unexpected(url.error());
73+
}
74+
75+
///
76+
/// \param query
77+
/// \return
78+
inline auto parse_query(
79+
std::string_view query) -> tl::expected<std::vector<query_parameter>, url_parse_errc> {
80+
[[maybe_unused]] bool validation_error = false;
81+
return parse_query(query, &validation_error);
82+
}
83+
} // namespace skyr::inline v2
84+
85+
#endif // SKYR_V2_CORE_PARSE_QUERY_HPP

include/skyr/v2/core/serialize.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ inline auto serialize_file_scheme(const url_record &url) -> std::string {
2828
return (!url.host && (url.scheme == "file")) ? "//" : "";
2929
}
3030

31-
inline auto serialize_host(const url_record &url) -> std::string {
31+
inline auto serialize_authority(const url_record &url) -> std::string {
3232
return url.host
3333
? fmt::format("//{}{}{}", serialize_credentials(url), url.host.value().serialize(), serialize_port(url))
3434
: serialize_file_scheme(url);
3535
}
3636

37+
inline auto serialize_path(const std::vector<std::string> &path) -> std::string {
38+
return fmt::format("/{}", path | ranges::views::join('/') | ranges::to<std::string>());
39+
}
40+
3741
inline auto serialize_path(const url_record &url) -> std::string {
38-
return url.cannot_be_a_base_url ? url.path.front()
39-
: fmt::format("/{}", url.path | ranges::views::join('/') | ranges::to<std::string>());
42+
return url.cannot_be_a_base_url ? url.path.front() : serialize_path(url.path);
4043
}
4144

4245
inline auto serialize_query(const url_record &url) -> std::string {
@@ -54,7 +57,7 @@ inline auto serialize_fragment(const url_record &url) -> std::string {
5457
/// \param url A URL record
5558
/// \returns A serialized URL string, excluding the fragment
5659
inline auto serialize_excluding_fragment(const url_record &url) -> url_record::string_type {
57-
return fmt::format("{}:{}{}{}", url.scheme, details::serialize_host(url), details::serialize_path(url),
60+
return fmt::format("{}:{}{}{}", url.scheme, details::serialize_authority(url), details::serialize_path(url),
5861
details::serialize_query(url));
5962
}
6063

include/skyr/v2/core/url_parser_context.hpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -236,19 +236,10 @@ class url_parser_context {
236236
buffer.push_back(lower);
237237
} else if (byte == ':') {
238238
if (state_override) {
239-
if (url.is_special() && !is_special(buffer)) {
240-
return tl::make_unexpected(url_parse_errc::cannot_override_scheme);
241-
}
242-
243-
if (!url.is_special() && is_special(buffer)) {
244-
return tl::make_unexpected(url_parse_errc::cannot_override_scheme);
245-
}
246-
247-
if ((url.includes_credentials() || url.port) && (buffer == "file")) {
248-
return tl::make_unexpected(url_parse_errc::cannot_override_scheme);
249-
}
250-
251-
if ((url.scheme == "file") && (!url.host || url.host.value().is_empty())) {
239+
if ((url.is_special() && !is_special(buffer)) ||
240+
(!url.is_special() && is_special(buffer)) ||
241+
((url.includes_credentials() || url.port) && (buffer == "file")) ||
242+
((url.scheme == "file") && (!url.host || url.host.value().is_empty()))) {
252243
return tl::make_unexpected(url_parse_errc::cannot_override_scheme);
253244
}
254245
}
@@ -895,11 +886,17 @@ class url_parser_context {
895886
}
896887

897888
void pct_encode_and_append_to_query(char byte) {
889+
if (!url.query) {
890+
set_empty_query();
891+
}
898892
auto pct_encoded = percent_encode_byte(std::byte(byte), percent_encoding::encode_set::none);
899893
url.query.value() += std::move(pct_encoded).to_string();
900894
}
901895

902896
void append_to_query(char byte) {
897+
if (!url.query) {
898+
set_empty_query();
899+
}
903900
url.query.value().push_back(byte);
904901
}
905902

@@ -908,6 +905,9 @@ class url_parser_context {
908905
}
909906

910907
void append_to_fragment(char byte) {
908+
if (!url.fragment) {
909+
set_empty_fragment();
910+
}
911911
auto pct_encoded = percent_encode_byte(std::byte(byte), percent_encoding::encode_set::fragment);
912912
url.fragment.value() += pct_encoded.to_string();
913913
}

include/skyr/v2/json/json.hpp

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
#include <optional>
1111
#include <vector>
1212
#include <tl/expected.hpp>
13-
#include <range/v3/view/split_when.hpp>
14-
#include <range/v3/view/transform.hpp>
1513
#include <nlohmann/json.hpp>
14+
#include <fmt/format.h>
15+
#include <skyr/v2/core/parse_query.hpp>
1616
#include <skyr/v2/percent_encoding/percent_encode.hpp>
1717
#include <skyr/v2/percent_encoding/percent_decode.hpp>
1818

@@ -28,78 +28,52 @@ inline auto encode_query(const nlohmann::json &json, char separator='&', char eq
2828
-> tl::expected<std::string, json_errc> {
2929
using namespace std::string_literals;
3030

31-
auto result = ""s;
31+
std::string result{};
3232

3333
if (!json.is_object()) {
3434
return tl::make_unexpected(json_errc::invalid_query);
3535
}
3636

3737
for (auto &[key, value] : json.items()) {
38-
3938
if (value.is_string()) {
40-
result += percent_encode(key);
41-
result += equal;
42-
result += percent_encode(value.get<std::string>());
43-
result += separator;
44-
}
45-
else if (value.is_array()) {
39+
result += fmt::format(
40+
"{}{}{}{}", percent_encode(key), equal, percent_encode(value.get<std::string>()), separator);
41+
} else if (value.is_array()) {
4642
for (auto &element : value.items()) {
47-
result += percent_encode(key);
48-
result += equal;
49-
result += percent_encode(element.value().get<std::string>());
50-
result += separator;
43+
result += fmt::format(
44+
"{}{}{}{}", percent_encode(key), equal, percent_encode(element.value().get<std::string>()), separator);
5145
}
52-
}
53-
else {
54-
result += percent_encode(key);
55-
result += equal;
56-
result += separator;
46+
} else {
47+
result += fmt::format("{}{}{}", percent_encode(key), equal, separator);
5748
}
5849
}
5950

6051
return result.substr(0, result.size() - 1);
6152
}
6253

63-
inline auto decode_query(std::string_view query, char separator='&', char equal='=') -> nlohmann::json {
64-
if (query[0] == '?') {
65-
query.remove_prefix(1);
66-
}
67-
68-
static constexpr auto is_separator = [] (auto &&c) {
69-
return c == '&' || c == ';';
70-
};
71-
72-
static constexpr auto to_nvp = [] (auto &&param) -> std::pair<std::string_view, std::optional<std::string_view>> {
73-
auto element = std::string_view(std::addressof(*std::begin(param)), ranges::distance(param));
74-
auto delim = element.find_first_of("=");
75-
if (delim != std::string_view::npos) {
76-
return { element.substr(0, delim), element.substr(delim + 1) };
77-
}
78-
else {
79-
return { element, std::nullopt };
80-
}
81-
};
82-
54+
inline auto decode_query(std::string_view query) -> nlohmann::json {
8355
nlohmann::json object;
84-
for (auto [name, value] : query | ranges::views::split_when(is_separator) | ranges::views::transform(to_nvp)) {
85-
const auto name_ = skyr::percent_decode(name).value();
86-
const auto value_ = value? skyr::percent_decode(value.value()).value() : std::string();
8756

88-
if (object.contains(name_)) {
89-
auto current_value = object[name_];
90-
if (current_value.is_string()) {
91-
auto prev_value = current_value.get<std::string>();
92-
object[name_] = std::vector<std::string>{prev_value, value_};
93-
}
94-
else if (current_value.is_array()) {
95-
auto values = current_value.get<std::vector<std::string>>();
96-
values.emplace_back(value_);
97-
object[name_] = values;
57+
auto parameters = parse_query(query);
58+
if (parameters) {
59+
for (auto &&[name, value] : parameters.value()) {
60+
const auto name_ = skyr::percent_decode(name).value();
61+
const auto value_ = value ? skyr::percent_decode(value.value()).value() : std::string();
62+
63+
if (object.contains(name_)) {
64+
auto current_value = object[name_];
65+
if (current_value.is_string()) {
66+
auto prev_value = current_value.get<std::string>();
67+
object[name_] = std::vector<std::string>{prev_value, value_};
68+
} else if (current_value.is_array()) {
69+
auto values = current_value.get<std::vector<std::string>>();
70+
values.emplace_back(value_);
71+
object[name_] = values;
72+
}
73+
} else {
74+
object[name_] = value_;
9875
}
9976
}
100-
else {
101-
object[name_] = value_;
102-
}
10377
}
10478
return object;
10579
}

include/skyr/v2/platform/endianness.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ requires std::is_integral_v<intT> constexpr inline auto swap_endianness(intT v)
2525
} // namespace details
2626

2727
template <class intT>
28-
requires std::is_integral_v<intT> constexpr inline auto to_network_byte_order(intT v) noexcept {
28+
requires std::is_integral_v<intT> constexpr inline auto to_network_byte_order(intT v) noexcept -> intT {
2929
return (std::endian::big == std::endian::native) ? v : details::swap_endianness(v); // NOLINT
3030
}
3131

3232
template <class intT>
33-
requires std::is_integral_v<intT> constexpr inline auto from_network_byte_order(intT v) noexcept {
33+
requires std::is_integral_v<intT> constexpr inline auto from_network_byte_order(intT v) noexcept -> intT {
3434
return (std::endian::big == std::endian::native) ? v : details::swap_endianness(v); // NOLINT
3535
}
3636
} // namespace skyr::inline v2

0 commit comments

Comments
 (0)