forked from glynos/url
-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathurl_search_parameters.hpp
259 lines (217 loc) · 7.5 KB
/
url_search_parameters.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2017-20 Glyn Matthews.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef SKYR_V2_URL_SEARCH_PARAMETERS_HPP
#define SKYR_V2_URL_SEARCH_PARAMETERS_HPP
#include <string>
#include <string_view>
#include <vector>
#include <functional>
#include <optional>
#include <algorithm>
#include <cassert>
#include <fmt/format.h>
#include <skyr/v2/core/parse_query.hpp>
#include <skyr/v2/percent_encoding/percent_encode.hpp>
#include <skyr/v2/percent_encoding/percent_decode.hpp>
namespace skyr::inline v2 {
class url;
namespace details {
struct is_name {
explicit is_name(std::string_view name) : name_(name) {
}
auto operator()(const query_parameter ¶meter) noexcept {
return name_ == parameter.name;
}
std::string_view name_;
};
} // namespace details
/// Supports iterating through
/// [URL search parameters](https://url.spec.whatwg.org/#urlsearchparams)
///
/// The API closely follows the
/// [WhatWG IDL specification](https://url.spec.whatwg.org/#interface-urlsearchparams)
class url_search_parameters {
friend class url;
public:
/// string type
/// \sa url::string_type
using string_type = std::string;
/// A key-value pair
using value_type = query_parameter;
/// An iterator through the search parameters
using const_iterator = std::vector<value_type>::const_iterator;
/// An alias to \c const_iterator
using iterator = const_iterator;
/// \c std::size_t
using size_type = std::size_t;
/// Default constructor
url_search_parameters() = default;
/// Constructor
/// \param query The search string
explicit url_search_parameters(std::string_view query) {
initialize(query);
}
/// Constructor
/// \param parameters
explicit url_search_parameters(std::vector<query_parameter> parameters) : parameters_(std::move(parameters)) {}
/// Constructor
/// \param parameters
url_search_parameters(std::initializer_list<value_type> parameters) : parameters_(parameters) {}
///
/// \param other
void swap(url_search_parameters &other) noexcept {
std::swap(parameters_, other.parameters_);
}
/// Appends a name-value pair to the search string
///
/// \param name The parameter name
/// \param value The parameter value
void append(std::string_view name, std::string_view value) {
parameters_.emplace_back(std::string(name), std::string(value));
update();
}
/// Removes a parameter from the search string
///
/// \param name The name of the parameter to remove
void remove(std::string_view name) {
auto first = std::begin(parameters_), last = std::end(parameters_);
auto it = std::remove_if(first, last, details::is_name(name));
parameters_.erase(it, last);
update();
}
/// \param name The search parameter name
/// \returns The first search parameter value with the given name
[[nodiscard]] auto get(std::string_view name) const -> std::optional<string_type> {
auto first = std::cbegin(parameters_), last = std::cend(parameters_);
auto it = std::find_if(first, last, details::is_name(name));
return (it != last) ? it->value : std::nullopt;
}
/// \param name The search parameter name
/// \returns All search parameter values with the given name
[[nodiscard]] auto get_all(std::string_view name) const -> std::vector<string_type> {
std::vector<string_type> result;
result.reserve(parameters_.size());
for (auto [parameter_name, value] : parameters_) {
if (parameter_name == name) {
result.emplace_back(value.value_or(""));
}
}
return result;
}
/// Tests if there is a parameter with the given name
///
/// \param name The search parameter name
/// \returns `true` if the value is in the search parameters,
/// `false` otherwise.
[[nodiscard]] auto contains(std::string_view name) const noexcept -> bool {
auto first = std::cbegin(parameters_), last = std::cend(parameters_);
return std::find_if(first, last, details::is_name(name)) != last;
}
/// Sets a URL search parameter
///
/// \param name The search parameter name
/// \param value The search parameter value
void set(std::string_view name, std::string_view value) {
auto first = std::begin(parameters_), last = std::end(parameters_);
auto it = std::find_if(first, last, details::is_name(name));
if (it != last) {
it->value = value;
++it;
it = std::remove_if(it, last, details::is_name(name));
ranges::erase(parameters_, it, last);
} else {
append(name, value);
}
update();
}
/// Clears the search parameters
///
/// \post `empty() == true`
void clear() noexcept {
parameters_.clear();
update();
}
/// Sorts the search parameters alphanumerically
///
/// https://url.spec.whatwg.org/#example-searchparams-sort
///
/// ```
/// auto url = skyr::url(https://github.com/%3C/div%3E%3C/div%3E%3C/div%3E%3Cdiv%20class=%22child-of-line-41%20%20react-code-text%20react-code-line-contents%22%20style=%22min-height:auto%22%3E%3Cdiv%3E%3Cdiv%20id=%22LC168%22%20class=%22react-file-line%20html-div%22%20data-testid=%22code-cell%22%20data-line-number=%22168%22%20style=%22position:relative%22%3E%20%20///%20%20%20"https://example.org/?q=\xf0\x9f\x8f\xb3\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x8c\x88&key=e1f7bc78");
/// url.search_parameters().sort();
/// assert(url.search() == "?key=e1f7bc78&q=%F0%9F%8F%B3%EF%B8%8F%E2%80%8D%F0%9F%8C%88");
/// ```
void sort() {
static constexpr auto less_name = [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; };
auto first = std::begin(parameters_), last = std::end(parameters_);
std::sort(first, last, less_name);
update();
}
/// \returns An iterator to the first element in the search parameters
[[nodiscard]] auto cbegin() const noexcept {
return parameters_.cbegin();
}
/// \returns An iterator to the last element in the search parameters
[[nodiscard]] auto cend() const noexcept {
return parameters_.cend();
}
/// \returns An iterator to the first element in the search parameters
[[nodiscard]] auto begin() const noexcept {
return cbegin();
}
/// \returns An iterator to the last element in the search parameters
[[nodiscard]] auto end() const noexcept {
return cend();
}
/// \returns `true` if the URL search string is empty, `false`
/// otherwise
[[nodiscard]] auto empty() const noexcept {
return parameters_.empty();
}
/// \returns The size of the parameters array (i.e. the
/// number of parameters)
[[nodiscard]] auto size() const noexcept {
return parameters_.size();
}
/// \returns The serialized URL search parameters
[[nodiscard]] auto to_string() const -> string_type {
auto result = string_type{};
bool start = true;
for (const auto &[name, value] : parameters_) {
if (start) {
result.append(percent_encode(name));
start = false;
} else {
result.append(fmt::format("&{}", percent_encode(name)));
}
if (value) {
result.append(fmt::format("={}", percent_encode(value.value())));
}
}
return result;
}
private:
explicit url_search_parameters(url *url);
void initialize(std::string_view query) {
if (auto parameters = parse_query(query); parameters) {
for (auto [name, value] : parameters.value()) {
auto name_ = percent_decode(name).value_or(std::string(name));
auto value_ = value ? percent_decode(value.value()).value_or(std::string(value.value())) : std::string();
parameters_.emplace_back(name_, value_);
}
}
}
void update();
std::vector<value_type> parameters_;
url *url_ = nullptr;
};
///
/// \param lhs
/// \param rhs
inline void swap(url_search_parameters &lhs, url_search_parameters &rhs) noexcept {
lhs.swap(rhs);
}
} // namespace skyr::inline v2
#endif // SKYR_V2_URL_SEARCH_PARAMETERS_HPP