1use core::{
12 cmp::Reverse,
13 fmt,
14 hash::{Hash, Hasher},
15};
16
17use crate::std::{
18 borrow::{Cow, ToOwned},
19 boxed::Box,
20 string::String,
21 vec,
22 vec::Vec,
23};
24
25use super::component_input::IntoUriComponent;
26use super::path::{PathMatchOptions, PathRef, byte_starts_with, maybe_decode, strip_leading_slash};
27use crate::byte_sets::is_pattern_name_byte;
28use crate::input_ext::PathInputExt;
29
30use rama_core::{
31 Service,
32 extensions::{Extension, ExtensionsRef},
33};
34use rama_utils::collections::smallvec::SmallVec;
35
36type EncodedSegment<'a> = Cow<'a, str>;
37
38#[derive(Debug, Clone)]
93pub struct PathPattern {
94 segments: Vec<PatternSegment>,
95 name_bytes: Vec<u8>,
99 trailing: TrailingSlash,
100 opts: PathMatchOptions,
101 capture_free: bool,
104 prefix: bool,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114pub enum PathPatternSegmentKind {
115 Literal,
117 Dynamic,
120 CatchAll,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub struct PathPatternSegmentSpecificity {
132 pub kind: PathPatternSegmentKind,
134 pub literal_bytes: usize,
136 pub dynamic_parts: usize,
138 pub optional_parts: usize,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145enum TrailingSlash {
146 Forbidden,
148 Required,
150 Optional,
152}
153
154impl TrailingSlash {
155 fn accepts(self, path_has_slash: bool) -> bool {
158 match self {
159 Self::Forbidden => !path_has_slash,
160 Self::Required => path_has_slash,
161 Self::Optional => true,
162 }
163 }
164}
165
166#[derive(Debug, Clone)]
168enum PatternSegment {
169 CatchAll,
172 NamedCatchAll { name_start: usize, name_len: usize },
175 Normal {
179 elems: SmallVec<[Element; 2]>,
180 ambiguity: usize,
188 },
189}
190
191#[derive(Debug, Clone)]
193struct Element {
194 kind: ElementKind,
195 optional: bool,
197}
198
199#[derive(Debug, Clone)]
200enum ElementKind {
201 Literal(Box<[u8]>),
204 Star,
206 Capture { name_start: usize, name_len: usize },
209}
210
211impl PartialEq for PathPattern {
212 fn eq(&self, other: &Self) -> bool {
213 self.trailing == other.trailing
214 && self.opts == other.opts
215 && self.prefix == other.prefix
216 && self.segments.len() == other.segments.len()
217 && self.segments.iter().zip(&other.segments).all(|(a, b)| {
218 pattern_segments_eq(
219 a,
220 &self.name_bytes,
221 b,
222 &other.name_bytes,
223 self.opts.ignore_ascii_case,
224 )
225 })
226 }
227}
228
229impl Eq for PathPattern {}
230
231impl Hash for PathPattern {
232 fn hash<H: Hasher>(&self, state: &mut H) {
233 self.trailing.hash(state);
234 self.opts.hash(state);
235 self.prefix.hash(state);
236 self.segments.len().hash(state);
237 for segment in &self.segments {
238 hash_pattern_segment(
239 segment,
240 &self.name_bytes,
241 self.opts.ignore_ascii_case,
242 state,
243 );
244 }
245 }
246}
247
248fn pattern_segments_eq(
249 a: &PatternSegment,
250 a_names: &[u8],
251 b: &PatternSegment,
252 b_names: &[u8],
253 ignore_ascii_case: bool,
254) -> bool {
255 match (a, b) {
256 (PatternSegment::CatchAll, PatternSegment::CatchAll) => true,
257 (
258 PatternSegment::NamedCatchAll {
259 name_start: a_start,
260 name_len: a_len,
261 },
262 PatternSegment::NamedCatchAll {
263 name_start: b_start,
264 name_len: b_len,
265 },
266 ) => {
267 let a = &a_names[*a_start..*a_start + *a_len];
268 let b = &b_names[*b_start..*b_start + *b_len];
269 a == b
270 }
271 (
272 PatternSegment::Normal {
273 elems: a_elems,
274 ambiguity: a_ambiguity,
275 },
276 PatternSegment::Normal {
277 elems: b_elems,
278 ambiguity: b_ambiguity,
279 },
280 ) => {
281 a_ambiguity == b_ambiguity
282 && a_elems.len() == b_elems.len()
283 && a_elems
284 .iter()
285 .zip(b_elems)
286 .all(|(a, b)| elements_eq(a, a_names, b, b_names, ignore_ascii_case))
287 }
288 _ => false,
289 }
290}
291
292fn elements_eq(
293 a: &Element,
294 a_names: &[u8],
295 b: &Element,
296 b_names: &[u8],
297 ignore_ascii_case: bool,
298) -> bool {
299 a.optional == b.optional
300 && element_kinds_eq(&a.kind, a_names, &b.kind, b_names, ignore_ascii_case)
301}
302
303fn element_kinds_eq(
304 a: &ElementKind,
305 a_names: &[u8],
306 b: &ElementKind,
307 b_names: &[u8],
308 ignore_ascii_case: bool,
309) -> bool {
310 match (a, b) {
311 (ElementKind::Literal(a), ElementKind::Literal(b)) => literal_eq(a, b, ignore_ascii_case),
312 (ElementKind::Star, ElementKind::Star) => true,
313 (
314 ElementKind::Capture {
315 name_start: a_start,
316 name_len: a_len,
317 },
318 ElementKind::Capture {
319 name_start: b_start,
320 name_len: b_len,
321 },
322 ) => {
323 let a = &a_names[*a_start..*a_start + *a_len];
324 let b = &b_names[*b_start..*b_start + *b_len];
325 a == b
326 }
327 _ => false,
328 }
329}
330
331fn literal_eq(a: &[u8], b: &[u8], ignore_ascii_case: bool) -> bool {
332 if ignore_ascii_case {
333 a.eq_ignore_ascii_case(b)
334 } else {
335 a == b
336 }
337}
338
339fn hash_pattern_segment<H: Hasher>(
340 segment: &PatternSegment,
341 names: &[u8],
342 ignore_ascii_case: bool,
343 state: &mut H,
344) {
345 match segment {
346 PatternSegment::CatchAll => 0u8.hash(state),
347 PatternSegment::NamedCatchAll {
348 name_start,
349 name_len,
350 } => {
351 1u8.hash(state);
352 names[*name_start..*name_start + *name_len].hash(state);
353 }
354 PatternSegment::Normal { elems, ambiguity } => {
355 2u8.hash(state);
356 ambiguity.hash(state);
357 elems.len().hash(state);
358 for element in elems {
359 hash_element(element, names, ignore_ascii_case, state);
360 }
361 }
362 }
363}
364
365fn hash_element<H: Hasher>(
366 element: &Element,
367 names: &[u8],
368 ignore_ascii_case: bool,
369 state: &mut H,
370) {
371 element.optional.hash(state);
372 match &element.kind {
373 ElementKind::Literal(literal) => {
374 0u8.hash(state);
375 hash_literal(literal, ignore_ascii_case, state);
376 }
377 ElementKind::Star => 1u8.hash(state),
378 ElementKind::Capture {
379 name_start,
380 name_len,
381 } => {
382 2u8.hash(state);
383 names[*name_start..*name_start + *name_len].hash(state);
384 }
385 }
386}
387
388fn hash_literal<H: Hasher>(literal: &[u8], ignore_ascii_case: bool, state: &mut H) {
389 if ignore_ascii_case {
390 literal.len().hash(state);
391 for byte in literal {
392 byte.to_ascii_lowercase().hash(state);
393 }
394 } else {
395 literal.hash(state);
396 }
397}
398
399fn pattern_segment_capture_free(segment: &PatternSegment) -> bool {
400 match segment {
401 PatternSegment::CatchAll | PatternSegment::NamedCatchAll { .. } => false,
402 PatternSegment::Normal { elems, .. } => elems
403 .iter()
404 .all(|element| !matches!(element.kind, ElementKind::Capture { .. })),
405 }
406}
407
408#[derive(Debug, Clone)]
414pub struct PathRouter<T> {
415 root: PathRouteNode<T>,
416 len: usize,
417}
418
419#[derive(Debug, Clone)]
420struct PathRouteNode<T> {
421 routes: Vec<PathRoute<T>>,
422 children: Vec<PathRouteEdge<T>>,
423}
424
425#[derive(Debug, Clone)]
426struct PathRouteEdge<T> {
427 segment: PatternSegment,
428 name_bytes: Vec<u8>,
429 opts: PathMatchOptions,
430 rank: PathRouterSegmentRank,
431 child: Box<PathRouteNode<T>>,
432}
433
434#[derive(Debug, Clone)]
435struct PathRoute<T> {
436 pattern: PathPattern,
437 specificity: Box<[PathRouterSegmentRank]>,
438 has_captures: bool,
439 value: T,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
443struct PathRouterSegmentRank {
444 kind: u8,
445 literal_bytes: usize,
446 fewer_dynamic_parts: Reverse<usize>,
447 fewer_optional_parts: Reverse<usize>,
448}
449
450#[derive(Debug)]
452pub struct PathRouteMatch<'a, 'p, T> {
453 value: &'a T,
454 matched_segment_count: usize,
455 captures: PathCaptures<'a, 'p>,
456}
457
458#[derive(Debug, Clone, Default, Extension)]
460#[extension(tags(net))]
461pub struct PathRouteCaptures {
462 params: SmallVec<[(String, String); 4]>,
463 glob: Option<String>,
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
468pub enum PathRouterError<E> {
469 NotFound,
471 Inner(E),
473}
474
475impl<E> fmt::Display for PathRouterError<E>
476where
477 E: fmt::Display,
478{
479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480 match self {
481 Self::NotFound => f.write_str("no path route matched input"),
482 Self::Inner(err) => err.fmt(f),
483 }
484 }
485}
486
487impl<E> core::error::Error for PathRouterError<E> where E: core::error::Error + 'static {}
488
489impl<T> Default for PathRouteNode<T> {
490 fn default() -> Self {
491 Self {
492 routes: Vec::new(),
493 children: Vec::new(),
494 }
495 }
496}
497
498impl<T> Default for PathRouter<T> {
499 fn default() -> Self {
500 Self::new()
501 }
502}
503
504impl<T> PathRouter<T> {
505 #[must_use]
507 pub fn new() -> Self {
508 Self {
509 root: PathRouteNode::default(),
510 len: 0,
511 }
512 }
513
514 #[must_use]
516 pub fn is_empty(&self) -> bool {
517 self.len == 0
518 }
519
520 #[must_use]
522 pub fn len(&self) -> usize {
523 self.len
524 }
525
526 pub fn insert_prefix(&mut self, pattern: impl IntoUriComponent, value: T) -> Option<T> {
532 self.insert_prefix_with_opts(pattern, PathMatchOptions::default(), value)
533 }
534
535 pub fn insert_prefix_with_opts(
539 &mut self,
540 pattern: impl IntoUriComponent,
541 opts: PathMatchOptions,
542 value: T,
543 ) -> Option<T> {
544 let mut pattern = PathPattern::new_prefix_with_opts(pattern, opts);
545 pattern.drop_trailing_catch_all();
546 let has_captures = !pattern.capture_free;
547 let specificity = path_router_specificity(&pattern).into_boxed_slice();
548
549 let mut node = &mut self.root;
550 for (segment, rank) in pattern.segments.iter().zip(specificity.iter().copied()) {
551 let child_idx = if let Some(idx) = node.children.iter().position(|edge| {
552 edge.opts == pattern.opts
553 && pattern_segments_eq(
554 &edge.segment,
555 &edge.name_bytes,
556 segment,
557 &pattern.name_bytes,
558 pattern.opts.ignore_ascii_case,
559 )
560 }) {
561 idx
562 } else {
563 let idx = node.children.partition_point(|edge| edge.rank >= rank);
564 let (segment, name_bytes) =
565 clone_pattern_segment_with_local_names(segment, &pattern.name_bytes);
566 node.children.insert(
567 idx,
568 PathRouteEdge {
569 segment,
570 name_bytes,
571 opts: pattern.opts,
572 rank,
573 child: Box::default(),
574 },
575 );
576 idx
577 };
578 node = &mut node.children[child_idx].child;
579 }
580
581 if let Some(route) = node
582 .routes
583 .iter_mut()
584 .find(|route| route.pattern == pattern)
585 {
586 return Some(core::mem::replace(&mut route.value, value));
587 }
588
589 let pos = node
590 .routes
591 .partition_point(|route| route.specificity.as_ref() >= specificity.as_ref());
592 node.routes.insert(
593 pos,
594 PathRoute {
595 pattern,
596 specificity,
597 has_captures,
598 value,
599 },
600 );
601 self.len += 1;
602 None
603 }
604
605 #[must_use]
607 pub fn match_prefix<'a, 'p>(&'a self, path: PathRef<'p>) -> Option<PathRouteMatch<'a, 'p, T>> {
608 let segments: SmallVec<[EncodedSegment<'p>; 8]> = path
609 .segments()
610 .map(|segment| segment.as_encoded_str())
611 .collect();
612 let segments = prefix_content_segments(&segments);
613 let matched = self.root.match_prefix(segments, 0)?;
614 let captures = if matched.route.has_captures {
615 matched.route.pattern.captures(path)?
616 } else {
617 PathCaptures::empty(&matched.route.pattern.name_bytes)
618 };
619 Some(PathRouteMatch {
620 value: &matched.route.value,
621 matched_segment_count: matched.consumed,
622 captures,
623 })
624 }
625
626 #[must_use]
629 pub fn match_exact<'a, 'p>(&'a self, path: PathRef<'p>) -> Option<PathRouteMatch<'a, 'p, T>> {
630 let segments: SmallVec<[EncodedSegment<'p>; 8]> = path
631 .segments()
632 .map(|segment| segment.as_encoded_str())
633 .collect();
634 let segments = prefix_content_segments(&segments);
635 let matched = self.root.match_prefix(segments, 0)?;
636 let captures = matched.route.pattern.captures_exact(path)?;
637 Some(PathRouteMatch {
638 value: &matched.route.value,
639 matched_segment_count: matched.consumed,
640 captures,
641 })
642 }
643}
644
645impl<Input, T> Service<Input> for PathRouter<T>
646where
647 Input: ExtensionsRef + PathInputExt + Send + 'static,
648 T: Service<Input>,
649{
650 type Output = T::Output;
651 type Error = PathRouterError<T::Error>;
652
653 async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
654 let Some((service, captures)) = self.match_service(input.path_ref()) else {
655 return Err(PathRouterError::NotFound);
656 };
657 if !captures.is_empty() {
658 input.extensions().insert(captures);
659 }
660 service.serve(input).await.map_err(PathRouterError::Inner)
661 }
662}
663
664impl<T> PathRouter<T> {
665 fn match_service<'a>(&'a self, path: PathRef<'_>) -> Option<(&'a T, PathRouteCaptures)> {
666 let matched = self.match_prefix(path)?;
667 let captures = PathRouteCaptures::from_captures(matched.captures());
668 Some((matched.value(), captures))
669 }
670}
671
672#[derive(Debug, Clone, Copy)]
673struct PathRouteCandidate<'a, T> {
674 route: &'a PathRoute<T>,
675 consumed: usize,
676 skipped_optional_segments: usize,
677}
678
679impl<T> PathRouteNode<T> {
680 fn match_prefix<'a>(
681 &'a self,
682 segments: &[EncodedSegment<'_>],
683 index: usize,
684 ) -> Option<PathRouteCandidate<'a, T>> {
685 let mut best = self.routes.first().map(|route| PathRouteCandidate {
686 route,
687 consumed: index,
688 skipped_optional_segments: 0,
689 });
690
691 for edge in &self.children {
692 match &edge.segment {
693 PatternSegment::Normal { elems, ambiguity } => {
694 if let Some(segment) = segments.get(index) {
695 let mut sink = Sink::Ignore;
696 if match_segment(
697 elems,
698 *ambiguity,
699 segment.as_ref().as_bytes(),
700 edge.opts,
701 &mut sink,
702 ) && let Some(candidate) = edge.child.match_prefix(segments, index + 1)
703 {
704 best = best_route(best, candidate);
705 }
706 }
707 if optional_whole_segment_binding(elems).is_some()
708 && let Some(mut candidate) = edge.child.match_prefix(segments, index)
709 {
710 candidate.skipped_optional_segments += 1;
711 best = best_route(best, candidate);
712 }
713 }
714 PatternSegment::CatchAll | PatternSegment::NamedCatchAll { .. } => {
715 for next in index + 1..=segments.len() {
716 if let Some(candidate) = edge.child.match_prefix(segments, next) {
717 best = best_route(best, candidate);
718 }
719 }
720 }
721 }
722 }
723
724 best
725 }
726}
727
728fn best_route<'a, T>(
729 current: Option<PathRouteCandidate<'a, T>>,
730 candidate: PathRouteCandidate<'a, T>,
731) -> Option<PathRouteCandidate<'a, T>> {
732 let Some(current) = current else {
733 return Some(candidate);
734 };
735 if candidate.consumed > current.consumed
736 || (candidate.consumed == current.consumed
737 && (candidate.skipped_optional_segments < current.skipped_optional_segments
738 || (candidate.skipped_optional_segments == current.skipped_optional_segments
739 && candidate.route.specificity.as_ref() > current.route.specificity.as_ref())))
740 {
741 Some(candidate)
742 } else {
743 Some(current)
744 }
745}
746
747#[cfg(test)]
748mod path_router_candidate_tests {
749 use super::*;
750
751 fn test_route(pattern: &str, value: &'static str) -> PathRoute<&'static str> {
752 let pattern = PathPattern::new_prefix(pattern);
753 let specificity = path_router_specificity(&pattern).into_boxed_slice();
754 PathRoute {
755 pattern,
756 specificity,
757 has_captures: false,
758 value,
759 }
760 }
761
762 #[test]
763 fn best_route_prefers_fewer_skipped_optional_segments_when_consumed_ties() {
764 let skipped = test_route("/root/{name}?", "skipped");
765 let direct = test_route("/root", "direct");
766
767 let best = best_route(
768 Some(PathRouteCandidate {
769 route: &skipped,
770 consumed: 1,
771 skipped_optional_segments: 1,
772 }),
773 PathRouteCandidate {
774 route: &direct,
775 consumed: 1,
776 skipped_optional_segments: 0,
777 },
778 )
779 .unwrap();
780
781 assert_eq!(best.route.value, "direct");
782 }
783
784 #[test]
785 fn best_route_uses_specificity_after_consumed_and_skipped_tie() {
786 let dynamic = test_route("/{tenant}/settings", "dynamic");
787 let literal = test_route("/acme/{section}", "literal");
788
789 let best = best_route(
790 Some(PathRouteCandidate {
791 route: &dynamic,
792 consumed: 2,
793 skipped_optional_segments: 0,
794 }),
795 PathRouteCandidate {
796 route: &literal,
797 consumed: 2,
798 skipped_optional_segments: 0,
799 },
800 )
801 .unwrap();
802
803 assert_eq!(best.route.value, "literal");
804 }
805}
806
807fn prefix_content_segments<'s, 'p>(segments: &'s [EncodedSegment<'p>]) -> &'s [EncodedSegment<'p>] {
808 match segments {
809 [only] if only.is_empty() => &segments[..0],
810 [head @ .., last] if last.is_empty() => head,
811 _ => segments,
812 }
813}
814
815fn clone_pattern_segment_with_local_names(
816 segment: &PatternSegment,
817 names: &[u8],
818) -> (PatternSegment, Vec<u8>) {
819 let mut local_names = Vec::new();
820 let segment = match segment {
821 PatternSegment::CatchAll => PatternSegment::CatchAll,
822 PatternSegment::NamedCatchAll {
823 name_start,
824 name_len,
825 } => {
826 local_names.extend_from_slice(&names[*name_start..*name_start + *name_len]);
827 PatternSegment::NamedCatchAll {
828 name_start: 0,
829 name_len: *name_len,
830 }
831 }
832 PatternSegment::Normal { elems, ambiguity } => PatternSegment::Normal {
833 elems: elems
834 .iter()
835 .map(|element| clone_element_with_local_names(element, names, &mut local_names))
836 .collect(),
837 ambiguity: *ambiguity,
838 },
839 };
840 (segment, local_names)
841}
842
843fn clone_element_with_local_names(
844 element: &Element,
845 names: &[u8],
846 local_names: &mut Vec<u8>,
847) -> Element {
848 let kind = match &element.kind {
849 ElementKind::Literal(literal) => ElementKind::Literal(literal.clone()),
850 ElementKind::Star => ElementKind::Star,
851 ElementKind::Capture {
852 name_start,
853 name_len,
854 } => {
855 let local_start = local_names.len();
856 local_names.extend_from_slice(&names[*name_start..*name_start + *name_len]);
857 ElementKind::Capture {
858 name_start: local_start,
859 name_len: *name_len,
860 }
861 }
862 };
863 Element {
864 kind,
865 optional: element.optional,
866 }
867}
868
869impl<'a, 'p, T> PathRouteMatch<'a, 'p, T> {
870 #[must_use]
872 pub fn value(&self) -> &'a T {
873 self.value
874 }
875
876 #[must_use]
878 pub fn matched_segment_count(&self) -> usize {
879 self.matched_segment_count
880 }
881
882 #[must_use]
884 pub fn captures(&self) -> &PathCaptures<'a, 'p> {
885 &self.captures
886 }
887
888 #[must_use]
890 pub fn into_parts(self) -> (&'a T, usize, PathCaptures<'a, 'p>) {
891 (self.value, self.matched_segment_count, self.captures)
892 }
893}
894
895impl PathRouteCaptures {
896 fn from_captures(captures: &PathCaptures<'_, '_>) -> Self {
897 Self {
898 params: captures
899 .iter()
900 .map(|(name, value)| (name.to_owned(), value.to_owned()))
901 .collect(),
902 glob: captures.glob().map(str::to_owned),
903 }
904 }
905
906 #[must_use]
908 pub fn get(&self, name: &str) -> Option<&str> {
909 self.params
910 .iter()
911 .find(|(key, _)| key == name)
912 .map(|(_, value)| value.as_str())
913 }
914
915 #[must_use]
917 pub fn get_non_empty(&self, name: &str) -> Option<&str> {
918 self.get(name).filter(|value| !value.is_empty())
919 }
920
921 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
923 self.params
924 .iter()
925 .map(|(name, value)| (name.as_str(), value.as_str()))
926 }
927
928 #[must_use]
930 pub fn glob(&self) -> Option<&str> {
931 self.glob.as_deref()
932 }
933
934 #[must_use]
936 pub fn is_empty(&self) -> bool {
937 self.params.is_empty() && self.glob.is_none()
938 }
939}
940
941fn path_router_specificity(pattern: &PathPattern) -> Vec<PathRouterSegmentRank> {
942 pattern
943 .segment_specificity()
944 .map(|spec| PathRouterSegmentRank {
945 kind: match spec.kind {
946 PathPatternSegmentKind::Literal => 2,
947 PathPatternSegmentKind::Dynamic => 1,
948 PathPatternSegmentKind::CatchAll => 0,
949 },
950 literal_bytes: spec.literal_bytes,
951 fewer_dynamic_parts: Reverse(spec.dynamic_parts),
952 fewer_optional_parts: Reverse(spec.optional_parts),
953 })
954 .collect()
955}
956
957impl PathPattern {
958 #[must_use]
969 pub fn new(pattern: impl IntoUriComponent) -> Self {
970 Self::new_with_opts(pattern, PathMatchOptions::default())
971 }
972
973 #[must_use]
988 #[expect(
989 clippy::needless_pass_by_value,
990 reason = "by-value matches IntoUriComponent's signature on sibling APIs; this impl only borrows the input"
991 )]
992 pub fn new_with_opts(pattern: impl IntoUriComponent, opts: PathMatchOptions) -> Self {
993 let raw = pattern.as_uri_component_bytes();
994 Self::compile(&raw, opts, false)
995 }
996
997 #[must_use]
1011 pub fn new_prefix(pattern: impl IntoUriComponent) -> Self {
1012 Self::new_prefix_with_opts(pattern, PathMatchOptions::default())
1013 }
1014
1015 #[must_use]
1017 #[expect(
1018 clippy::needless_pass_by_value,
1019 reason = "by-value matches IntoUriComponent's signature on sibling APIs; this impl only borrows the input"
1020 )]
1021 pub fn new_prefix_with_opts(pattern: impl IntoUriComponent, opts: PathMatchOptions) -> Self {
1022 let raw = pattern.as_uri_component_bytes();
1023 Self::compile(&raw, opts, true)
1024 }
1025
1026 fn compile(raw: &[u8], mut opts: PathMatchOptions, prefix: bool) -> Self {
1027 opts.partial = false;
1028 let (raw, trailing) = if let Some(rest) = raw.strip_suffix(b"/?") {
1032 (rest, TrailingSlash::Optional)
1033 } else if let Some(rest) = raw.strip_suffix(b"/") {
1034 (rest, TrailingSlash::Required)
1035 } else {
1036 (raw, TrailingSlash::Forbidden)
1037 };
1038 let body = strip_leading_slash(raw);
1039
1040 let mut name_bytes: Vec<u8> = Vec::new();
1041 let mut segments = Vec::new();
1042 let mut capture_free = true;
1043
1044 if !body.is_empty() {
1048 for seg in body.split(|&b| b == b'/') {
1049 match parse_catchall(seg) {
1050 Some(CatchAll::Anon) => {
1051 capture_free = false;
1052 segments.push(PatternSegment::CatchAll);
1053 continue;
1054 }
1055 Some(CatchAll::Named(name)) => {
1056 capture_free = false;
1057 let name_start = name_bytes.len();
1058 name_bytes.extend_from_slice(name);
1059 segments.push(PatternSegment::NamedCatchAll {
1060 name_start,
1061 name_len: name.len(),
1062 });
1063 continue;
1064 }
1065 None => {}
1066 }
1067 let elements = parse_segment(seg, &mut name_bytes, &mut capture_free);
1068 if optional_whole_segment_binding(&elements).is_some() {
1069 capture_free = false;
1070 }
1071 let ambiguity = elements
1072 .iter()
1073 .filter(|e| {
1074 e.optional
1075 || matches!(e.kind, ElementKind::Star | ElementKind::Capture { .. })
1076 })
1077 .count();
1078 segments.push(PatternSegment::Normal {
1079 elems: elements,
1080 ambiguity,
1081 });
1082 }
1083 }
1084
1085 Self {
1086 segments,
1087 name_bytes,
1088 trailing,
1089 opts,
1090 capture_free,
1091 prefix,
1092 }
1093 }
1094
1095 fn drop_trailing_catch_all(&mut self) -> bool {
1096 let Some(PatternSegment::CatchAll | PatternSegment::NamedCatchAll { .. }) =
1097 self.segments.last()
1098 else {
1099 return false;
1100 };
1101 self.segments.pop();
1102 self.capture_free = self.segments.iter().all(pattern_segment_capture_free);
1103 true
1104 }
1105
1106 pub fn segment_kinds(&self) -> impl ExactSizeIterator<Item = PathPatternSegmentKind> + '_ {
1121 self.segment_specificity().map(|spec| spec.kind)
1122 }
1123
1124 pub fn segment_specificity(
1140 &self,
1141 ) -> impl ExactSizeIterator<Item = PathPatternSegmentSpecificity> + '_ {
1142 self.segments.iter().map(|seg| match seg {
1143 PatternSegment::CatchAll | PatternSegment::NamedCatchAll { .. } => {
1144 PathPatternSegmentSpecificity {
1145 kind: PathPatternSegmentKind::CatchAll,
1146 literal_bytes: 0,
1147 dynamic_parts: 1,
1148 optional_parts: 0,
1149 }
1150 }
1151 PatternSegment::Normal { elems, ambiguity } => {
1152 let literal_bytes = elems
1153 .iter()
1154 .map(|el| match &el.kind {
1155 ElementKind::Literal(lit) => lit.len(),
1156 ElementKind::Star | ElementKind::Capture { .. } => 0,
1157 })
1158 .sum();
1159 let dynamic_parts = elems
1160 .iter()
1161 .filter(|el| matches!(el.kind, ElementKind::Star | ElementKind::Capture { .. }))
1162 .count();
1163 let optional_parts = elems.iter().filter(|el| el.optional).count();
1164 PathPatternSegmentSpecificity {
1165 kind: if *ambiguity == 0 {
1168 PathPatternSegmentKind::Literal
1169 } else {
1170 PathPatternSegmentKind::Dynamic
1171 },
1172 literal_bytes,
1173 dynamic_parts,
1174 optional_parts,
1175 }
1176 }
1177 })
1178 }
1179
1180 #[must_use]
1191 pub fn is_match(&self, path: PathRef<'_>) -> bool {
1192 if self.capture_free && !self.prefix {
1195 self.is_match_fast(path)
1196 } else {
1197 self.captures(path).is_some()
1198 }
1199 }
1200
1201 fn is_match_fast(&self, path: PathRef<'_>) -> bool {
1206 let mut path_iter = path.segments().peekable();
1209 let mut pat_iter = self.segments.iter();
1210 let mut content_count = 0usize;
1211 let mut ignore = Sink::Ignore;
1212
1213 let trailing = loop {
1214 let Some(seg) = path_iter.next() else {
1215 break false;
1217 };
1218 let is_last = path_iter.peek().is_none();
1219 if seg.is_empty() && is_last && (content_count >= 1 || self.segments.is_empty()) {
1223 break true;
1224 }
1225 match pat_iter.next() {
1226 Some(PatternSegment::Normal { elems, ambiguity }) => {
1227 if !match_segment(
1228 elems,
1229 *ambiguity,
1230 seg.as_encoded_str().as_ref().as_bytes(),
1231 self.opts,
1232 &mut ignore,
1233 ) {
1234 return false;
1235 }
1236 }
1237 None | Some(PatternSegment::CatchAll | PatternSegment::NamedCatchAll { .. }) => {
1241 return false;
1242 }
1243 }
1244 content_count += 1;
1245 };
1246
1247 pat_iter.next().is_none() && self.trailing.accepts(trailing)
1250 }
1251
1252 #[must_use]
1263 pub fn captures<'p>(&self, path: PathRef<'p>) -> Option<PathCaptures<'_, 'p>> {
1264 self.captures_with_prefix_mode(path, self.prefix)
1265 }
1266
1267 fn captures_exact<'p>(&self, path: PathRef<'p>) -> Option<PathCaptures<'_, 'p>> {
1268 self.captures_with_prefix_mode(path, false)
1269 }
1270
1271 fn captures_with_prefix_mode<'p>(
1272 &self,
1273 path: PathRef<'p>,
1274 prefix: bool,
1275 ) -> Option<PathCaptures<'_, 'p>> {
1276 let all: SmallVec<[EncodedSegment<'p>; 8]> =
1279 path.segments().map(|s| s.as_encoded_str()).collect();
1280 let segs: &[EncodedSegment<'p>] = if prefix {
1284 &all
1285 } else {
1286 self.check_trailing(&all)?
1287 };
1288 let mut bindings: SmallVec<[Binding<'p>; 4]> = SmallVec::new();
1289 let mut sink = Sink::Record(&mut bindings);
1290 let mut seq_memo = SeqMemo::new(&self.segments, segs.len());
1291 if match_sequence(
1292 &self.segments,
1293 segs,
1294 self.opts,
1295 &mut sink,
1296 &mut seq_memo,
1297 prefix,
1298 ) {
1299 Some(PathCaptures {
1300 name_bytes: &self.name_bytes,
1301 bindings,
1302 })
1303 } else {
1304 None
1305 }
1306 }
1307
1308 fn check_trailing<'s, 'p>(
1317 &self,
1318 segs: &'s [EncodedSegment<'p>],
1319 ) -> Option<&'s [EncodedSegment<'p>]> {
1320 let last_empty = segs.last().is_some_and(|s| s.is_empty());
1323 let is_root = segs.len() == 1 && last_empty && self.segments.is_empty();
1324 let (content, has_slash) = if is_root {
1325 (&segs[..0], true)
1326 } else if last_empty && segs.len() >= 2 {
1327 (&segs[..segs.len() - 1], true)
1328 } else {
1329 (segs, false)
1330 };
1331 self.trailing.accepts(has_slash).then_some(content)
1332 }
1333}
1334
1335#[derive(Debug, Clone)]
1338struct Binding<'p> {
1339 name_start: usize,
1340 name_len: usize,
1341 value: Cow<'p, str>,
1342 is_glob: bool,
1344}
1345
1346enum Sink<'b, 'p> {
1349 Ignore,
1350 Record(&'b mut SmallVec<[Binding<'p>; 4]>),
1351}
1352
1353impl<'p> Sink<'_, 'p> {
1354 fn insert_at(&mut self, idx: usize, b: Binding<'p>) {
1357 if let Sink::Record(v) = self {
1358 v.insert(idx, b);
1359 }
1360 }
1361 fn len(&self) -> usize {
1362 match self {
1363 Sink::Ignore => 0,
1364 Sink::Record(v) => v.len(),
1365 }
1366 }
1367 fn truncate(&mut self, n: usize) {
1368 if let Sink::Record(v) = self {
1369 v.truncate(n);
1370 }
1371 }
1372}
1373
1374#[derive(Debug, Clone)]
1390pub struct PathCaptures<'a, 'p> {
1391 name_bytes: &'a [u8],
1392 bindings: SmallVec<[Binding<'p>; 4]>,
1393}
1394
1395impl<'a, 'p> PathCaptures<'a, 'p> {
1396 fn empty(name_bytes: &'a [u8]) -> Self {
1397 Self {
1398 name_bytes,
1399 bindings: SmallVec::new(),
1400 }
1401 }
1402
1403 fn name_of(&self, b: &Binding<'p>) -> &'a str {
1404 let raw = &self.name_bytes[b.name_start..b.name_start + b.name_len];
1405 unsafe { core::str::from_utf8_unchecked(raw) }
1408 }
1409
1410 #[must_use]
1414 pub fn get(&self, name: &str) -> Option<&str> {
1415 self.bindings
1416 .iter()
1417 .find(|b| !b.is_glob && b.name_len != 0 && self.name_of(b) == name)
1418 .map(|b| b.value.as_ref())
1419 }
1420
1421 #[must_use]
1423 pub fn get_non_empty(&self, name: &str) -> Option<&str> {
1424 self.get(name).filter(|value| !value.is_empty())
1425 }
1426
1427 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
1430 self.bindings
1431 .iter()
1432 .filter(|b| !b.is_glob && b.name_len != 0)
1433 .map(|b| (self.name_of(b), b.value.as_ref()))
1434 }
1435
1436 #[must_use]
1439 pub fn glob(&self) -> Option<&str> {
1440 self.bindings
1441 .iter()
1442 .find(|b| b.is_glob)
1443 .map(|b| b.value.as_ref())
1444 }
1445
1446 #[must_use]
1448 pub fn is_empty(&self) -> bool {
1449 self.bindings.is_empty()
1450 }
1451}
1452
1453enum CatchAll<'a> {
1459 Anon,
1461 Named(&'a [u8]),
1463}
1464
1465fn parse_catchall(seg: &[u8]) -> Option<CatchAll<'_>> {
1469 let inner = seg.strip_prefix(b"{*")?.strip_suffix(b"}")?;
1470 if inner.is_empty() {
1471 return Some(CatchAll::Anon);
1472 }
1473 inner
1474 .iter()
1475 .all(|&b| is_pattern_name_byte(b))
1476 .then_some(CatchAll::Named(inner))
1477}
1478
1479fn parse_segment(
1487 seg: &[u8],
1488 name_bytes: &mut Vec<u8>,
1489 capture_free: &mut bool,
1490) -> SmallVec<[Element; 2]> {
1491 let mut elements: SmallVec<[Element; 2]> = SmallVec::new();
1492 let mut literal: Vec<u8> = Vec::new();
1493 let mut i = 0;
1494
1495 macro_rules! flush_literal {
1497 () => {
1498 if !literal.is_empty() {
1499 elements.push(Element {
1500 kind: ElementKind::Literal(core::mem::take(&mut literal).into_boxed_slice()),
1501 optional: false,
1502 });
1503 }
1504 };
1505 }
1506
1507 while i < seg.len() {
1508 match seg[i] {
1509 b'{' => {
1510 if let Some((kind, next)) = parse_brace(seg, i, name_bytes, capture_free) {
1513 flush_literal!();
1514 elements.push(Element {
1515 kind,
1516 optional: false,
1517 });
1518 i = next;
1519 } else {
1520 literal.push(b'{');
1521 i += 1;
1522 }
1523 }
1524 b'?' => {
1525 if let Some(last) = literal.pop() {
1527 flush_literal!();
1531 elements.push(Element {
1532 kind: ElementKind::Literal(Box::from([last])),
1533 optional: true,
1534 });
1535 } else if let Some(last) = elements.last_mut() {
1536 last.optional = true;
1537 } else {
1538 literal.push(b'?');
1540 }
1541 i += 1;
1542 }
1543 other => {
1544 literal.push(other);
1545 i += 1;
1546 }
1547 }
1548 }
1549 flush_literal!();
1550 elements
1551}
1552
1553fn parse_brace(
1560 seg: &[u8],
1561 open: usize,
1562 name_bytes: &mut Vec<u8>,
1563 capture_free: &mut bool,
1564) -> Option<(ElementKind, usize)> {
1565 let close = open + 1 + seg[open + 1..].iter().position(|&b| b == b'}')?;
1566 let inner = &seg[open + 1..close];
1567 let next = close + 1;
1568 if inner.is_empty() {
1569 return Some((ElementKind::Star, next));
1570 }
1571 if !inner.iter().all(|&b| is_pattern_name_byte(b)) {
1572 return None;
1573 }
1574 let name_start = name_bytes.len();
1575 name_bytes.extend_from_slice(inner);
1576 *capture_free = false;
1577 Some((
1578 ElementKind::Capture {
1579 name_start,
1580 name_len: inner.len(),
1581 },
1582 next,
1583 ))
1584}
1585
1586struct BitGrid {
1606 words: Box<[u64]>,
1607 cols: usize,
1608}
1609
1610impl BitGrid {
1611 fn new(rows: usize, cols: usize) -> Self {
1612 let words = vec![0u64; (rows * cols).div_ceil(64)].into_boxed_slice();
1613 Self { words, cols }
1614 }
1615
1616 #[inline]
1617 fn bit(&self, row: usize, col: usize) -> (usize, u64) {
1618 let idx = row * self.cols + col;
1619 (idx >> 6, 1u64 << (idx & 63))
1620 }
1621
1622 #[inline]
1623 fn get(&self, row: usize, col: usize) -> bool {
1624 let (word, mask) = self.bit(row, col);
1625 self.words[word] & mask != 0
1626 }
1627
1628 #[inline]
1629 fn set(&mut self, row: usize, col: usize) {
1630 let (word, mask) = self.bit(row, col);
1631 self.words[word] |= mask;
1632 }
1633}
1634
1635enum SeqMemo {
1639 None,
1640 Grid {
1641 grid: BitGrid,
1642 base_pats: usize,
1643 base_segs: usize,
1644 },
1645}
1646
1647impl SeqMemo {
1648 fn new(pats: &[PatternSegment], n_segs: usize) -> Self {
1649 let catch_alls = pats
1650 .iter()
1651 .filter(|p| {
1652 matches!(
1653 p,
1654 PatternSegment::CatchAll | PatternSegment::NamedCatchAll { .. }
1655 )
1656 })
1657 .count();
1658 if catch_alls >= 2 {
1659 Self::Grid {
1660 grid: BitGrid::new(pats.len() + 1, n_segs + 1),
1661 base_pats: pats.len(),
1662 base_segs: n_segs,
1663 }
1664 } else {
1665 Self::None
1666 }
1667 }
1668
1669 fn is_failed(&self, pats_left: usize, segs_left: usize) -> bool {
1673 match self {
1674 Self::None => false,
1675 Self::Grid {
1676 grid,
1677 base_pats,
1678 base_segs,
1679 } => grid.get(base_pats - pats_left, base_segs - segs_left),
1680 }
1681 }
1682
1683 fn mark_failed(&mut self, pats_left: usize, segs_left: usize) {
1684 if let Self::Grid {
1685 grid,
1686 base_pats,
1687 base_segs,
1688 } = self
1689 {
1690 grid.set(*base_pats - pats_left, *base_segs - segs_left);
1691 }
1692 }
1693}
1694
1695fn match_sequence<'p>(
1700 pats: &[PatternSegment],
1701 segs: &[EncodedSegment<'p>],
1702 opts: PathMatchOptions,
1703 sink: &mut Sink<'_, 'p>,
1704 memo: &mut SeqMemo,
1705 prefix: bool,
1706) -> bool {
1707 if memo.is_failed(pats.len(), segs.len()) {
1708 return false;
1709 }
1710
1711 let matched = match pats.split_first() {
1712 None => prefix || segs.is_empty(),
1713 Some((PatternSegment::CatchAll, rest)) => {
1714 match_catch_all(None, rest, segs, opts, sink, memo, prefix)
1715 }
1716 Some((
1717 PatternSegment::NamedCatchAll {
1718 name_start,
1719 name_len,
1720 },
1721 rest,
1722 )) => match_catch_all(
1723 Some((*name_start, *name_len)),
1724 rest,
1725 segs,
1726 opts,
1727 sink,
1728 memo,
1729 prefix,
1730 ),
1731 Some((PatternSegment::Normal { elems, ambiguity }, rest)) => {
1732 let mark = sink.len();
1733 if let Some((seg, segs_rest)) = segs.split_first()
1734 && match_segment(elems, *ambiguity, seg.as_ref().as_bytes(), opts, sink)
1735 && match_sequence(rest, segs_rest, opts, sink, memo, prefix)
1736 {
1737 return true;
1738 }
1739 sink.truncate(mark);
1740
1741 if let Some(binding) = optional_whole_segment_binding(elems) {
1742 let mark = sink.len();
1743 if let OptionalWholeSegment::Capture {
1744 name_start,
1745 name_len,
1746 } = binding
1747 {
1748 sink.insert_at(
1749 mark,
1750 Binding {
1751 name_start,
1752 name_len,
1753 value: Cow::Borrowed(""),
1754 is_glob: false,
1755 },
1756 );
1757 }
1758 if match_sequence(rest, segs, opts, sink, memo, prefix) {
1759 return true;
1760 }
1761 sink.truncate(mark);
1762 }
1763 false
1764 }
1765 };
1766
1767 if !matched {
1768 memo.mark_failed(pats.len(), segs.len());
1769 }
1770 matched
1771}
1772
1773#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1777enum OptionalWholeSegment {
1778 Anonymous,
1779 Capture { name_start: usize, name_len: usize },
1780}
1781
1782fn optional_whole_segment_binding(elems: &[Element]) -> Option<OptionalWholeSegment> {
1783 let [
1784 Element {
1785 kind,
1786 optional: true,
1787 },
1788 ] = elems
1789 else {
1790 return None;
1791 };
1792 match kind {
1793 ElementKind::Star => Some(OptionalWholeSegment::Anonymous),
1794 ElementKind::Capture {
1795 name_start,
1796 name_len,
1797 } => Some(OptionalWholeSegment::Capture {
1798 name_start: *name_start,
1799 name_len: *name_len,
1800 }),
1801 ElementKind::Literal(_) => None,
1802 }
1803}
1804
1805fn match_catch_all<'p>(
1810 name: Option<(usize, usize)>,
1811 rest: &[PatternSegment],
1812 segs: &[EncodedSegment<'p>],
1813 opts: PathMatchOptions,
1814 sink: &mut Sink<'_, 'p>,
1815 memo: &mut SeqMemo,
1816 prefix: bool,
1817) -> bool {
1818 for take in 1..=segs.len() {
1819 let mark = sink.len();
1820 if match_sequence(rest, &segs[take..], opts, sink, memo, prefix) {
1821 let value = join_decoded(&segs[..take], opts.percent_decode);
1823 let (name_start, name_len, is_glob) = match name {
1824 Some((start, len)) => (start, len, false),
1825 None => (0, 0, true),
1826 };
1827 sink.insert_at(
1828 mark,
1829 Binding {
1830 name_start,
1831 name_len,
1832 value,
1833 is_glob,
1834 },
1835 );
1836 return true;
1837 }
1838 sink.truncate(mark);
1839 }
1840 false
1841}
1842
1843fn match_segment<'p>(
1850 elems: &[Element],
1851 ambiguity: usize,
1852 raw_seg: &[u8],
1853 opts: PathMatchOptions,
1854 sink: &mut Sink<'_, 'p>,
1855) -> bool {
1856 let decoded = maybe_decode(raw_seg, opts.percent_decode);
1857 if ambiguity >= 2 {
1858 let mut memo = ElemMemo::new(elems.len(), decoded.len());
1859 match_elems(elems, &decoded, opts, sink, &mut Some(&mut memo))
1860 } else {
1861 match_elems(elems, &decoded, opts, sink, &mut None)
1862 }
1863}
1864
1865struct ElemMemo {
1868 grid: BitGrid,
1869 base_elems: usize,
1870 base_hay: usize,
1871}
1872
1873impl ElemMemo {
1874 fn new(n_elems: usize, n_hay: usize) -> Self {
1875 Self {
1876 grid: BitGrid::new(n_elems + 1, n_hay + 1),
1877 base_elems: n_elems,
1878 base_hay: n_hay,
1879 }
1880 }
1881 fn is_failed(&self, elems_left: usize, hay_left: usize) -> bool {
1882 self.grid
1883 .get(self.base_elems - elems_left, self.base_hay - hay_left)
1884 }
1885 fn mark_failed(&mut self, elems_left: usize, hay_left: usize) {
1886 self.grid
1887 .set(self.base_elems - elems_left, self.base_hay - hay_left);
1888 }
1889}
1890
1891fn match_elems<'p>(
1895 elems: &[Element],
1896 hay: &[u8],
1897 opts: PathMatchOptions,
1898 sink: &mut Sink<'_, 'p>,
1899 memo: &mut Option<&mut ElemMemo>,
1900) -> bool {
1901 if let Some(m) = memo
1902 && m.is_failed(elems.len(), hay.len())
1903 {
1904 return false;
1905 }
1906
1907 let matched = match elems.split_first() {
1908 None => hay.is_empty(),
1909 Some((el, rest)) => match &el.kind {
1910 ElementKind::Literal(lit) => {
1911 (byte_starts_with(hay, lit, opts.ignore_ascii_case)
1912 && match_elems(rest, &hay[lit.len()..], opts, sink, memo))
1913 || (el.optional && match_elems(rest, hay, opts, sink, memo))
1915 }
1916 ElementKind::Star => {
1917 match_run(None, rest, hay, opts, sink, memo)
1918 || (el.optional && match_elems(rest, hay, opts, sink, memo))
1919 }
1920 ElementKind::Capture {
1921 name_start,
1922 name_len,
1923 } => {
1924 match_run(Some((*name_start, *name_len)), rest, hay, opts, sink, memo)
1925 || (el.optional
1926 && match_empty_capture(
1927 (*name_start, *name_len),
1928 rest,
1929 hay,
1930 opts,
1931 sink,
1932 memo,
1933 ))
1934 }
1935 },
1936 };
1937
1938 if !matched && let Some(m) = memo {
1939 m.mark_failed(elems.len(), hay.len());
1940 }
1941 matched
1942}
1943
1944fn match_run<'p>(
1948 name: Option<(usize, usize)>,
1949 rest: &[Element],
1950 hay: &[u8],
1951 opts: PathMatchOptions,
1952 sink: &mut Sink<'_, 'p>,
1953 memo: &mut Option<&mut ElemMemo>,
1954) -> bool {
1955 for take in (1..=hay.len()).rev() {
1957 let mark = sink.len();
1958 if match_elems(rest, &hay[take..], opts, sink, memo) {
1959 if let Some((name_start, name_len)) = name {
1960 let value = decoded_owned(&hay[..take]);
1962 sink.insert_at(
1964 mark,
1965 Binding {
1966 name_start,
1967 name_len,
1968 value,
1969 is_glob: false,
1970 },
1971 );
1972 }
1973 return true;
1974 }
1975 sink.truncate(mark);
1976 }
1977 false
1978}
1979
1980fn match_empty_capture<'p>(
1983 name: (usize, usize),
1984 rest: &[Element],
1985 hay: &[u8],
1986 opts: PathMatchOptions,
1987 sink: &mut Sink<'_, 'p>,
1988 memo: &mut Option<&mut ElemMemo>,
1989) -> bool {
1990 let mark = sink.len();
1991 if match_elems(rest, hay, opts, sink, memo) {
1992 sink.insert_at(
1993 mark,
1994 Binding {
1995 name_start: name.0,
1996 name_len: name.1,
1997 value: Cow::Borrowed(""),
1998 is_glob: false,
1999 },
2000 );
2001 true
2002 } else {
2003 sink.truncate(mark);
2004 false
2005 }
2006}
2007
2008fn join_decoded<'p>(segs: &[EncodedSegment<'p>], decode: bool) -> Cow<'p, str> {
2011 let cap = segs.iter().map(|s| s.len()).sum::<usize>() + segs.len();
2013 let mut out = String::with_capacity(cap);
2014 for (i, s) in segs.iter().enumerate() {
2015 if i > 0 {
2016 out.push('/');
2017 }
2018 out.push_str(&String::from_utf8_lossy(&maybe_decode(
2019 s.as_ref().as_bytes(),
2020 decode,
2021 )));
2022 }
2023 Cow::Owned(out)
2024}
2025
2026fn decoded_owned<'p>(bytes: &[u8]) -> Cow<'p, str> {
2029 Cow::Owned(String::from_utf8_lossy(bytes).into_owned())
2030}