VLink  2.0.0
A high-performance communication middleware
url_parser.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file url_parser.h
26  * @brief Strict subset of an RFC 3986 URL parser used by the VLink transport layer.
27  *
28  * @details
29  * This is an internal implementation header used by @c Protocol and the
30  * URL-driven transport routing path; application code does not interact with
31  * it directly. @c UrlParser decomposes a VLink topic URL into the parts that
32  * @c Url needs to select a transport @c Conf and to configure the resulting
33  * @c NodeImpl. Typical inputs look like:
34  *
35  * @code
36  * dds://my_domain/vehicle/speed?domain=1&qos=best_effort
37  * intra://my_topic
38  * someip://127.0.0.1:30490/my_service?instance_id=1
39  * @endcode
40  *
41  * @par URL grammar
42  * | Component | Example | Description |
43  * | ---------- | ------------------------ | ------------------------------------------------- |
44  * | transport | @c dds | URI scheme / VLink transport prefix before @c :// |
45  * | content | @c //host/path | Full content portion after the scheme separator |
46  * | username | @c user | Optional credential before host |
47  * | password | @c pass | Optional credential after @c : |
48  * | host | @c 127.0.0.1 | Hostname or IP address |
49  * | port | @c 30490 | TCP / UDP port number |
50  * | path | @c vehicle/speed | Topic path; leading slash stored separately |
51  * | query | @c domain=1&qos=... | Raw query string after @c ? |
52  * | fragment | @c section1 | Fragment identifier after @c # |
53  *
54  * @par Parser outputs
55  * | Accessor | Resulting type |
56  * | ------------------------------- | ------------------------------------------- |
57  * | @c get_transport() const | @c const std::string& |
58  * | @c get_category() const | @c Category |
59  * | @c get_content() const | @c const std::string& (non-hierarchical) |
60  * | @c get_username() const | @c const std::string& (hierarchical) |
61  * | @c get_password() const | @c const std::string& (hierarchical) |
62  * | @c get_host() const | @c const std::string& (hierarchical) |
63  * | @c get_port() const | @c int64_t (hierarchical) |
64  * | @c get_path() const | @c const std::string& (hierarchical) |
65  * | @c get_query() const | @c const std::string& |
66  * | @c get_query_dictionary() const | @c const std::map<std::string,std::string>& |
67  * | @c get_fragment() const | @c const std::string& |
68  * | @c to_string() const | @c std::string (reconstructed URL) |
69  *
70  * @par Query dictionary
71  * The raw query string is split into a @c std::map<std::string,std::string>
72  * using either @c & (default) or @c ; as the key/value pair separator. Each
73  * token is split on the first @c = character; keys without a value become
74  * empty strings.
75  *
76  * @par Category
77  * - @c kHierarchical: standard @c scheme://authority/path?query#fragment URL.
78  * - @c kNonHierarchical: opaque @c scheme:content form (e.g. @c mailto:user(at)host).
79  *
80  * @par Example
81  * @code
82  * vlink::UrlParser parser("dds://10.0.0.1:7400/cars/1?qos=best_effort#stats");
83  *
84  * parser.get_transport(); // "dds"
85  * parser.get_host(); // "10.0.0.1"
86  * parser.get_port(); // 7400
87  * parser.get_path(); // "cars/1"
88  * parser.get_query_dictionary(); // {"qos" -> "best_effort"}
89  * parser.get_fragment(); // "stats"
90  * @endcode
91  *
92  * @note @c UrlParser is a value type; parsing happens once during construction
93  * and accessors return references valid for the lifetime of the object.
94  */
95 
96 #pragma once
97 
98 #include <cstdint>
99 #include <map>
100 #include <string>
101 
102 #include "../base/macros.h"
103 
104 namespace vlink {
105 
106 /**
107  * @class UrlParser
108  * @brief Immutable URL decomposition used by the VLink transport router.
109  *
110  * @details
111  * Parses the input URL once at construction and exposes each component through
112  * @c const accessors. For hierarchical URLs the stored path drops the leading
113  * @c / marker; the rootedness flag is preserved separately so @c to_string()
114  * can reconstruct an equivalent representation.
115  */
116 class VLINK_EXPORT UrlParser final {
117  public:
118  /**
119  * @enum Category
120  * @brief Distinguishes hierarchical and non-hierarchical URI forms.
121  */
122  enum class Category : uint8_t {
123  kHierarchical = 0, ///< Standard @c scheme://authority/path layout (most VLink transports).
124  kNonHierarchical = 1, ///< Opaque @c scheme:content layout (e.g. @c mailto:).
125  };
126 
127  /**
128  * @enum Component
129  * @brief Component identifiers accepted by the explicit-components constructor.
130  */
131  enum class Component : uint8_t {
132  kTransport = 0, ///< URI scheme / VLink transport prefix.
133  kContent = 1, ///< Full content string after the scheme separator.
134  kUsername = 2, ///< Optional authentication username.
135  kPassword = 3, ///< Optional authentication password.
136  kHost = 4, ///< Hostname or IP address.
137  kPort = 5, ///< Port number (stored as string in the components map).
138  kPath = 6, ///< Resource path (e.g. @c /vehicle/speed).
139  kQuery = 7, ///< Raw query string (without the leading @c ?).
140  kFragment = 8, ///< Fragment identifier (without the leading @c #).
141  };
142 
143  /**
144  * @enum Separator
145  * @brief Selectable separator used to break the query string into key/value pairs.
146  */
147  enum class Separator : uint8_t {
148  kAmpersand = 0, ///< @c & separator (default; @c key=val&key2=val2).
149  kSemicolon = 1, ///< @c ; separator (alternative; @c key=val;key2=val2).
150  };
151 
152  /**
153  * @brief Parses @p str as a null-terminated URL string.
154  *
155  * @param str URL string to parse.
156  * @param category Hierarchical or non-hierarchical layout; default hierarchical.
157  * @param separator Query-pair separator; default @c &.
158  */
159  explicit UrlParser(const char* str, Category category = Category::kHierarchical,
160  Separator separator = Separator::kAmpersand);
161 
162  /**
163  * @brief Parses @p str as an @c std::string URL.
164  *
165  * @param str URL string to parse.
166  * @param category Hierarchical or non-hierarchical layout; default hierarchical.
167  * @param separator Query-pair separator; default @c &.
168  */
169  explicit UrlParser(const std::string& str, Category category = Category::kHierarchical,
170  Separator separator = Separator::kAmpersand);
171 
172  /**
173  * @brief Builds a parser directly from an explicit component map.
174  *
175  * @details
176  * Useful for constructing a URL from a previously decomposed set of fields
177  * instead of parsing a raw string.
178  *
179  * @param components Map of @c Component to value for every present component.
180  * @param category Hierarchical or non-hierarchical layout.
181  * @param rooted @c true when the path starts with @c / (hierarchical URLs).
182  * @param separator Query-pair separator; default @c &.
183  *
184  * @throws Exception::RuntimeError when @p category is @c Category::kHierarchical
185  * and @c Component::kPath is missing, or when @p category is
186  * @c Category::kNonHierarchical and @c Component::kContent is missing.
187  */
188  explicit UrlParser(const std::map<Component, std::string>& components, Category category, bool rooted,
189  Separator separator = Separator::kAmpersand);
190 
191  /**
192  * @brief Builds a parser by copying @p other and overriding selected components.
193  *
194  * @details
195  * Equivalent to producing a modified copy of an existing URL.
196  *
197  * @param other Source parser.
198  * @param replacements Components that override the corresponding entries in @p other.
199  */
200  explicit UrlParser(const UrlParser& other, const std::map<Component, std::string>& replacements);
201 
202  /**
203  * @brief Returns the parsed transport string (e.g. @c "dds" or @c "intra").
204  *
205  * @return Reference to the transport component; empty when absent.
206  */
207  [[nodiscard]] const std::string& get_transport() const;
208 
209  /**
210  * @brief Returns the URL category supplied at construction time.
211  *
212  * @return Either @c Category::kHierarchical or @c Category::kNonHierarchical.
213  */
214  [[nodiscard]] Category get_category() const;
215 
216  /**
217  * @brief Returns the full content portion of a non-hierarchical URL.
218  *
219  * @details
220  * Calling this method on a hierarchical URL throws @c Exception::RuntimeError.
221  *
222  * @return Reference to the content string.
223  */
224  [[nodiscard]] const std::string& get_content() const;
225 
226  /**
227  * @brief Returns the authentication username component.
228  *
229  * @details
230  * Valid only for hierarchical URLs; otherwise throws @c Exception::RuntimeError.
231  *
232  * @return Reference to the username; empty when absent.
233  */
234  [[nodiscard]] const std::string& get_username() const;
235 
236  /**
237  * @brief Returns the authentication password component.
238  *
239  * @details
240  * Valid only for hierarchical URLs; otherwise throws @c Exception::RuntimeError.
241  *
242  * @return Reference to the password; empty when absent.
243  */
244  [[nodiscard]] const std::string& get_password() const;
245 
246  /**
247  * @brief Returns the host component (hostname or IP address).
248  *
249  * @details
250  * Valid only for hierarchical URLs; otherwise throws @c Exception::RuntimeError.
251  *
252  * @return Reference to the host string; empty when absent.
253  */
254  [[nodiscard]] const std::string& get_host() const;
255 
256  /**
257  * @brief Returns the port number, or @c 0 when no port was specified.
258  *
259  * @details
260  * Valid only for hierarchical URLs; otherwise throws @c Exception::RuntimeError.
261  *
262  * @return Parsed port as @c int64_t; @c 0 when absent.
263  */
264  [[nodiscard]] int64_t get_port() const;
265 
266  /**
267  * @brief Returns the path component of the URL.
268  *
269  * @details
270  * Valid only for hierarchical URLs; otherwise throws @c Exception::RuntimeError.
271  * The returned string omits the leading @c / for rooted hierarchical URLs;
272  * the rootedness flag is retained internally and emitted again by
273  * @c to_string().
274  *
275  * @return Reference to the path; empty when absent.
276  */
277  [[nodiscard]] const std::string& get_path() const;
278 
279  /**
280  * @brief Returns the raw query string (without the leading @c ?).
281  *
282  * @return Reference to the raw query; empty when no query was present.
283  */
284  [[nodiscard]] const std::string& get_query() const;
285 
286  /**
287  * @brief Returns the parsed query string as a key/value dictionary.
288  *
289  * @details
290  * Built by splitting the raw query on the configured @c Separator and then
291  * splitting each token on the first @c = character. Keys without a value
292  * appear with an empty string.
293  *
294  * @return Reference to the query dictionary.
295  */
296  [[nodiscard]] const std::map<std::string, std::string>& get_query_dictionary() const;
297 
298  /**
299  * @brief Returns the fragment identifier (without the leading @c #).
300  *
301  * @return Reference to the fragment; empty when absent.
302  */
303  [[nodiscard]] const std::string& get_fragment() const;
304 
305  /**
306  * @brief Reconstructs the URL string from the parsed components.
307  *
308  * @details
309  * Hierarchical URLs are reassembled as @c scheme://authority/path?query#fragment
310  * when an authority is present; non-hierarchical URLs as
311  * @c scheme:content?query#fragment. The output may differ from the original
312  * input when equivalent components were originally written differently.
313  *
314  * @return Reconstructed URL string.
315  */
316  [[nodiscard]] std::string to_string() const;
317 
318  private:
319  void setup(const std::string& str, Category category);
320 
321  std::string::const_iterator parse_transport(const std::string& str, std::string::const_iterator transport_start);
322 
323  std::string::const_iterator parse_content(const std::string& str, std::string::const_iterator content_start);
324 
325  std::string::const_iterator parse_username(const std::string& str, const std::string& content,
326  std::string::const_iterator username_start);
327 
328  std::string::const_iterator parse_password(const std::string& str, const std::string& content,
329  std::string::const_iterator password_start);
330 
331  std::string::const_iterator parse_host(const std::string& str, const std::string& content,
332  std::string::const_iterator host_start);
333 
334  std::string::const_iterator parse_port(const std::string& str, const std::string& content,
335  std::string::const_iterator port_start);
336 
337  std::string::const_iterator parse_query(const std::string& str, std::string::const_iterator query_start);
338 
339  std::string::const_iterator parse_fragment(const std::string& str, std::string::const_iterator fragment_start);
340 
341  void init_query_dictionary();
342 
343  std::string transport_;
344  std::string content_;
345  std::string username_;
346  std::string password_;
347  std::string host_;
348  std::string path_;
349  std::string query_;
350  std::string fragment_;
351  std::map<std::string, std::string> query_dict_;
352  Category category_{Category::kHierarchical};
353  int64_t port_{0};
354  bool is_rooted_{false};
355  Separator separator_{Separator::kAmpersand};
356 };
357 
358 } // namespace vlink
#define VLINK_EXPORT
Definition: macros.h:81