Skip to main content

moq_pattern/
pattern.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::str::FromStr;
4
5use super::Patterns;
6
7/// Why an exact pattern intersection could not be represented safely.
8#[derive(Clone, Debug, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum IntersectionError {
11	/// The exact intersection would contain too many distinct patterns.
12	TooManyPatterns,
13}
14
15impl fmt::Display for IntersectionError {
16	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17		match self {
18			Self::TooManyPatterns => write!(f, "pattern intersection exceeds the complexity limit"),
19		}
20	}
21}
22
23impl std::error::Error for IntersectionError {}
24
25/// Why a string or a segment list is not a valid [`Pattern`].
26#[derive(Clone, Debug, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum InvalidPattern {
29	/// A segment is empty: a leading, trailing, or doubled `/`.
30	EmptySegment,
31	/// A segment is malformed: a literal, prefix, or suffix contains `/` or `*`, a
32	/// partial has neither prefix nor suffix (that is a wildcard), or a segment has
33	/// more than one `*`, which is reserved.
34	InvalidSegment(String),
35	/// More than one `**`.
36	MultipleGlobstars,
37	/// More than [`Pattern::MAX_SEGMENTS`] segments.
38	TooManySegments,
39}
40
41impl fmt::Display for InvalidPattern {
42	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43		match self {
44			Self::EmptySegment => write!(f, "empty path segment"),
45			Self::InvalidSegment(segment) => write!(f, "invalid pattern segment: {segment:?}"),
46			Self::MultipleGlobstars => write!(f, "more than one ** segment"),
47			Self::TooManySegments => write!(f, "more than {} segments", Pattern::MAX_SEGMENTS),
48		}
49	}
50}
51
52impl std::error::Error for InvalidPattern {}
53
54/// One segment of a [`Pattern`].
55#[derive(Clone, Debug, PartialEq, Eq, Hash)]
56#[non_exhaustive]
57pub enum Segment {
58	/// Matches exactly this segment. Never empty, and never contains `/` or `*`.
59	Literal(String),
60	/// `*`: matches any one segment.
61	Wildcard,
62	/// `prefix*suffix`: matches any one segment that starts with `prefix` and ends
63	/// with `suffix`, without the two overlapping. Either may be empty, not both.
64	Partial {
65		/// What the segment must start with; may be empty.
66		prefix: String,
67		/// What the segment must end with; may be empty.
68		suffix: String,
69	},
70	/// `**`: matches any run of zero or more segments. At most one per pattern.
71	Globstar,
72}
73
74impl Segment {
75	/// Parse one segment of a pattern's text.
76	fn parse(text: &str) -> Result<Self, InvalidPattern> {
77		match text {
78			"" => Err(InvalidPattern::EmptySegment),
79			"*" => Ok(Self::Wildcard),
80			"**" => Ok(Self::Globstar),
81			_ if text.contains('/') => Err(InvalidPattern::InvalidSegment(text.to_string())),
82			_ => match text.split_once('*') {
83				None => Ok(Self::Literal(text.to_string())),
84				Some((prefix, suffix)) if !suffix.contains('*') => Ok(Self::Partial {
85					prefix: prefix.to_string(),
86					suffix: suffix.to_string(),
87				}),
88				// More than one star in a segment is reserved.
89				Some(_) => Err(InvalidPattern::InvalidSegment(text.to_string())),
90			},
91		}
92	}
93
94	/// Whether every segment this one matches, `other` matches too.
95	///
96	/// `**` is excluded: it spans segments, so containment handles it structurally.
97	fn covers(&self, other: &Self) -> bool {
98		match (self, other) {
99			(Self::Wildcard, Self::Literal(_) | Self::Partial { .. } | Self::Wildcard) => true,
100			(Self::Literal(a), Self::Literal(b)) => a == b,
101			(Self::Partial { .. }, Self::Literal(literal)) => self.matches(literal),
102			// `p*s` covers `p'*s'` exactly when `p` starts `p'` and `s` ends `s'`: the
103			// middle is free on both sides, so nothing else can constrain it.
104			(
105				Self::Partial { prefix, suffix },
106				Self::Partial {
107					prefix: other_prefix,
108					suffix: other_suffix,
109				},
110			) => other_prefix.starts_with(prefix.as_str()) && other_suffix.ends_with(suffix.as_str()),
111			_ => false,
112		}
113	}
114
115	/// Whether some path segment matches both. Same exclusion as [`covers`](Self::covers).
116	fn compatible(&self, other: &Self) -> bool {
117		match (self, other) {
118			// Two partials meet when one prefix starts the other and one suffix ends
119			// the other: the longer prefix followed by the longer suffix matches both.
120			(
121				Self::Partial { prefix, suffix },
122				Self::Partial {
123					prefix: other_prefix,
124					suffix: other_suffix,
125				},
126			) => {
127				(prefix.starts_with(other_prefix.as_str()) || other_prefix.starts_with(prefix.as_str()))
128					&& (suffix.ends_with(other_suffix.as_str()) || other_suffix.ends_with(suffix.as_str()))
129			}
130			_ => self.covers(other) || other.covers(self),
131		}
132	}
133
134	/// Whether this segment matches one path segment.
135	fn matches(&self, part: &str) -> bool {
136		match self {
137			Self::Literal(literal) => literal == part,
138			Self::Wildcard => true,
139			Self::Partial { prefix, suffix } => {
140				part.len() >= prefix.len() + suffix.len()
141					&& part.starts_with(prefix.as_str())
142					&& part.ends_with(suffix.as_str())
143			}
144			Self::Globstar => false,
145		}
146	}
147
148	/// The segments matching exactly the parts both match. Same exclusion as
149	/// [`covers`](Self::covers). Empty when the two are incompatible.
150	fn intersect(&self, other: &Self) -> Vec<Self> {
151		match (self, other) {
152			(Self::Globstar, _) | (_, Self::Globstar) => Vec::new(),
153			(Self::Wildcard, other) => vec![other.clone()],
154			(this, Self::Wildcard) => vec![this.clone()],
155			(Self::Literal(a), Self::Literal(b)) => (a == b).then(|| self.clone()).into_iter().collect(),
156			(Self::Literal(literal), partial @ Self::Partial { .. })
157			| (partial @ Self::Partial { .. }, Self::Literal(literal)) => partial
158				.matches(literal)
159				.then(|| Self::Literal(literal.clone()))
160				.into_iter()
161				.collect(),
162			(
163				Self::Partial { prefix, suffix },
164				Self::Partial {
165					prefix: other_prefix,
166					suffix: other_suffix,
167				},
168			) => {
169				if !self.compatible(other) {
170					return Vec::new();
171				}
172				// The longer prefix and the longer suffix pin every part long enough to
173				// hold both without overlapping. Shorter parts exist too, where the two
174				// runs share bytes: those are finitely many literals.
175				let prefix = if prefix.len() >= other_prefix.len() {
176					prefix
177				} else {
178					other_prefix
179				};
180				let suffix = if suffix.len() >= other_suffix.len() {
181					suffix
182				} else {
183					other_suffix
184				};
185				let mut out = vec![Self::Partial {
186					prefix: prefix.clone(),
187					suffix: suffix.clone(),
188				}];
189				for overlap in 1..=prefix.len().min(suffix.len()) {
190					if !prefix.is_char_boundary(prefix.len() - overlap) || !suffix.is_char_boundary(overlap) {
191						continue;
192					}
193					if prefix[prefix.len() - overlap..] != suffix[..overlap] {
194						continue;
195					}
196					let part = format!("{prefix}{}", &suffix[overlap..]);
197					if self.matches(&part) && other.matches(&part) {
198						out.push(Self::Literal(part));
199					}
200				}
201				out
202			}
203		}
204	}
205}
206
207/// Every segment-wise intersection of two runs of the same length: the cartesian
208/// product of [`Segment::intersect`] per position. Empty when any position is
209/// incompatible.
210fn intersect_run(a: &[Segment], b: &[Segment], limit: usize) -> Result<Vec<Vec<Segment>>, IntersectionError> {
211	debug_assert_eq!(a.len(), b.len());
212	let mut out: Vec<Vec<Segment>> = vec![Vec::with_capacity(a.len())];
213	for (a, b) in a.iter().zip(b) {
214		let choices = a.intersect(b);
215		if choices.is_empty() {
216			return Ok(Vec::new());
217		}
218		if out.len().checked_mul(choices.len()).is_none_or(|size| size > limit) {
219			return Err(IntersectionError::TooManyPatterns);
220		}
221		out = out
222			.iter()
223			.flat_map(|prefix| {
224				choices.iter().map(move |choice| {
225					let mut next = prefix.clone();
226					next.push(choice.clone());
227					next
228				})
229			})
230			.collect();
231	}
232	Ok(out)
233}
234
235fn insert_intersection(
236	out: &mut Patterns,
237	remaining: &mut usize,
238	segments: Vec<Segment>,
239) -> Result<(), IntersectionError> {
240	if *remaining == 0 {
241		return Err(IntersectionError::TooManyPatterns);
242	}
243	*remaining -= 1;
244	if let Ok(pattern) = Pattern::new(segments) {
245		out.insert(pattern);
246	}
247	Ok(())
248}
249
250fn intersect_into(
251	a: &[Segment],
252	b: &[Segment],
253	out: &mut Patterns,
254	remaining: &mut usize,
255) -> Result<(), IntersectionError> {
256	for run in intersect_run(a, b, *remaining)? {
257		insert_intersection(out, remaining, run)?;
258	}
259	Ok(())
260}
261
262/// `head`, then `**` stretched to `len` segments as `*`, then `tail`.
263fn expand(head: &[Segment], tail: &[Segment], len: usize) -> Vec<Segment> {
264	let mut out = head.to_vec();
265	out.extend(std::iter::repeat_n(Segment::Wildcard, len - head.len() - tail.len()));
266	out.extend_from_slice(tail);
267	out
268}
269
270impl fmt::Display for Segment {
271	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272		match self {
273			Self::Literal(literal) => f.write_str(literal),
274			Self::Wildcard => f.write_str("*"),
275			Self::Partial { prefix, suffix } => write!(f, "{prefix}*{suffix}"),
276			Self::Globstar => f.write_str("**"),
277		}
278	}
279}
280
281/// How much of a path a pattern pins down, for ranking the patterns that match one path.
282///
283/// Greater is more specific. The order is total and agrees with containment: when `a`
284/// matches a strict superset of `b`'s paths, `a.specificity() < b.specificity()`. Patterns
285/// that compare equal without being equal (`*/a` and `*/b`) form one tier; what breaks
286/// that tie is the caller's business.
287///
288/// Compared in order: literal segments (more wins), then no `**` beats `**`, then
289/// partial segments (more wins), then `*` segments (more wins), then the bytes the
290/// partials pin (more wins), then the length of the literal head.
291#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
292pub struct Specificity {
293	literals: usize,
294	exact: bool,
295	partials: usize,
296	wildcards: usize,
297	pinned: usize,
298	head: usize,
299}
300
301/// A pattern over broadcast paths: literal segments, `*` for one segment, `prefix*suffix`
302/// for one segment with a known start and end, and at most one `**` for any run of
303/// segments. Every segment kind matches whole segments, and a pattern is exact: `foo`
304/// matches only `foo`, and a subtree is `foo/**`.
305///
306/// Build one with [`FromStr`] (`"a/*/**".parse()`), [`new`](Self::new) from segments,
307/// or [`literal`](Self::literal) and [`subtree`](Self::subtree) from a path. Equality and
308/// ordering are by text, which is canonical: two patterns match the same paths when
309/// they are equal, and only then. Construction moves `**` before adjacent `*` segments.
310#[derive(Clone, PartialEq, Eq, Hash)]
311pub struct Pattern {
312	text: String,
313	segments: Vec<Segment>,
314	// Index of the `**` segment, if any.
315	globstar: Option<usize>,
316	// Byte length of the literal head within `text`.
317	head: usize,
318}
319
320impl Pattern {
321	/// The most segments a pattern may have, matching the path limit on the wire.
322	pub const MAX_SEGMENTS: usize = 32;
323	/// The most patterns one exact intersection may produce.
324	pub const MAX_INTERSECTIONS: usize = 1024;
325
326	/// A pattern from its segments, validating the grammar and moving `**` before adjacent `*` segments.
327	pub fn new(segments: impl IntoIterator<Item = Segment>) -> Result<Self, InvalidPattern> {
328		let mut segments: Vec<Segment> = segments.into_iter().collect();
329		if segments.len() > Self::MAX_SEGMENTS {
330			return Err(InvalidPattern::TooManySegments);
331		}
332
333		let mut globstar = None;
334		for (i, segment) in segments.iter().enumerate() {
335			match segment {
336				Segment::Literal(literal) if literal.is_empty() => return Err(InvalidPattern::EmptySegment),
337				Segment::Literal(literal) if literal.contains(['*', '/']) => {
338					return Err(InvalidPattern::InvalidSegment(literal.clone()));
339				}
340				Segment::Partial { prefix, suffix }
341					if (prefix.is_empty() && suffix.is_empty())
342						|| prefix.contains(['*', '/'])
343						|| suffix.contains(['*', '/']) =>
344				{
345					return Err(InvalidPattern::InvalidSegment(format!("{prefix}*{suffix}")));
346				}
347				Segment::Globstar if globstar.is_some() => return Err(InvalidPattern::MultipleGlobstars),
348				Segment::Globstar => globstar = Some(i),
349				_ => {}
350			}
351		}
352
353		// Adjacent `*` and `**` commute; keep `**` first for one language identity.
354		if let Some(mut index) = globstar {
355			while index > 0 && segments[index - 1] == Segment::Wildcard {
356				segments.swap(index - 1, index);
357				index -= 1;
358			}
359			globstar = Some(index);
360		}
361
362		let mut text = String::new();
363		let mut head = 0;
364		let mut in_head = true;
365		for (i, segment) in segments.iter().enumerate() {
366			if i > 0 {
367				text.push('/');
368			}
369			match segment {
370				Segment::Literal(literal) => text.push_str(literal),
371				other => {
372					in_head = false;
373					text.push_str(&other.to_string());
374				}
375			}
376			if in_head {
377				head = text.len();
378			}
379		}
380
381		Ok(Self {
382			text,
383			segments,
384			globstar,
385			head,
386		})
387	}
388
389	/// The pattern matching every path: `**`.
390	pub fn all() -> Self {
391		Self::new([Segment::Globstar]).expect("** is valid")
392	}
393
394	/// The pattern matching exactly `path`.
395	///
396	/// The path is normalized like a broadcast path (slashes trimmed and collapsed), so
397	/// `/foo//bar/` is `foo/bar`. Fails when a segment is `*` or `**`, or contains `*`:
398	/// those are wildcards, and a path using them cannot be named by a pattern.
399	pub fn literal(path: &str) -> Result<Self, InvalidPattern> {
400		Self::new(literal_segments(path))
401	}
402
403	/// The pattern matching `path` and every path beneath it: `path/**`.
404	///
405	/// Normalizes and validates `path` like [`literal`](Self::literal). The empty path
406	/// yields `**`.
407	pub fn subtree(path: &str) -> Result<Self, InvalidPattern> {
408		Self::new(literal_segments(path).chain([Segment::Globstar]))
409	}
410
411	/// The canonical text: segments joined by `/`, wildcards as `*` and `**`.
412	pub fn as_str(&self) -> &str {
413		&self.text
414	}
415
416	/// The segments, in order.
417	pub fn segments(&self) -> &[Segment] {
418		&self.segments
419	}
420
421	/// The literal segments before the first wildcard, as a path.
422	///
423	/// Every matching path starts with it, so it is where a tree walk starts. Empty
424	/// when the pattern starts with a wildcard; the whole pattern when it has none.
425	pub fn head(&self) -> &str {
426		&self.text[..self.head]
427	}
428
429	/// Whether the pattern has no wildcards, so it matches exactly one path.
430	pub fn is_literal(&self) -> bool {
431		self.head == self.text.len()
432	}
433
434	/// The covered prefix if this pattern is prefix-shaped: zero or more literals then `**`.
435	///
436	/// `**` covers every path (the empty prefix). `foo/**` covers `foo` and everything
437	/// beneath it. A literal, a `*`, or a `**` that is not last is `None`.
438	pub fn as_prefix(&self) -> Option<&str> {
439		match self.segments.split_last() {
440			Some((Segment::Globstar, head)) if head.iter().all(|s| matches!(s, Segment::Literal(_))) => {
441				Some(self.head())
442			}
443			_ => None,
444		}
445	}
446
447	/// Whether the pattern has a `**`, so it matches paths of more than one length.
448	pub fn has_globstar(&self) -> bool {
449		self.globstar.is_some()
450	}
451
452	/// Whether `path` is in the set this pattern describes.
453	///
454	/// The path is normalized like a broadcast path: slashes are trimmed and collapsed.
455	pub fn matches(&self, path: &str) -> bool {
456		let parts: Vec<&str> = split_path(path).collect();
457		match self.globstar {
458			None => {
459				parts.len() == self.segments.len()
460					&& self
461						.segments
462						.iter()
463						.zip(&parts)
464						.all(|(segment, part)| segment.matches(part))
465			}
466			Some(_) => {
467				let (head, tail) = self.split();
468				parts.len() >= head.len() + tail.len()
469					&& head.iter().zip(&parts).all(|(segment, part)| segment.matches(part))
470					&& tail
471						.iter()
472						.rev()
473						.zip(parts.iter().rev())
474						.all(|(segment, part)| segment.matches(part))
475			}
476		}
477	}
478
479	/// Whether every path `other` matches, this pattern matches too.
480	///
481	/// This is the authorization check: a grant contains a request when the request
482	/// cannot name a path outside it. A pattern contains itself.
483	pub fn contains(&self, other: &Self) -> bool {
484		match (self.globstar, other.globstar) {
485			(None, None) => {
486				self.segments.len() == other.segments.len()
487					&& self.segments.iter().zip(&other.segments).all(|(a, b)| a.covers(b))
488			}
489			// A fixed-length pattern cannot contain one that matches many lengths.
490			(None, Some(_)) => false,
491			(Some(_), None) => {
492				let (head, tail) = self.split();
493				other.segments.len() >= head.len() + tail.len()
494					&& head.iter().zip(&other.segments).all(|(a, b)| a.covers(b))
495					&& tail
496						.iter()
497						.rev()
498						.zip(other.segments.iter().rev())
499						.all(|(a, b)| a.covers(b))
500			}
501			(Some(_), Some(_)) => {
502				let (head, tail) = self.split();
503				let (other_head, other_tail) = other.split();
504
505				// The other's `**` can be arbitrarily long, so any of our segments that
506				// reach past the other's head or tail must be `*`; and the other's
507				// shortest path (its `**` empty) must still be long enough for ours.
508				let covers_run = |ours: &[Segment], theirs: &[Segment]| {
509					ours.iter().enumerate().all(|(i, a)| match theirs.get(i) {
510						Some(b) => a.covers(b),
511						None => *a == Segment::Wildcard,
512					})
513				};
514				let reversed = |run: &[Segment]| run.iter().rev().cloned().collect::<Vec<_>>();
515
516				head.len() + tail.len() <= other_head.len() + other_tail.len()
517					&& covers_run(head, other_head)
518					&& covers_run(&reversed(tail), &reversed(other_tail))
519			}
520		}
521	}
522
523	/// Whether some path matches both patterns.
524	pub fn overlaps(&self, other: &Self) -> bool {
525		let compatible_run = |a: &[Segment], b: &[Segment]| a.iter().zip(b).all(|(a, b)| a.compatible(b));
526		let compatible_tail =
527			|a: &[Segment], b: &[Segment]| a.iter().rev().zip(b.iter().rev()).all(|(a, b)| a.compatible(b));
528
529		match (self.globstar, other.globstar) {
530			(None, None) => {
531				self.segments.len() == other.segments.len() && compatible_run(&self.segments, &other.segments)
532			}
533			(None, Some(_)) => other.overlaps(self),
534			(Some(_), None) => {
535				let (head, tail) = self.split();
536				other.segments.len() >= head.len() + tail.len()
537					&& compatible_run(head, &other.segments)
538					&& compatible_tail(tail, &other.segments)
539			}
540			(Some(_), Some(_)) => {
541				// A path long enough keeps the heads and tails apart, so the only
542				// constraints are segment-wise where the heads and tails overlap.
543				let (head, tail) = self.split();
544				let (other_head, other_tail) = other.split();
545				compatible_run(head, other_head) && compatible_tail(tail, other_tail)
546			}
547		}
548	}
549
550	/// How much of a path this pattern pins down. See [`Specificity`].
551	pub fn specificity(&self) -> Specificity {
552		let count = |wanted: fn(&Segment) -> bool| self.segments.iter().filter(|s| wanted(s)).count();
553		Specificity {
554			literals: count(|s| matches!(s, Segment::Literal(_))),
555			exact: self.globstar.is_none(),
556			partials: count(|s| matches!(s, Segment::Partial { .. })),
557			wildcards: count(|s| matches!(s, Segment::Wildcard)),
558			pinned: self
559				.segments
560				.iter()
561				.map(|s| match s {
562					Segment::Partial { prefix, suffix } => prefix.len() + suffix.len(),
563					_ => 0,
564				})
565				.sum(),
566			head: self
567				.segments
568				.iter()
569				.take_while(|s| matches!(s, Segment::Literal(_)))
570				.count(),
571		}
572	}
573
574	/// The patterns that, relative to `root`, match exactly the paths this pattern
575	/// matches beneath `root`.
576	///
577	/// This is how a grant or an advertisement is presented inside a rooted view. It is
578	/// a set because `**` may consume the root or stop short of it: `**/a` rebased at `a`
579	/// is both the empty pattern (the root itself) and `**/a` (deeper paths ending in
580	/// `a`). Empty when nothing under `root` matches. The root is normalized like a
581	/// broadcast path.
582	pub fn rebase(&self, root: &str) -> Patterns {
583		let root: Vec<&str> = split_path(root).collect();
584		let mut out = Patterns::new();
585
586		let matches_run = |segments: &[Segment], parts: &[&str]| segments.iter().zip(parts).all(|(s, p)| s.matches(p));
587		// Construction cannot fail: the segments come from a valid pattern, and a
588		// rebase never lengthens it.
589		let build = |segments: &[Segment]| Pattern::new(segments.to_vec()).expect("a rebased pattern is valid");
590
591		match self.globstar {
592			None => {
593				if root.len() <= self.segments.len() && matches_run(&self.segments, &root) {
594					out.insert(build(&self.segments[root.len()..]));
595				}
596			}
597			Some(index) => {
598				let (head, tail) = self.split();
599				if root.len() <= head.len() {
600					if matches_run(head, &root) {
601						out.insert(build(&self.segments[root.len()..]));
602					}
603					return out;
604				}
605				if !matches_run(head, &root) {
606					return out;
607				}
608
609				// The root reaches into the `**`. Either the `**` swallows the rest of the
610				// root and stays open, or it closed inside the root and some of the tail
611				// already matched the root's last segments.
612				let rest = &root[head.len()..];
613				out.insert(build(&self.segments[index..]));
614				for consumed in 1..=tail.len().min(rest.len()) {
615					if matches_run(&tail[..consumed], &rest[rest.len() - consumed..]) {
616						out.insert(build(&tail[consumed..]));
617					}
618				}
619			}
620		}
621
622		out
623	}
624
625	/// The patterns matching exactly the paths both patterns match.
626	///
627	/// This is how a claim is clamped to a scope: the covered paths inside the
628	/// grant, as patterns of their own. It is a set because two partial segments or
629	/// two `**` runs can meet in more than one way: `ab*` and `*b` meet at `ab*b` and
630	/// at `ab`, and `a/**` and `**/a` meet at `a/**/a` and at `a`. Empty when the
631	/// two do not [overlap](Self::overlaps).
632	pub fn intersect(&self, other: &Self) -> Result<Patterns, IntersectionError> {
633		// The contained pattern is the intersection as written, where the general
634		// case below could only spell the same set in more pieces.
635		if self.contains(other) {
636			return Ok(Patterns::from(other.clone()));
637		}
638		if other.contains(self) {
639			return Ok(Patterns::from(self.clone()));
640		}
641
642		let mut out = Patterns::new();
643		let mut remaining = Self::MAX_INTERSECTIONS;
644
645		match (self.globstar, other.globstar) {
646			(None, None) => {
647				if self.segments.len() == other.segments.len() {
648					intersect_into(&self.segments, &other.segments, &mut out, &mut remaining)?;
649				}
650			}
651			(Some(_), None) => {
652				let (head, tail) = self.split();
653				if other.segments.len() >= head.len() + tail.len() {
654					let stretched = expand(head, tail, other.segments.len());
655					intersect_into(&stretched, &other.segments, &mut out, &mut remaining)?;
656				}
657			}
658			(None, Some(_)) => return other.intersect(self),
659			(Some(_), Some(_)) => {
660				let (head, tail) = self.split();
661				let (other_head, other_tail) = other.split();
662				let heads = head.len().max(other_head.len());
663				let tails = tail.len().max(other_tail.len());
664				let shortest = (head.len() + tail.len()).max(other_head.len() + other_tail.len());
665
666				// Paths too short to keep the longer head and the longer tail apart
667				// constrain both from each end at once: enumerate each length. When
668				// the open form below would not fit, every length that fits is short.
669				let long = heads + tails;
670				let open = long < Self::MAX_SEGMENTS;
671				let cap = if open { long } else { Self::MAX_SEGMENTS + 1 };
672				for len in shortest..cap {
673					let a = expand(head, tail, len);
674					let b = expand(other_head, other_tail, len);
675					intersect_into(&a, &b, &mut out, &mut remaining)?;
676				}
677
678				// Longer paths pin the heads and the tails independently and leave the
679				// run between them free.
680				if open {
681					let pad = |run: &[Segment], len: usize, front: bool| -> Vec<Segment> {
682						let fill = std::iter::repeat_n(Segment::Wildcard, len - run.len());
683						if front {
684							run.iter().cloned().chain(fill).collect()
685						} else {
686							fill.chain(run.iter().cloned()).collect()
687						}
688					};
689					let fronts = intersect_run(&pad(head, heads, true), &pad(other_head, heads, true), remaining)?;
690					let backs = intersect_run(&pad(tail, tails, false), &pad(other_tail, tails, false), remaining)?;
691					if fronts
692						.len()
693						.checked_mul(backs.len())
694						.is_none_or(|size| size > remaining)
695					{
696						return Err(IntersectionError::TooManyPatterns);
697					}
698					for front in &fronts {
699						for back in &backs {
700							let mut segments = front.clone();
701							segments.push(Segment::Globstar);
702							segments.extend_from_slice(back);
703							insert_intersection(&mut out, &mut remaining, segments)?;
704						}
705					}
706				}
707			}
708		}
709
710		Ok(out)
711	}
712
713	/// What each wildcard of this pattern stands for in `matched`, a pattern this
714	/// one [contains](Self::contains); `None` when it does not.
715	///
716	/// One capture per non-literal segment (`*`, `prefix*suffix`, `**`), in order,
717	/// the way a regex match exposes its groups: `foo/*/chat` against `foo/alice/chat`
718	/// captures `alice`, and `foo/**` against `foo/alice/chat` captures `alice/chat`.
719	/// A capture is a pattern because `matched` may be one: `foo/**` against
720	/// `foo/alice/**` captures `alice/**`. When `matched` has a `**` that this
721	/// pattern's own segments straddle (`**/*` against `a/**`, where the last
722	/// segment is `a` or anything after it), the segments it straddles cannot be
723	/// pinned and capture themselves: `**` then `*`.
724	pub fn captures(&self, matched: &Self) -> Option<Vec<Self>> {
725		if !self.contains(matched) {
726			return None;
727		}
728		// Construction cannot fail: every capture is a run of `matched`'s own valid
729		// segments or one of ours, never longer than either.
730		let build = |segments: &[Segment]| Pattern::new(segments.to_vec()).expect("a capture is valid");
731		let mut out = Vec::new();
732
733		if self.globstar.is_none() {
734			for (segment, theirs) in self.segments.iter().zip(&matched.segments) {
735				if !matches!(segment, Segment::Literal(_)) {
736					out.push(build(std::slice::from_ref(theirs)));
737				}
738			}
739			return Some(out);
740		}
741
742		let (head, tail) = self.split();
743		let middle = matched.segments.len() - tail.len();
744		// Our head aligns with `matched` from the front and our tail from the back.
745		// A segment of ours aligned at or beyond `matched`'s `**` (from its own
746		// side) has no fixed counterpart, so it captures itself.
747		let free = matched.globstar;
748		let pinned = |at: usize, from_front: bool| match free {
749			Some(free) if from_front => at < free,
750			Some(free) => at > free,
751			None => true,
752		};
753
754		for (i, segment) in head.iter().enumerate() {
755			if !matches!(segment, Segment::Literal(_)) {
756				let capture = if pinned(i, true) { &matched.segments[i] } else { segment };
757				out.push(build(std::slice::from_ref(capture)));
758			}
759		}
760		if free.is_none_or(|free| free >= head.len() && free < middle) {
761			out.push(build(&matched.segments[head.len()..middle]));
762		} else {
763			out.push(Pattern::all());
764		}
765		for (j, segment) in tail.iter().enumerate() {
766			if !matches!(segment, Segment::Literal(_)) {
767				let at = middle + j;
768				let capture = if pinned(at, false) {
769					&matched.segments[at]
770				} else {
771					segment
772				};
773				out.push(build(std::slice::from_ref(capture)));
774			}
775		}
776		Some(out)
777	}
778
779	/// This pattern placed beneath a literal `root`: the same paths, named from the
780	/// root's parent. The inverse of [`rebase`](Self::rebase) for a single pattern.
781	///
782	/// The root is normalized and validated like [`literal`](Self::literal), and the
783	/// result must fit [`MAX_SEGMENTS`](Self::MAX_SEGMENTS).
784	pub fn rooted(&self, root: &str) -> Result<Self, InvalidPattern> {
785		Self::new(literal_segments(root).chain(self.segments.iter().cloned()))
786	}
787
788	/// The segments before and after the `**`. Only meaningful when there is one.
789	fn split(&self) -> (&[Segment], &[Segment]) {
790		match self.globstar {
791			Some(index) => (&self.segments[..index], &self.segments[index + 1..]),
792			None => (&self.segments, &[]),
793		}
794	}
795}
796
797/// The non-empty segments of a path: leading, trailing, and doubled slashes are dropped,
798/// matching how a broadcast path is normalized.
799fn split_path(path: &str) -> impl Iterator<Item = &str> {
800	path.split('/').filter(|part| !part.is_empty())
801}
802
803/// The segments of a path as literals, leaving validation to [`Pattern::new`].
804fn literal_segments(path: &str) -> impl Iterator<Item = Segment> + '_ {
805	// A `*` or `**` segment becomes an invalid literal, which `new` rejects: a path
806	// is never read as a pattern.
807	split_path(path).map(|part| Segment::Literal(part.to_string()))
808}
809
810impl FromStr for Pattern {
811	type Err = InvalidPattern;
812
813	/// Parse a pattern's text. Unlike a path, slashes are not normalized: a leading,
814	/// trailing, or doubled `/` is an error, so a typo cannot silently widen a grant.
815	fn from_str(text: &str) -> Result<Self, InvalidPattern> {
816		if text.is_empty() {
817			return Self::new([]);
818		}
819		text.split('/')
820			.map(Segment::parse)
821			.collect::<Result<Vec<_>, _>>()
822			.and_then(Self::new)
823	}
824}
825
826impl TryFrom<&str> for Pattern {
827	type Error = InvalidPattern;
828
829	fn try_from(text: &str) -> Result<Self, InvalidPattern> {
830		text.parse()
831	}
832}
833
834impl TryFrom<String> for Pattern {
835	type Error = InvalidPattern;
836
837	fn try_from(text: String) -> Result<Self, InvalidPattern> {
838		text.parse()
839	}
840}
841
842impl Default for Pattern {
843	/// The empty pattern, which matches only the empty path.
844	fn default() -> Self {
845		Self::new([]).expect("the empty pattern is valid")
846	}
847}
848
849impl fmt::Display for Pattern {
850	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
851		f.write_str(&self.text)
852	}
853}
854
855impl fmt::Debug for Pattern {
856	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857		write!(f, "Pattern({:?})", self.text)
858	}
859}
860
861impl PartialOrd for Pattern {
862	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
863		Some(self.cmp(other))
864	}
865}
866
867impl Ord for Pattern {
868	/// Ordered by text, so a sorted list of patterns is deterministic.
869	fn cmp(&self, other: &Self) -> Ordering {
870		self.text.cmp(&other.text)
871	}
872}
873
874#[cfg(feature = "serde")]
875impl serde::Serialize for Pattern {
876	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
877		serializer.serialize_str(self.as_str())
878	}
879}
880
881#[cfg(feature = "serde")]
882impl<'de> serde::Deserialize<'de> for Pattern {
883	/// Reads the canonical text, so a persisted pattern is validated on the way in.
884	fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
885		let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
886		text.parse().map_err(serde::de::Error::custom)
887	}
888}
889
890impl AsRef<str> for Pattern {
891	fn as_ref(&self) -> &str {
892		&self.text
893	}
894}
895
896#[cfg(test)]
897mod tests {
898	use super::*;
899
900	fn pattern(text: &str) -> Pattern {
901		text.parse().unwrap_or_else(|err| panic!("{text:?}: {err}"))
902	}
903
904	#[test]
905	fn parses_and_prints_canonically() {
906		for text in [
907			"",
908			"a",
909			"a/b",
910			"*",
911			"**",
912			"a/*/b",
913			"**/transcode.pro",
914			"a/**/b/*",
915			"**/*",
916			"**/*.hang",
917			"foo*",
918			"foo.*.hang",
919		] {
920			assert_eq!(pattern(text).to_string(), text);
921		}
922		assert_eq!(
923			pattern("a/*/**/b").segments(),
924			&[
925				Segment::Literal("a".into()),
926				Segment::Globstar,
927				Segment::Wildcard,
928				Segment::Literal("b".into()),
929			]
930		);
931	}
932
933	#[test]
934	fn rejects_bad_syntax() {
935		assert_eq!("/a".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
936		assert_eq!("a/".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
937		assert_eq!("a//b".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
938		assert_eq!("/".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
939		assert_eq!("**/**".parse::<Pattern>(), Err(InvalidPattern::MultipleGlobstars));
940		assert_eq!(
941			"***".parse::<Pattern>(),
942			Err(InvalidPattern::InvalidSegment("***".into()))
943		);
944
945		assert_eq!(
946			"a*b*c".parse::<Pattern>(),
947			Err(InvalidPattern::InvalidSegment("a*b*c".into()))
948		);
949		assert_eq!(
950			"*a*".parse::<Pattern>(),
951			Err(InvalidPattern::InvalidSegment("*a*".into()))
952		);
953		assert_eq!(
954			"*.hang".parse::<Pattern>().unwrap().segments(),
955			&[Segment::Partial {
956				prefix: String::new(),
957				suffix: ".hang".into()
958			}]
959		);
960		assert_eq!(
961			Pattern::new([Segment::Partial {
962				prefix: String::new(),
963				suffix: String::new()
964			}]),
965			Err(InvalidPattern::InvalidSegment("*".into()))
966		);
967		assert_eq!(
968			Pattern::new([Segment::Partial {
969				prefix: "a/".into(),
970				suffix: String::new()
971			}]),
972			Err(InvalidPattern::InvalidSegment("a/*".into()))
973		);
974
975		let deep = ["a"; Pattern::MAX_SEGMENTS + 1].join("/");
976		assert_eq!(deep.parse::<Pattern>(), Err(InvalidPattern::TooManySegments));
977		let max = ["a"; Pattern::MAX_SEGMENTS].join("/");
978		assert!(max.parse::<Pattern>().is_ok());
979
980		assert_eq!(
981			Pattern::new([Segment::Literal("a/b".into())]),
982			Err(InvalidPattern::InvalidSegment("a/b".into()))
983		);
984		assert_eq!(
985			Pattern::new([Segment::Literal(String::new())]),
986			Err(InvalidPattern::EmptySegment)
987		);
988	}
989
990	#[test]
991	fn literal_and_subtree_normalize_paths() {
992		assert_eq!(Pattern::literal("/foo//bar/").unwrap(), pattern("foo/bar"));
993		assert_eq!(Pattern::literal("").unwrap(), Pattern::default());
994		assert_eq!(Pattern::subtree("foo").unwrap(), pattern("foo/**"));
995		assert_eq!(Pattern::subtree("/").unwrap(), Pattern::all());
996		assert_eq!(Pattern::literal("a/*"), Err(InvalidPattern::InvalidSegment("*".into())));
997		assert_eq!(Pattern::literal("**"), Err(InvalidPattern::InvalidSegment("**".into())));
998	}
999
1000	#[test]
1001	fn as_prefix_accepts_literals_then_globstar() {
1002		assert_eq!(Pattern::all().as_prefix(), Some(""));
1003		assert_eq!(pattern("foo/**").as_prefix(), Some("foo"));
1004		assert_eq!(pattern("foo/bar/**").as_prefix(), Some("foo/bar"));
1005		assert_eq!(pattern("foo").as_prefix(), None);
1006		assert_eq!(Pattern::default().as_prefix(), None);
1007		assert_eq!(pattern("foo/*").as_prefix(), None);
1008		assert_eq!(pattern("*/foo/**").as_prefix(), None);
1009		assert_eq!(pattern("foo/**/bar").as_prefix(), None);
1010		assert_eq!(pattern("foo*/**").as_prefix(), None);
1011	}
1012
1013	#[test]
1014	fn matches_whole_segments() {
1015		let cases = [
1016			("", "", true),
1017			("", "a", false),
1018			("a", "a", true),
1019			("a", "a/b", false),
1020			("a", "ab", false),
1021			("*", "a", true),
1022			("*", "", false),
1023			("*", "a/b", false),
1024			("**", "", true),
1025			("**", "a/b/c", true),
1026			("a/**", "a", true),
1027			("a/**", "a/b/c", true),
1028			("a/**", "b", false),
1029			("**/c", "c", true),
1030			("**/c", "a/b/c", true),
1031			("**/c", "a/c/b", false),
1032			("a/**/c", "a/c", true),
1033			("a/**/c", "a/x/y/c", true),
1034			("a/**/c", "a", false),
1035			("a/*/c", "a/x/c", true),
1036			("a/*/c", "a/c", false),
1037			("a/*/**", "a", false),
1038			("a/*/**", "a/b", true),
1039			("**/transcode.pro", "pid/foo.hang/transcode.pro", true),
1040			("**/transcode.pro", "pid/foo.transcode.pro", false),
1041			("**/*.hang", "pid/cam.hang", true),
1042			("**/*.hang", ".hang", true),
1043			("**/*.hang", "pid/cam.hang/x", false),
1044			("foo*", "foo", true),
1045			("foo*", "foobar", true),
1046			("foo*", "fo", false),
1047			("foo.*.hang", "foo..hang", true),
1048			("foo.*.hang", "foo.1.hang", true),
1049			("foo.*.hang", "foo.hang", false),
1050			("a*a", "a", false),
1051			("a*a", "aa", true),
1052		];
1053		for (text, path, expected) in cases {
1054			assert_eq!(pattern(text).matches(path), expected, "{text} vs {path}");
1055		}
1056		// Paths normalize like broadcast paths.
1057		assert!(pattern("a/b").matches("/a//b/"));
1058	}
1059
1060	#[test]
1061	fn contains_is_containment() {
1062		let cases = [
1063			("**", "**", true),
1064			("**", "", true),
1065			("**", "a/*/b", true),
1066			("", "**", false),
1067			("*", "a", true),
1068			("a", "*", false),
1069			("a/**", "a", true),
1070			("a/**", "a/b/**", true),
1071			("a/**", "**", false),
1072			("a/**", "**/a", false),
1073			("**/a", "a", true),
1074			("**/a", "**/b/a", true),
1075			("**/a", "a/**", false),
1076			("*/**", "**", false),
1077			("*/**", "a/**", true),
1078			("*/*/**", "a/**", false),
1079			("*/*/**", "a/b/**", true),
1080			("a/**/c", "a/c", true),
1081			("a/**/c", "a/x/c", true),
1082			("a/**/c", "a/**/x/c", true),
1083			("a/*/**/*", "a/**/b", false),
1084			("a/*/c", "a/b/c", true),
1085			("a/*/c", "a/**/c", false),
1086			("*", "*.hang", true),
1087			("*.hang", "*", false),
1088			("*.hang", "cam.hang", true),
1089			("*.hang", "cam.hang2", false),
1090			("*.hang", "*.hang", true),
1091			("*.hang", "cam*.hang", true),
1092			("*.hang", "cam*hang", false),
1093			("foo*", "foo.*.hang", true),
1094			("foo.*", "foo*", false),
1095			("**/*.hang", "pid/*/cam.hang", true),
1096		];
1097		for (outer, inner, expected) in cases {
1098			assert_eq!(
1099				pattern(outer).contains(&pattern(inner)),
1100				expected,
1101				"{outer} contains {inner}"
1102			);
1103		}
1104	}
1105
1106	#[test]
1107	fn overlaps_is_symmetric_intersection() {
1108		let cases = [
1109			("a", "a", true),
1110			("a", "b", false),
1111			("a", "*", true),
1112			("a", "a/*", false),
1113			("a/**", "**/b", true),
1114			("a/**", "b/**", false),
1115			("a/*", "*/b", true),
1116			("a/*", "b/*", false),
1117			("*/*", "a/**", true),
1118			("*", "a/**", true),
1119			("*", "a/*/**", false),
1120			("**", "", true),
1121			("a/**/b", "**/c", false),
1122			("a/**/b", "**/*", true),
1123			("*.hang", "cam*", true),
1124			("*.hang", "cam.msf", false),
1125			("*.hang", "*.msf", false),
1126			("foo*", "foo.bar*", true),
1127			("foo*", "fob*", false),
1128			("a*b", "ab", true),
1129			("ab*", "*ab", true),
1130			("a/**/b", "x/**", false),
1131			("a/**/b", "**/x", false),
1132		];
1133		for (a, b, expected) in cases {
1134			assert_eq!(pattern(a).overlaps(&pattern(b)), expected, "{a} overlaps {b}");
1135			assert_eq!(pattern(b).overlaps(&pattern(a)), expected, "{b} overlaps {a}");
1136		}
1137	}
1138
1139	#[test]
1140	fn specificity_ranks_by_what_is_pinned_down() {
1141		// Strictly descending.
1142		let ranked = ["a/b/c", "a/b", "a/*.hang", "a/*", "a/**", "*.hang", "*", "**"];
1143		for pair in ranked.windows(2) {
1144			assert!(
1145				pattern(pair[0]).specificity() > pattern(pair[1]).specificity(),
1146				"{} should outrank {}",
1147				pair[0],
1148				pair[1]
1149			);
1150		}
1151		// A longer literal head breaks otherwise equal ties.
1152		assert!(pattern("a/**").specificity() > pattern("**/a").specificity());
1153		// A partial pinning more bytes is more specific than one pinning fewer.
1154		assert!(pattern("cam*.hang").specificity() > pattern("*.hang").specificity());
1155		assert!(pattern("*.hang").specificity() > pattern("*").specificity());
1156		// Equal structure is one tier.
1157		assert_eq!(pattern("*/a").specificity(), pattern("*/b").specificity());
1158		assert_eq!(pattern("*/a/**").specificity(), pattern("*/**/a").specificity());
1159	}
1160
1161	#[test]
1162	fn rebase_is_set_valued() {
1163		let cases: &[(&str, &str, &[&str])] = &[
1164			("**", "a", &["**"]),
1165			("**/a", "a", &["", "**/a"]),
1166			("a/**", "a", &["**"]),
1167			("a/**", "a/b", &["**"]),
1168			("a/**", "b", &[]),
1169			("a/b", "a", &["b"]),
1170			("a/b", "a/b", &[""]),
1171			("a/b", "a/b/c", &[]),
1172			("*/b", "a", &["b"]),
1173			("a/*/c", "a/x", &["c"]),
1174			("a/**/b/c", "a/b", &["**/b/c", "c"]),
1175			("a/**/b", "a/b/b", &["**/b", ""]),
1176			("**/b/c", "b", &["**/b/c", "c"]),
1177			("", "", &[""]),
1178			("", "a", &[]),
1179			("**", "", &["**"]),
1180			("*.hang/**", "cam.hang", &["**"]),
1181			("*.hang/**", "cam.msf", &[]),
1182			("**/*.hang", "a.hang", &["", "**/*.hang"]),
1183		];
1184		for (text, root, expected) in cases {
1185			let got = pattern(text).rebase(root);
1186			let expected: Patterns = expected.iter().map(|e| pattern(e)).collect();
1187			assert_eq!(got, expected, "{text} rebased at {root}");
1188		}
1189	}
1190
1191	#[test]
1192	fn rooted_inverts_rebase() {
1193		assert_eq!(pattern("**").rooted("a/b").unwrap(), pattern("a/b/**"));
1194		assert_eq!(pattern("").rooted("a").unwrap(), pattern("a"));
1195		assert_eq!(pattern("*/c").rooted("").unwrap(), pattern("*/c"));
1196		assert_eq!(
1197			pattern("a").rooted("*"),
1198			Err(InvalidPattern::InvalidSegment("*".into()))
1199		);
1200
1201		let deep = ["a"; Pattern::MAX_SEGMENTS].join("/");
1202		assert_eq!(pattern("b").rooted(&deep), Err(InvalidPattern::TooManySegments));
1203	}
1204
1205	#[test]
1206	fn head_is_the_literal_prefix() {
1207		assert_eq!(pattern("a/b/*/c").head(), "a/b");
1208		assert_eq!(pattern("**/a").head(), "");
1209		assert_eq!(pattern("a/b").head(), "a/b");
1210		assert_eq!(pattern("").head(), "");
1211		assert_eq!(pattern("a/b*/c").head(), "a");
1212		assert!(!pattern("a/b*").is_literal());
1213		assert!(pattern("a/b").is_literal());
1214		assert!(!pattern("a/*").is_literal());
1215		assert!(pattern("a/**").has_globstar());
1216		assert!(!pattern("a/*").has_globstar());
1217	}
1218
1219	#[cfg(feature = "serde")]
1220	#[test]
1221	fn serde_round_trips_as_text() {
1222		let p = pattern("a/*/**");
1223		let json = serde_json::to_string(&p).unwrap();
1224		assert_eq!(json, "\"a/**/*\"");
1225		assert_eq!(serde_json::from_str::<Pattern>(&json).unwrap(), p);
1226		assert!(serde_json::from_str::<Pattern>("\"a//b\"").is_err());
1227	}
1228
1229	#[test]
1230	fn ordering_is_by_text() {
1231		let mut list = [pattern("b"), pattern("**"), pattern("a/*"), pattern("a")];
1232		list.sort();
1233		let texts: Vec<_> = list.iter().map(ToString::to_string).collect();
1234		assert_eq!(texts, ["**", "a", "a/*", "b"]);
1235	}
1236}