RAVENNAKIT
Loading...
Searching...
No Matches
path_matcher.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2/*
3 * Project: RAVENNAKIT (RAVENNA / AES67 / ST2110-30 SDK)
4 * Copyright (c) 2024-2025 Sound on Digital
5 *
6 * This file is part of RAVENNAKIT.
7 *
8 * RAVENNAKIT is dual-licensed:
9 * 1) Under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version (the "AGPL License"); and
12 * 2) Under a commercial license from Sound on Digital, for customers who
13 * cannot (or do not wish to) comply with the AGPL License terms.
14 *
15 * If you obtained this file under the AGPL License, you may redistribute it
16 * and/or modify it under the terms of the AGPL License. See the LICENSE
17 * file in the project root for details.
18 *
19 * For commercial licensing, support, and other inquiries, please visit:
20 *
21 * https://ravennakit.com
22 *
23 */
24
25#pragma once
26
28
29#include <map>
30#include <string_view>
31#include <boost/system/result.hpp>
32#include <fmt/ostream.h>
33
34namespace rav {
46 public:
47 static constexpr auto k_loop_upper_bound = 100'000;
48
53
57 class Parameters {
58 public:
64 void set(const std::string_view name, const std::string_view value) {
65 parameters_[std::string(name)] = std::string(value);
66 }
67
73 [[nodiscard]] const std::string* get(const std::string_view name) const {
74 const auto it = parameters_.find(std::string(name));
75 if (it != parameters_.end()) {
76 return &it->second;
77 }
78 return nullptr;
79 }
80
88 template<typename T>
89 std::enable_if_t<std::is_integral_v<T>, std::optional<T>> get_as(const std::string_view name) const {
90 const auto it = parameters_.find(std::string(name));
91 if (it != parameters_.end()) {
92 return string_to_int<T>(it->second);
93 }
94 return std::nullopt;
95 }
96
101 [[nodiscard]] const std::map<std::string, std::string>& get_all() const {
102 return parameters_;
103 }
104
108 [[nodiscard]] bool empty() const {
109 return parameters_.empty();
110 }
111
115 void clear() {
116 parameters_.clear();
117 }
118
119 private:
120 std::map<std::string, std::string> parameters_; // name => value
121 };
122
133 [[nodiscard]] static boost::system::result<bool, Error>
134 match(const std::string_view path, const std::string_view pattern, Parameters* parameters = nullptr) {
135 if (path.empty() || pattern.empty()) {
136 return false;
137 }
138
139 StringParser path_parser(path);
140 StringParser pattern_parser(pattern);
141
142 path_parser.skip('/');
143 pattern_parser.skip('/');
144
145 auto path_section = path_parser.split('/');
146 auto pattern_section = pattern_parser.split('/');
147
148 for (size_t i = 0; i < k_loop_upper_bound; ++i) {
149 if (!path_section && !pattern_section) {
150 return true;
151 }
152
153 if (pattern_section == "**") {
154 if (!pattern_parser.exhausted()) {
156 }
157 return true;
158 }
159
160 if (!path_section || !pattern_section) {
161 return false;
162 }
163
164 if (path_section == pattern_section || pattern_section == "*") {
165 path_section = path_parser.split('/');
166 pattern_section = pattern_parser.split('/');
167 continue;
168 }
169
170 StringParser parameter_parser(*pattern_section);
171 const auto leading = parameter_parser.read_until('{');
172 const auto parameter_name = parameter_parser.read_until('}');
173 const auto trailing = parameter_parser.read_until_end();
174
175 if (!leading) {
176 return false; // No '{' was found
177 }
178
179 if (!parameter_name || parameter_name->empty()) {
180 return false;
181 }
182
183 auto parameter_value = *path_section;
184
185 if (leading) {
186 bool found = false;
187 parameter_value = string_remove_prefix(parameter_value, *leading, &found);
188 if (!found) {
189 return false;
190 }
191 }
192
193 if (trailing) {
194 bool found = false;
195 parameter_value = string_remove_suffix(parameter_value, *trailing, &found);
196 if (!found) {
197 return false;
198 }
199 }
200
201 if (parameters == nullptr) {
203 }
204
205 parameters->set(*parameter_name, parameter_value);
206
207 path_section = path_parser.split('/');
208 pattern_section = pattern_parser.split('/');
209 }
210
211 RAV_ASSERT_FALSE("PathMatcher::match() loop upper bound exceeded");
212 return false;
213 }
214};
215
216} // namespace rav
217
218// Make PathMatcher::Error printable with fmt
219template<>
220struct fmt::formatter<rav::PathMatcher::Error>: ostream_formatter {};
221
222namespace rav {
223
224// Make PathMatcher::Error compatible with ostream
225inline std::ostream& operator<<(std::ostream& os, const PathMatcher::Error err) {
226 switch (err) {
228 os << "invalid_recursive_wildcard";
229 break;
231 os << "invalid_argument";
232 break;
233 }
234 return os;
235}
236
237// Make PathMatcher::Error compatible with boost::system::result
238BOOST_NORETURN BOOST_NOINLINE inline void throw_exception_from_error(PathMatcher::Error const& e, boost::source_location const& loc) {
239 boost::throw_with_location(std::runtime_error(fmt::format("PathMatcher::Error: {}", e)), loc);
240}
241
242} // namespace rav
#define RAV_ASSERT_FALSE(message)
Asserts with false, entering the RAV_ASSERT procedure as a quick way to assert that a branch is inval...
Definition assert.hpp:189
Contains parameters extracted from a path with convenience functions to convert to integers.
void clear()
Clear all parameters.
const std::map< std::string, std::string > & get_all() const
Get all parameters as a map.
const std::string * get(const std::string_view name) const
Get a parameter value by name.
std::enable_if_t< std::is_integral_v< T >, std::optional< T > > get_as(const std::string_view name) const
Get a parameter value by name and convert it to an integer of type T.
void set(const std::string_view name, const std::string_view value)
Set a parameter with the given name and value.
A simple path matcher that matches paths against patterns.
static constexpr auto k_loop_upper_bound
static boost::system::result< bool, Error > match(const std::string_view path, const std::string_view pattern, Parameters *parameters=nullptr)
Match a path against a pattern.
A handy utility class for parsing strings.
bool exhausted() const
std::optional< std::string_view > read_until(const char delimiter, const bool include_delimiter=false)
Reads until given delimiter.
std::optional< std::string_view > read_until_end()
Reads the rest of the string.
std::optional< std::string_view > split(const char delimiter, const bool include_delimiter=false)
Reads until the given delimiter or until the end of the string.
bool skip(const char *chars)
Skips the given sequence of characters from the beginning of the string.
BOOST_NORETURN BOOST_NOINLINE void throw_exception_from_error(PathMatcher::Error const &e, boost::source_location const &loc)
std::enable_if_t< std::is_integral_v< Type >, std::optional< Type > > string_to_int(std::string_view string, const bool strict=false, const int base=10)
String to number - a small convenience function around std::from_chars.
Definition string.hpp:264
std::string_view string_remove_prefix(const std::string_view &string, const std::string_view prefix_to_remove, bool *found=nullptr)
Returns a copy of string with given prefix removed, if prefix is found at the beginning of the string...
Definition string.hpp:182
std::string_view string_remove_suffix(const std::string_view &string, const std::string_view suffix_to_remove, bool *found=nullptr)
Returns a copy of string with given suffix removed, if suffix is found at the end of the string.
Definition string.hpp:206
std::ostream & operator<<(std::ostream &os, const PathMatcher::Error err)