GCC Code Coverage Report


Directory: libs/url/
File: libs/url/example/router/detail/impl/router.cpp
Date: 2024-02-29 20:02:56
Exec Total Coverage
Lines: 366 366 100.0%
Functions: 35 35 100.0%
Branches: 219 268 81.7%

Line Branch Exec Source
1 //
2 // Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/url
8 //
9
10 #ifndef BOOST_URL_DETAIL_ROUTER_IPP
11 #define BOOST_URL_DETAIL_ROUTER_IPP
12
13 #include "../router.hpp"
14 #include <boost/url/decode_view.hpp>
15 #include <boost/url/grammar/alnum_chars.hpp>
16 #include <boost/url/grammar/alpha_chars.hpp>
17 #include <boost/url/grammar/lut_chars.hpp>
18 #include <boost/url/grammar/token_rule.hpp>
19 #include <boost/url/grammar/variant_rule.hpp>
20 #include <boost/url/rfc/detail/path_rules.hpp>
21 #include <boost/url/detail/replacement_field_rule.hpp>
22 #include <vector>
23
24 namespace boost {
25 namespace urls {
26 namespace detail {
27
28 // A path segment template
29 class segment_template
30 {
31 enum class modifier : unsigned char
32 {
33 none,
34 // {id?}
35 optional,
36 // {id*}
37 star,
38 // {id+}
39 plus
40 };
41
42 std::string str_;
43 bool is_literal_ = true;
44 modifier modifier_ = modifier::none;
45
46 friend struct segment_template_rule_t;
47 public:
48 1271 segment_template() = default;
49
50 bool
51 match(pct_string_view seg) const;
52
53 core::string_view
54 326 string() const
55 {
56 326 return str_;
57 }
58
59 core::string_view
60 id() const;
61
62 bool
63 empty() const
64 {
65 return str_.empty();
66 }
67
68 bool
69 687 is_literal() const
70 {
71 687 return is_literal_;
72 }
73
74 bool
75 163 has_modifier() const
76 {
77
1/2
✓ Branch 1 taken 163 times.
✗ Branch 2 not taken.
326 return !is_literal() &&
78
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 101 times.
326 modifier_ != modifier::none;
79 }
80
81 bool
82 101 is_optional() const
83 {
84 101 return modifier_ == modifier::optional;
85 }
86
87 bool
88 51 is_star() const
89 {
90 51 return modifier_ == modifier::star;
91 }
92
93 bool
94 41 is_plus() const
95 {
96 41 return modifier_ == modifier::plus;
97 }
98
99 friend
100 83 bool operator==(
101 segment_template const& a,
102 segment_template const& b)
103 {
104
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 75 times.
83 if (a.is_literal_ != b.is_literal_)
105 8 return false;
106
2/2
✓ Branch 0 taken 73 times.
✓ Branch 1 taken 2 times.
75 if (a.is_literal_)
107 73 return a.str_ == b.str_;
108 2 return a.modifier_ == b.modifier_;
109 }
110
111 // segments have precedence:
112 // - literal
113 // - unique
114 // - optional
115 // - plus
116 // - star
117 friend
118 18 bool operator<(
119 segment_template const& a,
120 segment_template const& b)
121 {
122
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 3 times.
18 if (b.is_literal())
123 15 return false;
124
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
3 if (a.is_literal())
125 1 return !b.is_literal();
126 2 return a.modifier_ < b.modifier_;
127 }
128 };
129
130 // A segment template is either a literal string
131 // or a replacement field (as in a format_string).
132 // Fields cannot contain format specs and might
133 // have one of the following modifiers:
134 // - ?: optional segment
135 // - *: zero or more segments
136 // - +: one or more segments
137 struct segment_template_rule_t
138 {
139 using value_type = segment_template;
140
141 system::result<value_type>
142 parse(
143 char const*& it,
144 char const* end
145 ) const noexcept;
146 };
147
148 constexpr auto segment_template_rule = segment_template_rule_t{};
149
150 constexpr auto path_template_rule =
151 grammar::tuple_rule(
152 grammar::squelch(
153 grammar::optional_rule(
154 grammar::delim_rule('/'))),
155 grammar::range_rule(
156 segment_template_rule,
157 grammar::tuple_rule(
158 grammar::squelch(grammar::delim_rule('/')),
159 segment_template_rule)));
160
161 bool
162 311 segment_template::
163 match(pct_string_view seg) const
164 {
165
2/2
✓ Branch 0 taken 151 times.
✓ Branch 1 taken 160 times.
311 if (is_literal_)
166 151 return *seg == str_;
167
168 // other nodes match any string
169 160 return true;
170 }
171
172 core::string_view
173 168 segment_template::
174 id() const
175 {
176 // if (is_literal_) return {};
177
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 168 times.
168 BOOST_ASSERT(!is_literal());
178 168 core::string_view r = {str_};
179 168 r.remove_prefix(1);
180 168 r.remove_suffix(1);
181
2/2
✓ Branch 1 taken 118 times.
✓ Branch 2 taken 17 times.
303 if (r.ends_with('?') ||
182
6/6
✓ Branch 0 taken 135 times.
✓ Branch 1 taken 33 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 96 times.
✓ Branch 5 taken 72 times.
✓ Branch 6 taken 96 times.
303 r.ends_with('+') ||
183 118 r.ends_with('*'))
184 72 r.remove_suffix(1);
185 168 return r;
186 }
187
188 auto
189 654 segment_template_rule_t::
190 parse(
191 char const*& it,
192 char const* end) const noexcept
193 -> system::result<value_type>
194 {
195 1308 segment_template t;
196
2/2
✓ Branch 0 taken 646 times.
✓ Branch 1 taken 8 times.
654 if (it != end &&
197
2/2
✓ Branch 0 taken 323 times.
✓ Branch 1 taken 323 times.
646 *it == '{')
198 {
199 // replacement field
200 323 auto it0 = it;
201 323 ++it;
202 auto send =
203 323 grammar::find_if(
204 323 it, end, grammar::lut_chars('}'));
205
2/2
✓ Branch 0 taken 322 times.
✓ Branch 1 taken 1 times.
323 if (send != end)
206 {
207 322 core::string_view s(it, send);
208 static constexpr auto modifiers_cs =
209 grammar::lut_chars("?*+");
210 static constexpr auto id_rule =
211 grammar::tuple_rule(
212 grammar::optional_rule(
213 arg_id_rule),
214 grammar::optional_rule(
215 grammar::delim_rule(modifiers_cs)));
216
3/4
✓ Branch 1 taken 314 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 314 times.
✗ Branch 4 not taken.
636 if (s.empty() ||
217
3/4
✓ Branch 2 taken 314 times.
✓ Branch 3 taken 8 times.
✓ Branch 5 taken 322 times.
✗ Branch 6 not taken.
636 grammar::parse(s, id_rule))
218 {
219 322 it = send + 1;
220
221 322 t.str_ = core::string_view(it0, send + 1);
222 322 t.is_literal_ = false;
223
2/2
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 274 times.
322 if (s.ends_with('?'))
224 48 t.modifier_ =
225 segment_template::modifier::optional;
226
2/2
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 226 times.
274 else if (s.ends_with('*'))
227 48 t.modifier_ =
228 segment_template::modifier::star;
229
2/2
✓ Branch 1 taken 58 times.
✓ Branch 2 taken 168 times.
226 else if (s.ends_with('+'))
230 58 t.modifier_ =
231 segment_template::modifier::plus;
232 322 return t;
233 }
234 }
235 1 it = it0;
236 }
237 // literal segment
238 auto rv = grammar::parse(
239 332 it, end, urls::detail::segment_rule);
240
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 332 times.
332 BOOST_ASSERT(rv);
241 332 rv->decode({}, urls::string_token::assign_to(t.str_));
242 332 t.is_literal_ = true;
243 332 return t;
244 }
245
246 // a small vector for child nodes...
247 // we shouldn't expect many children per node, and
248 // we don't want to allocate for that. But we also
249 // cannot cap the max number of child nodes because
250 // especially the root nodes can potentially an
251 // exponentially higher number of child nodes.
252 class child_idx_vector
253 {
254 static constexpr std::size_t N = 5;
255 std::size_t static_child_idx_[N]{};
256 std::size_t* child_idx{nullptr};
257 std::size_t size_{0};
258 std::size_t cap_{0};
259
260 public:
261 1176 ~child_idx_vector()
262 1176 {
263
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1174 times.
1176 delete[] child_idx;
264 1176 }
265
266 393 child_idx_vector() = default;
267
268 390 child_idx_vector(child_idx_vector const& other)
269 390 : size_{other.size_}
270 390 , cap_{other.cap_}
271 {
272
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 389 times.
390 if (other.child_idx)
273 {
274
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 child_idx = new std::size_t[cap_];
275 1 std::memcpy(child_idx, other.child_idx, size_ * sizeof(std::size_t));
276 1 return;
277 }
278 389 std::memcpy(static_child_idx_, other.static_child_idx_, size_ * sizeof(std::size_t));
279 }
280
281 393 child_idx_vector(child_idx_vector&& other)
282 393 : child_idx{other.child_idx}
283 393 , size_{other.size_}
284 393 , cap_{other.cap_}
285 {
286 393 std::memcpy(static_child_idx_, other.static_child_idx_, N);
287 393 other.child_idx = nullptr;
288 393 }
289
290 bool
291 47 empty() const
292 {
293 47 return size_ == 0;
294 }
295
296 std::size_t
297 606 size() const
298 {
299 606 return size_;
300 }
301
302 std::size_t*
303 1304 begin()
304 {
305
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 1271 times.
1304 if (child_idx)
306 33 return child_idx;
307 1271 return static_child_idx_;
308 }
309
310 std::size_t*
311 642 end()
312 {
313 642 return begin() + size_;
314 }
315
316 std::size_t const*
317 678 begin() const
318 {
319
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 674 times.
678 if (child_idx)
320 4 return child_idx;
321 674 return static_child_idx_;
322 }
323
324 std::size_t const*
325 339 end() const
326 {
327 339 return begin() + size_;
328 }
329
330 void
331 5 erase(std::size_t* it)
332 {
333
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 BOOST_ASSERT(it - begin() >= 0);
334 5 std::memmove(it - 1, it, end() - it);
335 5 --size_;
336 5 }
337
338 void
339 298 push_back(std::size_t v)
340 {
341
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 297 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
298 if (size_ == N && !child_idx)
342 {
343 1 child_idx = new std::size_t[N*2];
344 1 cap_ = N*2;
345 1 std::memcpy(child_idx, static_child_idx_, N * sizeof(std::size_t));
346 }
347
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 292 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 4 times.
297 else if (child_idx && size_ == cap_)
348 {
349
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 auto* tmp = new std::size_t[cap_*2];
350 1 std::memcpy(tmp, child_idx, cap_ * sizeof(std::size_t));
351
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete[] child_idx;
352 1 child_idx = tmp;
353 1 cap_ = cap_*2;
354 }
355 298 begin()[size_++] = v;
356 298 }
357 };
358
359 // A node in the resource tree
360 // Each segment in the resource tree might be
361 // associated with
362 struct node
363 {
364 static constexpr std::size_t npos{std::size_t(-1)};
365
366 // literal segment or replacement field
367 detail::segment_template seg{};
368
369 // A pointer to the resource
370 router_base::any_resource const* resource{nullptr};
371
372 // The complete match for the resource
373 std::string path_template;
374
375 // Index of the parent node in the
376 // implementation pool of nodes
377 std::size_t parent_idx{npos};
378
379 // Index of child nodes in the pool
380 detail::child_idx_vector child_idx;
381 };
382
383 class impl
384 {
385 // Pool of nodes in the resource tree
386 std::vector<node> nodes_;
387
388 public:
389 95 impl()
390 95 {
391 // root node with no resource
392
1/2
✓ Branch 2 taken 95 times.
✗ Branch 3 not taken.
95 nodes_.push_back(node{});
393 95 }
394
395 95 ~impl()
396 95 {
397
2/2
✓ Branch 3 taken 388 times.
✓ Branch 4 taken 95 times.
483 for (auto &r: nodes_)
398
2/2
✓ Branch 0 taken 111 times.
✓ Branch 1 taken 277 times.
388 delete r.resource;
399 95 }
400
401 // include a node for a resource
402 void
403 insert_impl(
404 core::string_view path,
405 router_base::any_resource const* v);
406
407 // match a node and return the element
408 router_base::any_resource const*
409 find_impl(
410 segments_encoded_view path,
411 core::string_view*& matches,
412 core::string_view*& ids) const;
413
414 private:
415 // try to match from this root node
416 node const*
417 try_match(
418 segments_encoded_view::const_iterator it,
419 segments_encoded_view::const_iterator end,
420 node const* root,
421 int level,
422 core::string_view*& matches,
423 core::string_view*& ids) const;
424
425 // check if a node has a resource when we
426 // also consider optional paths through
427 // the child nodes.
428 static
429 node const*
430 find_optional_resource(
431 const node* root,
432 std::vector<node> const& ns,
433 core::string_view*& matches,
434 core::string_view*& ids);
435 };
436
437 node const*
438 55 impl::
439 find_optional_resource(
440 const node* root,
441 std::vector<node> const& ns,
442 core::string_view*& matches,
443 core::string_view*& ids)
444 {
445
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 BOOST_ASSERT(root);
446
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 42 times.
55 if (root->resource)
447 13 return root;
448
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 42 times.
42 BOOST_ASSERT(!root->child_idx.empty());
449
2/2
✓ Branch 2 taken 42 times.
✓ Branch 3 taken 27 times.
69 for (auto i: root->child_idx)
450 {
451 42 auto& c = ns[i];
452
4/4
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 16 times.
76 if (!c.seg.is_optional() &&
453
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 8 times.
34 !c.seg.is_star())
454 26 continue;
455 // Child nodes are also
456 // potentially optional.
457 16 auto matches0 = matches;
458 16 auto ids0 = ids;
459 16 *matches++ = {};
460 16 *ids++ = c.seg.id();
461 16 auto n = find_optional_resource(
462 &c, ns, matches, ids);
463
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 times.
16 if (n)
464 15 return n;
465 1 matches = matches0;
466 1 ids = ids0;
467 }
468 27 return nullptr;
469 }
470
471 void
472 113 impl::
473 insert_impl(
474 core::string_view path,
475 router_base::any_resource const* v)
476 {
477 // Parse dynamic route segments
478
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 111 times.
113 if (path.starts_with("/"))
479 2 path.remove_prefix(1);
480 auto segsr =
481
1/2
✓ Branch 1 taken 113 times.
✗ Branch 2 not taken.
226 grammar::parse(path, detail::path_template_rule);
482
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 112 times.
113 if (!segsr)
483 {
484
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete v;
485
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 segsr.value();
486 }
487 224 auto segs = *segsr;
488 224 auto it = segs.begin();
489 113 auto end = segs.end();
490
491 // Iterate existing nodes
492 112 node* cur = &nodes_.front();
493 112 int level = 0;
494
2/2
✓ Branch 1 taken 326 times.
✓ Branch 2 taken 112 times.
438 while (it != end)
495 {
496 326 core::string_view seg = (*it).string();
497
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 324 times.
326 if (seg == ".")
498 {
499 2 ++it;
500 10 continue;
501 }
502
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 317 times.
324 if (seg == "..")
503 {
504 // discount unmatched leaf or
505 // keep track of levels behind root
506
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 5 times.
7 if (cur == &nodes_.front())
507 {
508 2 --level;
509 2 ++it;
510 2 continue;
511 }
512 // move to parent deleting current
513 // if it carries no resource
514 5 std::size_t p_idx = cur->parent_idx;
515 5 if (cur == &nodes_.back() &&
516
4/8
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
10 !cur->resource &&
517 5 cur->child_idx.empty())
518 {
519 5 node* p = &nodes_[p_idx];
520 5 std::size_t cur_idx = cur - nodes_.data();
521
1/2
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
5 p->child_idx.erase(
522 std::remove(
523 p->child_idx.begin(),
524 p->child_idx.end(),
525 cur_idx));
526 5 nodes_.pop_back();
527 }
528 5 cur = &nodes_[p_idx];
529 5 ++it;
530 5 continue;
531 }
532 // discount unmatched root parent
533
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 316 times.
317 if (level < 0)
534 {
535 1 ++level;
536 1 ++it;
537 1 continue;
538 }
539 // look for child
540
1/2
✓ Branch 3 taken 316 times.
✗ Branch 4 not taken.
316 auto cit = std::find_if(
541 cur->child_idx.begin(),
542 cur->child_idx.end(),
543 166 [this, &it](std::size_t ci) -> bool
544 {
545 83 return nodes_[ci].seg == *it;
546 });
547
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 298 times.
316 if (cit != cur->child_idx.end())
548 {
549 // move to existing child
550 18 cur = &nodes_[*cit];
551 }
552 else
553 {
554 // create child if it doesn't exist
555 596 node child;
556
1/2
✓ Branch 2 taken 298 times.
✗ Branch 3 not taken.
298 child.seg = *it;
557 298 std::size_t cur_id = cur - nodes_.data();
558 298 child.parent_idx = cur_id;
559
1/2
✓ Branch 2 taken 298 times.
✗ Branch 3 not taken.
298 nodes_.push_back(std::move(child));
560
1/2
✓ Branch 3 taken 298 times.
✗ Branch 4 not taken.
298 nodes_[cur_id].child_idx.push_back(nodes_.size() - 1);
561
2/2
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 280 times.
298 if (nodes_[cur_id].child_idx.size() > 1)
562 {
563 // keep nodes sorted
564 18 auto& cs = nodes_[cur_id].child_idx;
565 18 std::size_t n = cs.size() - 1;
566
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 times.
19 while (n)
567 {
568
2/2
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 17 times.
18 if (nodes_[cs.begin()[n]].seg < nodes_[cs.begin()[n - 1]].seg)
569 1 std::swap(cs.begin()[n], cs.begin()[n - 1]);
570 else
571 17 break;
572 1 --n;
573 }
574 }
575 298 cur = &nodes_.back();
576 }
577 316 ++it;
578 }
579
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 111 times.
112 if (level != 0)
580 {
581
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete v;
582 1 urls::detail::throw_invalid_argument();
583 }
584 111 cur->resource = v;
585
1/2
✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
111 cur->path_template = path;
586 111 }
587
588 node const*
589 455 impl::
590 try_match(
591 segments_encoded_view::const_iterator it,
592 segments_encoded_view::const_iterator end,
593 node const* cur,
594 int level,
595 core::string_view*& matches,
596 core::string_view*& ids) const
597 {
598
2/2
✓ Branch 1 taken 340 times.
✓ Branch 2 taken 115 times.
455 while (it != end)
599 {
600 340 pct_string_view s = *it;
601
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 335 times.
340 if (*s == ".")
602 {
603 // ignore segment
604 5 ++it;
605 50 continue;
606 }
607
2/2
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 291 times.
335 if (*s == "..")
608 {
609
610 // move back to the parent node
611 44 ++it;
612
6/6
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 31 times.
✓ Branch 5 taken 13 times.
76 if (level <= 0 &&
613 32 cur != &nodes_.front())
614 {
615
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 9 times.
31 if (!cur->seg.is_literal())
616 {
617 22 --matches;
618 22 --ids;
619 }
620 31 cur = &nodes_[cur->parent_idx];
621 }
622 else
623 // there's no parent, so we
624 // discount that from the implicit
625 // tree beyond terminals
626 13 --level;
627 44 continue;
628 }
629
630 // we are in the implicit tree above the
631 // root, so discount that as a level
632
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 290 times.
291 if (level < 0)
633 {
634 1 ++level;
635 1 ++it;
636 1 continue;
637 }
638
639 // calculate the lower bound on the
640 // possible number of branches to
641 // determine if we need to branch.
642 // We branch when we might have more than
643 // one child matching node at this level.
644 // If so, we need to potentially branch
645 // to find which path leads to a valid
646 // resource. Otherwise, we can just
647 // consume the node and input without
648 // any recursive function calls.
649 290 bool branch = false;
650
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 283 times.
290 if (cur->child_idx.size() > 1)
651 {
652 7 int branches_lb = 0;
653
2/2
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 2 times.
27 for (auto i: cur->child_idx)
654 {
655 25 auto& c = nodes_[i];
656
4/4
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 3 times.
33 if (c.seg.is_literal() ||
657
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
8 !c.seg.has_modifier())
658 {
659 // a literal path counts only
660 // if it matches
661
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 branches_lb += c.seg.match(s);
662 }
663 else
664 {
665 // everything not matching
666 // a single path counts as
667 // more than one path already
668 3 branches_lb = 2;
669 }
670
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 20 times.
25 if (branches_lb > 1)
671 {
672 // already know we need to
673 // branch
674 5 branch = true;
675 5 break;
676 }
677 }
678 }
679
680 // attempt to match each child node
681 290 node const* r = nullptr;
682 290 bool match_any = false;
683
2/2
✓ Branch 2 taken 289 times.
✓ Branch 3 taken 19 times.
308 for (auto i: cur->child_idx)
684 {
685 289 auto& c = nodes_[i];
686
3/4
✓ Branch 1 taken 289 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 278 times.
✓ Branch 4 taken 11 times.
289 if (c.seg.match(s))
687 {
688
2/2
✓ Branch 1 taken 123 times.
✓ Branch 2 taken 155 times.
278 if (c.seg.is_literal())
689 {
690 // just continue from the
691 // next segment
692
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 121 times.
123 if (branch)
693 {
694
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 r = try_match(
695 std::next(it), end,
696 &c, level,
697 matches, ids);
698
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (r)
699 2 break;
700 }
701 else
702 {
703 121 cur = &c;
704 121 match_any = true;
705 121 break;
706 }
707 }
708
2/2
✓ Branch 1 taken 96 times.
✓ Branch 2 taken 59 times.
155 else if (!c.seg.has_modifier())
709 {
710 // just continue from the
711 // next segment
712
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 94 times.
96 if (branch)
713 {
714 2 auto matches0 = matches;
715 2 auto ids0 = ids;
716 2 *matches++ = *it;
717
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 *ids++ = c.seg.id();
718
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 r = try_match(
719 std::next(it), end, &c,
720 level, matches, ids);
721
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (r)
722 {
723 1 break;
724 }
725 else
726 {
727 // rewind
728 1 matches = matches0;
729 1 ids = ids0;
730 }
731 }
732 else
733 {
734 // only path possible
735 94 *matches++ = *it;
736
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 *ids++ = c.seg.id();
737 94 cur = &c;
738 94 match_any = true;
739 94 break;
740 }
741 }
742
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 41 times.
59 else if (c.seg.is_optional())
743 {
744 // attempt to match by ignoring
745 // and not ignoring the segment.
746 // we first try the complete
747 // continuation consuming the
748 // input, which is the
749 // longest and most likely
750 // match
751 18 auto matches0 = matches;
752 18 auto ids0 = ids;
753 18 *matches++ = *it;
754
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 *ids++ = c.seg.id();
755
2/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
18 r = try_match(
756 std::next(it), end,
757 &c, level, matches, ids);
758
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 7 times.
18 if (r)
759 11 break;
760 // rewind
761 7 matches = matches0;
762 7 ids = ids0;
763 // try complete continuation
764 // consuming no segment
765 7 *matches++ = {};
766
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 *ids++ = c.seg.id();
767
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 r = try_match(
768 it, end, &c,
769 level, matches, ids);
770
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if (r)
771 5 break;
772 // rewind
773 2 matches = matches0;
774 2 ids = ids0;
775 }
776 else
777 {
778 // check if the next segments
779 // won't send us to a parent
780 // directory
781 41 auto first = it;
782 41 std::size_t ndotdot = 0;
783 41 std::size_t nnondot = 0;
784 41 auto it1 = it;
785
2/2
✓ Branch 1 taken 83 times.
✓ Branch 2 taken 31 times.
114 while (it1 != end)
786 {
787
2/2
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 66 times.
83 if (*it1 == "..")
788 {
789 17 ++ndotdot;
790
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 7 times.
17 if (ndotdot >= (nnondot + c.seg.is_star()))
791 10 break;
792 }
793
1/2
✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
66 else if (*it1 != ".")
794 {
795 66 ++nnondot;
796 }
797 73 ++it1;
798 }
799
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 31 times.
41 if (it1 != end)
800 37 break;
801
802 // attempt to match many
803 // segments
804 31 auto matches0 = matches;
805 31 auto ids0 = ids;
806 31 *matches++ = *it;
807
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 *ids++ = c.seg.id();
808 // if this is a plus seg, we
809 // already consumed the first
810 // segment
811
2/2
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 14 times.
31 if (c.seg.is_plus())
812 {
813 17 ++first;
814 }
815 // {*} is usually the last
816 // match in a path.
817 // try complete continuation
818 // match for every subrange
819 // from {last, last} to
820 // {first, last}.
821 // We also try {last, last}
822 // first because it is the
823 // longest match.
824 31 auto start = end;
825
2/2
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 14 times.
39 while (start != first)
826 {
827
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 r = try_match(
828 start, end, &c,
829 level, matches, ids);
830
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 8 times.
25 if (r)
831 {
832
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 core::string_view prev = *std::prev(start);
833 34 *matches0 = {
834 matches0->data(),
835 17 prev.data() + prev.size()};
836 17 break;
837 }
838 8 matches = matches0 + 1;
839 8 ids = ids0 + 1;
840 8 --start;
841 }
842
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 14 times.
31 if (r)
843 {
844 17 break;
845 }
846 // start == first
847 14 matches = matches0 + 1;
848 14 ids = ids0 + 1;
849
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 r = try_match(
850 start, end, &c,
851 level, matches, ids);
852
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
14 if (r)
853 {
854
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
10 if (!c.seg.is_plus())
855 2 *matches0 = {};
856 10 break;
857 }
858 }
859 }
860 }
861 // r represent we already found a terminal
862 // node which is a match
863
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 244 times.
290 if (r)
864 46 return r;
865 // if we couldn't match anything, we go
866 // one level up in the implicit tree
867 // because the path might still have a
868 // "..".
869
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 215 times.
244 if (!match_any)
870 29 ++level;
871 244 ++it;
872 }
873
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 102 times.
115 if (level != 0)
874 {
875 // the path ended below or above an
876 // existing node
877 13 return nullptr;
878 }
879
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 63 times.
102 if (!cur->resource)
880 {
881 // we consumed all the input and reached
882 // a node with no resource, but it might
883 // still have child optional segments
884 // with resources we can reach without
885 // consuming any input
886 78 return find_optional_resource(
887 39 cur, nodes_, matches, ids);
888 }
889 63 return cur;
890 }
891
892 router_base::any_resource const*
893 93 impl::
894 find_impl(
895 segments_encoded_view path,
896 core::string_view*& matches,
897 core::string_view*& ids) const
898 {
899 // parse_path is inconsistent for empty paths
900
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 89 times.
93 if (path.empty())
901
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 path = segments_encoded_view("./");
902
903 // Iterate nodes from the root
904 93 node const*p = try_match(
905 path.begin(), path.end(),
906 93 &nodes_.front(), 0,
907 matches, ids);
908
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 17 times.
93 if (p)
909 76 return p->resource;
910 17 return nullptr;
911 }
912
913 95 router_base::
914 95 router_base()
915
1/2
✓ Branch 2 taken 95 times.
✗ Branch 3 not taken.
95 : impl_(new impl{}) {}
916
917 190 router_base::
918 380 ~router_base()
919 {
920
1/2
✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
190 delete reinterpret_cast<impl*>(impl_);
921 190 }
922
923 void
924 113 router_base::
925 insert_impl(
926 core::string_view s,
927 any_resource const* v)
928 {
929 113 reinterpret_cast<impl*>(impl_)
930 113 ->insert_impl(s, v);
931 111 }
932
933 auto
934 93 router_base::
935 find_impl(
936 segments_encoded_view path,
937 core::string_view*& matches,
938 core::string_view*& ids) const noexcept
939 -> any_resource const*
940 {
941 93 return reinterpret_cast<impl*>(impl_)
942 93 ->find_impl(path, matches, ids);
943 }
944
945 } // detail
946 } // urls
947 } // boost
948
949 #endif
950