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 `**`, and a path of [`MAX_SEGMENTS`](Self::MAX_SEGMENTS) yields the literal,
407	/// since nothing can sit beneath it.
408	pub fn subtree(path: &str) -> Result<Self, InvalidPattern> {
409		let mut segments: Vec<Segment> = literal_segments(path).collect();
410		if segments.len() < Self::MAX_SEGMENTS {
411			segments.push(Segment::Globstar);
412		}
413		Self::new(segments)
414	}
415
416	/// The canonical text: segments joined by `/`, wildcards as `*` and `**`.
417	pub fn as_str(&self) -> &str {
418		&self.text
419	}
420
421	/// The segments, in order.
422	pub fn segments(&self) -> &[Segment] {
423		&self.segments
424	}
425
426	/// The literal segments before the first wildcard, as a path.
427	///
428	/// Every matching path starts with it, so it is where a tree walk starts. Empty
429	/// when the pattern starts with a wildcard; the whole pattern when it has none.
430	pub fn head(&self) -> &str {
431		&self.text[..self.head]
432	}
433
434	/// Whether the pattern has no wildcards, so it matches exactly one path.
435	pub fn is_literal(&self) -> bool {
436		self.head == self.text.len()
437	}
438
439	/// The covered prefix if this pattern is prefix-shaped: zero or more literals then `**`.
440	///
441	/// `**` covers every path (the empty prefix). `foo/**` covers `foo` and everything
442	/// beneath it. A literal, a `*`, or a `**` that is not last is `None`.
443	pub fn as_prefix(&self) -> Option<&str> {
444		match self.segments.split_last() {
445			Some((Segment::Globstar, head)) if head.iter().all(|s| matches!(s, Segment::Literal(_))) => {
446				Some(self.head())
447			}
448			_ => None,
449		}
450	}
451
452	/// Whether the pattern has a `**`, so it matches paths of more than one length.
453	pub fn has_globstar(&self) -> bool {
454		self.globstar.is_some()
455	}
456
457	/// Whether `path` is in the set this pattern describes.
458	///
459	/// The path is normalized like a broadcast path: slashes are trimmed and collapsed.
460	pub fn matches(&self, path: &str) -> bool {
461		let parts: Vec<&str> = split_path(path).collect();
462		match self.globstar {
463			None => {
464				parts.len() == self.segments.len()
465					&& self
466						.segments
467						.iter()
468						.zip(&parts)
469						.all(|(segment, part)| segment.matches(part))
470			}
471			Some(_) => {
472				let (head, tail) = self.split();
473				parts.len() >= head.len() + tail.len()
474					&& head.iter().zip(&parts).all(|(segment, part)| segment.matches(part))
475					&& tail
476						.iter()
477						.rev()
478						.zip(parts.iter().rev())
479						.all(|(segment, part)| segment.matches(part))
480			}
481		}
482	}
483
484	/// Whether every path `other` matches, this pattern matches too.
485	///
486	/// This is the authorization check: a grant contains a request when the request
487	/// cannot name a path outside it. A pattern contains itself.
488	pub fn contains(&self, other: &Self) -> bool {
489		match (self.globstar, other.globstar) {
490			(None, None) => {
491				self.segments.len() == other.segments.len()
492					&& self.segments.iter().zip(&other.segments).all(|(a, b)| a.covers(b))
493			}
494			// A fixed-length pattern cannot contain one that matches many lengths.
495			(None, Some(_)) => false,
496			(Some(_), None) => {
497				let (head, tail) = self.split();
498				other.segments.len() >= head.len() + tail.len()
499					&& head.iter().zip(&other.segments).all(|(a, b)| a.covers(b))
500					&& tail
501						.iter()
502						.rev()
503						.zip(other.segments.iter().rev())
504						.all(|(a, b)| a.covers(b))
505			}
506			(Some(_), Some(_)) => {
507				let (head, tail) = self.split();
508				let (other_head, other_tail) = other.split();
509
510				// The other's `**` can be arbitrarily long, so any of our segments that
511				// reach past the other's head or tail must be `*`; and the other's
512				// shortest path (its `**` empty) must still be long enough for ours.
513				let covers_run = |ours: &[Segment], theirs: &[Segment]| {
514					ours.iter().enumerate().all(|(i, a)| match theirs.get(i) {
515						Some(b) => a.covers(b),
516						None => *a == Segment::Wildcard,
517					})
518				};
519				let reversed = |run: &[Segment]| run.iter().rev().cloned().collect::<Vec<_>>();
520
521				head.len() + tail.len() <= other_head.len() + other_tail.len()
522					&& covers_run(head, other_head)
523					&& covers_run(&reversed(tail), &reversed(other_tail))
524			}
525		}
526	}
527
528	/// Whether some path matches both patterns.
529	pub fn overlaps(&self, other: &Self) -> bool {
530		let compatible_run = |a: &[Segment], b: &[Segment]| a.iter().zip(b).all(|(a, b)| a.compatible(b));
531		let compatible_tail =
532			|a: &[Segment], b: &[Segment]| a.iter().rev().zip(b.iter().rev()).all(|(a, b)| a.compatible(b));
533
534		match (self.globstar, other.globstar) {
535			(None, None) => {
536				self.segments.len() == other.segments.len() && compatible_run(&self.segments, &other.segments)
537			}
538			(None, Some(_)) => other.overlaps(self),
539			(Some(_), None) => {
540				let (head, tail) = self.split();
541				other.segments.len() >= head.len() + tail.len()
542					&& compatible_run(head, &other.segments)
543					&& compatible_tail(tail, &other.segments)
544			}
545			(Some(_), Some(_)) => {
546				// A path long enough keeps the heads and tails apart, so the only
547				// constraints are segment-wise where the heads and tails overlap.
548				let (head, tail) = self.split();
549				let (other_head, other_tail) = other.split();
550				compatible_run(head, other_head) && compatible_tail(tail, other_tail)
551			}
552		}
553	}
554
555	/// How much of a path this pattern pins down. See [`Specificity`].
556	pub fn specificity(&self) -> Specificity {
557		let count = |wanted: fn(&Segment) -> bool| self.segments.iter().filter(|s| wanted(s)).count();
558		Specificity {
559			literals: count(|s| matches!(s, Segment::Literal(_))),
560			exact: self.globstar.is_none(),
561			partials: count(|s| matches!(s, Segment::Partial { .. })),
562			wildcards: count(|s| matches!(s, Segment::Wildcard)),
563			pinned: self
564				.segments
565				.iter()
566				.map(|s| match s {
567					Segment::Partial { prefix, suffix } => prefix.len() + suffix.len(),
568					_ => 0,
569				})
570				.sum(),
571			head: self
572				.segments
573				.iter()
574				.take_while(|s| matches!(s, Segment::Literal(_)))
575				.count(),
576		}
577	}
578
579	/// The patterns that, relative to `root`, match exactly the paths this pattern
580	/// matches beneath `root`.
581	///
582	/// This is how a grant or an advertisement is presented inside a rooted view. It is
583	/// a set because `**` may consume the root or stop short of it: `**/a` rebased at `a`
584	/// is both the empty pattern (the root itself) and `**/a` (deeper paths ending in
585	/// `a`). Empty when nothing under `root` matches. The root is normalized like a
586	/// broadcast path.
587	pub fn rebase(&self, root: &str) -> Patterns {
588		let root: Vec<&str> = split_path(root).collect();
589		let mut out = Patterns::new();
590
591		let matches_run = |segments: &[Segment], parts: &[&str]| segments.iter().zip(parts).all(|(s, p)| s.matches(p));
592		// Construction cannot fail: the segments come from a valid pattern, and a
593		// rebase never lengthens it.
594		let build = |segments: &[Segment]| Pattern::new(segments.to_vec()).expect("a rebased pattern is valid");
595
596		match self.globstar {
597			None => {
598				if root.len() <= self.segments.len() && matches_run(&self.segments, &root) {
599					out.insert(build(&self.segments[root.len()..]));
600				}
601			}
602			Some(index) => {
603				let (head, tail) = self.split();
604				if root.len() <= head.len() {
605					if matches_run(head, &root) {
606						out.insert(build(&self.segments[root.len()..]));
607					}
608					return out;
609				}
610				if !matches_run(head, &root) {
611					return out;
612				}
613
614				// The root reaches into the `**`. Either the `**` swallows the rest of the
615				// root and stays open, or it closed inside the root and some of the tail
616				// already matched the root's last segments.
617				let rest = &root[head.len()..];
618				out.insert(build(&self.segments[index..]));
619				for consumed in 1..=tail.len().min(rest.len()) {
620					if matches_run(&tail[..consumed], &rest[rest.len() - consumed..]) {
621						out.insert(build(&tail[consumed..]));
622					}
623				}
624			}
625		}
626
627		out
628	}
629
630	/// The patterns matching exactly the paths both patterns match.
631	///
632	/// This is how a claim is clamped to a scope: the covered paths inside the
633	/// grant, as patterns of their own. It is a set because two partial segments or
634	/// two `**` runs can meet in more than one way: `ab*` and `*b` meet at `ab*b` and
635	/// at `ab`, and `a/**` and `**/a` meet at `a/**/a` and at `a`. Empty when the
636	/// two do not [overlap](Self::overlaps).
637	pub fn intersect(&self, other: &Self) -> Result<Patterns, IntersectionError> {
638		// The contained pattern is the intersection as written, where the general
639		// case below could only spell the same set in more pieces.
640		if self.contains(other) {
641			return Ok(Patterns::from(other.clone()));
642		}
643		if other.contains(self) {
644			return Ok(Patterns::from(self.clone()));
645		}
646
647		let mut out = Patterns::new();
648		let mut remaining = Self::MAX_INTERSECTIONS;
649
650		match (self.globstar, other.globstar) {
651			(None, None) => {
652				if self.segments.len() == other.segments.len() {
653					intersect_into(&self.segments, &other.segments, &mut out, &mut remaining)?;
654				}
655			}
656			(Some(_), None) => {
657				let (head, tail) = self.split();
658				if other.segments.len() >= head.len() + tail.len() {
659					let stretched = expand(head, tail, other.segments.len());
660					intersect_into(&stretched, &other.segments, &mut out, &mut remaining)?;
661				}
662			}
663			(None, Some(_)) => return other.intersect(self),
664			(Some(_), Some(_)) => {
665				let (head, tail) = self.split();
666				let (other_head, other_tail) = other.split();
667				let heads = head.len().max(other_head.len());
668				let tails = tail.len().max(other_tail.len());
669				let shortest = (head.len() + tail.len()).max(other_head.len() + other_tail.len());
670
671				// Paths too short to keep the longer head and the longer tail apart
672				// constrain both from each end at once: enumerate each length. When
673				// the open form below would not fit, every length that fits is short.
674				let long = heads + tails;
675				let open = long < Self::MAX_SEGMENTS;
676				let cap = if open { long } else { Self::MAX_SEGMENTS + 1 };
677				for len in shortest..cap {
678					let a = expand(head, tail, len);
679					let b = expand(other_head, other_tail, len);
680					intersect_into(&a, &b, &mut out, &mut remaining)?;
681				}
682
683				// Longer paths pin the heads and the tails independently and leave the
684				// run between them free.
685				if open {
686					let pad = |run: &[Segment], len: usize, front: bool| -> Vec<Segment> {
687						let fill = std::iter::repeat_n(Segment::Wildcard, len - run.len());
688						if front {
689							run.iter().cloned().chain(fill).collect()
690						} else {
691							fill.chain(run.iter().cloned()).collect()
692						}
693					};
694					let fronts = intersect_run(&pad(head, heads, true), &pad(other_head, heads, true), remaining)?;
695					let backs = intersect_run(&pad(tail, tails, false), &pad(other_tail, tails, false), remaining)?;
696					if fronts
697						.len()
698						.checked_mul(backs.len())
699						.is_none_or(|size| size > remaining)
700					{
701						return Err(IntersectionError::TooManyPatterns);
702					}
703					for front in &fronts {
704						for back in &backs {
705							let mut segments = front.clone();
706							segments.push(Segment::Globstar);
707							segments.extend_from_slice(back);
708							insert_intersection(&mut out, &mut remaining, segments)?;
709						}
710					}
711				}
712			}
713		}
714
715		Ok(out)
716	}
717
718	/// What each wildcard of this pattern stands for in `matched`, a pattern this
719	/// one [contains](Self::contains); `None` when it does not.
720	///
721	/// One capture per non-literal segment (`*`, `prefix*suffix`, `**`), in order,
722	/// the way a regex match exposes its groups: `foo/*/chat` against `foo/alice/chat`
723	/// captures `alice`, and `foo/**` against `foo/alice/chat` captures `alice/chat`.
724	/// A capture is a pattern because `matched` may be one: `foo/**` against
725	/// `foo/alice/**` captures `alice/**`. When `matched` has a `**` that this
726	/// pattern's own segments straddle (`**/*` against `a/**`, where the last
727	/// segment is `a` or anything after it), the segments it straddles cannot be
728	/// pinned and capture themselves: `**` then `*`.
729	pub fn captures(&self, matched: &Self) -> Option<Vec<Self>> {
730		if !self.contains(matched) {
731			return None;
732		}
733		// Construction cannot fail: every capture is a run of `matched`'s own valid
734		// segments or one of ours, never longer than either.
735		let build = |segments: &[Segment]| Pattern::new(segments.to_vec()).expect("a capture is valid");
736		let mut out = Vec::new();
737
738		if self.globstar.is_none() {
739			for (segment, theirs) in self.segments.iter().zip(&matched.segments) {
740				if !matches!(segment, Segment::Literal(_)) {
741					out.push(build(std::slice::from_ref(theirs)));
742				}
743			}
744			return Some(out);
745		}
746
747		let (head, tail) = self.split();
748		let middle = matched.segments.len() - tail.len();
749		// Our head aligns with `matched` from the front and our tail from the back.
750		// A segment of ours aligned at or beyond `matched`'s `**` (from its own
751		// side) has no fixed counterpart, so it captures itself.
752		let free = matched.globstar;
753		let pinned = |at: usize, from_front: bool| match free {
754			Some(free) if from_front => at < free,
755			Some(free) => at > free,
756			None => true,
757		};
758
759		for (i, segment) in head.iter().enumerate() {
760			if !matches!(segment, Segment::Literal(_)) {
761				let capture = if pinned(i, true) { &matched.segments[i] } else { segment };
762				out.push(build(std::slice::from_ref(capture)));
763			}
764		}
765		if free.is_none_or(|free| free >= head.len() && free < middle) {
766			out.push(build(&matched.segments[head.len()..middle]));
767		} else {
768			out.push(Pattern::all());
769		}
770		for (j, segment) in tail.iter().enumerate() {
771			if !matches!(segment, Segment::Literal(_)) {
772				let at = middle + j;
773				let capture = if pinned(at, false) {
774					&matched.segments[at]
775				} else {
776					segment
777				};
778				out.push(build(std::slice::from_ref(capture)));
779			}
780		}
781		Some(out)
782	}
783
784	/// This pattern placed beneath a literal `root`: the same paths, named from the
785	/// root's parent. The inverse of [`rebase`](Self::rebase) for a single pattern.
786	///
787	/// The root is normalized and validated like [`literal`](Self::literal), and the
788	/// result must fit [`MAX_SEGMENTS`](Self::MAX_SEGMENTS).
789	pub fn rooted(&self, root: &str) -> Result<Self, InvalidPattern> {
790		Self::new(literal_segments(root).chain(self.segments.iter().cloned()))
791	}
792
793	/// The segments before and after the `**`. Only meaningful when there is one.
794	fn split(&self) -> (&[Segment], &[Segment]) {
795		match self.globstar {
796			Some(index) => (&self.segments[..index], &self.segments[index + 1..]),
797			None => (&self.segments, &[]),
798		}
799	}
800}
801
802/// The non-empty segments of a path: leading, trailing, and doubled slashes are dropped,
803/// matching how a broadcast path is normalized.
804fn split_path(path: &str) -> impl Iterator<Item = &str> {
805	path.split('/').filter(|part| !part.is_empty())
806}
807
808/// The segments of a path as literals, leaving validation to [`Pattern::new`].
809fn literal_segments(path: &str) -> impl Iterator<Item = Segment> + '_ {
810	// A `*` or `**` segment becomes an invalid literal, which `new` rejects: a path
811	// is never read as a pattern.
812	split_path(path).map(|part| Segment::Literal(part.to_string()))
813}
814
815impl FromStr for Pattern {
816	type Err = InvalidPattern;
817
818	/// Parse a pattern's text. Unlike a path, slashes are not normalized: a leading,
819	/// trailing, or doubled `/` is an error, so a typo cannot silently widen a grant.
820	fn from_str(text: &str) -> Result<Self, InvalidPattern> {
821		if text.is_empty() {
822			return Self::new([]);
823		}
824		text.split('/')
825			.map(Segment::parse)
826			.collect::<Result<Vec<_>, _>>()
827			.and_then(Self::new)
828	}
829}
830
831impl TryFrom<&str> for Pattern {
832	type Error = InvalidPattern;
833
834	fn try_from(text: &str) -> Result<Self, InvalidPattern> {
835		text.parse()
836	}
837}
838
839impl TryFrom<String> for Pattern {
840	type Error = InvalidPattern;
841
842	fn try_from(text: String) -> Result<Self, InvalidPattern> {
843		text.parse()
844	}
845}
846
847impl Default for Pattern {
848	/// The empty pattern, which matches only the empty path.
849	fn default() -> Self {
850		Self::new([]).expect("the empty pattern is valid")
851	}
852}
853
854impl fmt::Display for Pattern {
855	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
856		f.write_str(&self.text)
857	}
858}
859
860impl fmt::Debug for Pattern {
861	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
862		write!(f, "Pattern({:?})", self.text)
863	}
864}
865
866impl PartialOrd for Pattern {
867	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
868		Some(self.cmp(other))
869	}
870}
871
872impl Ord for Pattern {
873	/// Ordered by text, so a sorted list of patterns is deterministic.
874	fn cmp(&self, other: &Self) -> Ordering {
875		self.text.cmp(&other.text)
876	}
877}
878
879#[cfg(feature = "serde")]
880impl serde::Serialize for Pattern {
881	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
882		serializer.serialize_str(self.as_str())
883	}
884}
885
886#[cfg(feature = "serde")]
887impl<'de> serde::Deserialize<'de> for Pattern {
888	/// Reads the canonical text, so a persisted pattern is validated on the way in.
889	fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
890		let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
891		text.parse().map_err(serde::de::Error::custom)
892	}
893}
894
895impl AsRef<str> for Pattern {
896	fn as_ref(&self) -> &str {
897		&self.text
898	}
899}
900
901#[cfg(test)]
902mod tests {
903	use super::*;
904
905	fn pattern(text: &str) -> Pattern {
906		text.parse().unwrap_or_else(|err| panic!("{text:?}: {err}"))
907	}
908
909	#[test]
910	fn parses_and_prints_canonically() {
911		for text in [
912			"",
913			"a",
914			"a/b",
915			"*",
916			"**",
917			"a/*/b",
918			"**/transcode.pro",
919			"a/**/b/*",
920			"**/*",
921			"**/*.hang",
922			"foo*",
923			"foo.*.hang",
924		] {
925			assert_eq!(pattern(text).to_string(), text);
926		}
927		assert_eq!(
928			pattern("a/*/**/b").segments(),
929			&[
930				Segment::Literal("a".into()),
931				Segment::Globstar,
932				Segment::Wildcard,
933				Segment::Literal("b".into()),
934			]
935		);
936	}
937
938	#[test]
939	fn rejects_bad_syntax() {
940		assert_eq!("/a".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
941		assert_eq!("a/".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
942		assert_eq!("a//b".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
943		assert_eq!("/".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
944		assert_eq!("**/**".parse::<Pattern>(), Err(InvalidPattern::MultipleGlobstars));
945		assert_eq!(
946			"***".parse::<Pattern>(),
947			Err(InvalidPattern::InvalidSegment("***".into()))
948		);
949
950		assert_eq!(
951			"a*b*c".parse::<Pattern>(),
952			Err(InvalidPattern::InvalidSegment("a*b*c".into()))
953		);
954		assert_eq!(
955			"*a*".parse::<Pattern>(),
956			Err(InvalidPattern::InvalidSegment("*a*".into()))
957		);
958		assert_eq!(
959			"*.hang".parse::<Pattern>().unwrap().segments(),
960			&[Segment::Partial {
961				prefix: String::new(),
962				suffix: ".hang".into()
963			}]
964		);
965		assert_eq!(
966			Pattern::new([Segment::Partial {
967				prefix: String::new(),
968				suffix: String::new()
969			}]),
970			Err(InvalidPattern::InvalidSegment("*".into()))
971		);
972		assert_eq!(
973			Pattern::new([Segment::Partial {
974				prefix: "a/".into(),
975				suffix: String::new()
976			}]),
977			Err(InvalidPattern::InvalidSegment("a/*".into()))
978		);
979
980		let deep = ["a"; Pattern::MAX_SEGMENTS + 1].join("/");
981		assert_eq!(deep.parse::<Pattern>(), Err(InvalidPattern::TooManySegments));
982		let max = ["a"; Pattern::MAX_SEGMENTS].join("/");
983		assert!(max.parse::<Pattern>().is_ok());
984
985		assert_eq!(
986			Pattern::new([Segment::Literal("a/b".into())]),
987			Err(InvalidPattern::InvalidSegment("a/b".into()))
988		);
989		assert_eq!(
990			Pattern::new([Segment::Literal(String::new())]),
991			Err(InvalidPattern::EmptySegment)
992		);
993	}
994
995	#[test]
996	fn literal_and_subtree_normalize_paths() {
997		assert_eq!(Pattern::literal("/foo//bar/").unwrap(), pattern("foo/bar"));
998		assert_eq!(Pattern::literal("").unwrap(), Pattern::default());
999		assert_eq!(Pattern::subtree("foo").unwrap(), pattern("foo/**"));
1000		assert_eq!(Pattern::subtree("/").unwrap(), Pattern::all());
1001		let max = ["a"; Pattern::MAX_SEGMENTS].join("/");
1002		assert_eq!(Pattern::subtree(&max).unwrap(), pattern(&max));
1003		let deep = ["a"; Pattern::MAX_SEGMENTS + 1].join("/");
1004		assert_eq!(Pattern::subtree(&deep), Err(InvalidPattern::TooManySegments));
1005		assert_eq!(Pattern::literal("a/*"), Err(InvalidPattern::InvalidSegment("*".into())));
1006		assert_eq!(Pattern::literal("**"), Err(InvalidPattern::InvalidSegment("**".into())));
1007	}
1008
1009	#[test]
1010	fn as_prefix_accepts_literals_then_globstar() {
1011		assert_eq!(Pattern::all().as_prefix(), Some(""));
1012		assert_eq!(pattern("foo/**").as_prefix(), Some("foo"));
1013		assert_eq!(pattern("foo/bar/**").as_prefix(), Some("foo/bar"));
1014		assert_eq!(pattern("foo").as_prefix(), None);
1015		assert_eq!(Pattern::default().as_prefix(), None);
1016		assert_eq!(pattern("foo/*").as_prefix(), None);
1017		assert_eq!(pattern("*/foo/**").as_prefix(), None);
1018		assert_eq!(pattern("foo/**/bar").as_prefix(), None);
1019		assert_eq!(pattern("foo*/**").as_prefix(), None);
1020	}
1021
1022	#[test]
1023	fn matches_whole_segments() {
1024		let cases = [
1025			("", "", true),
1026			("", "a", false),
1027			("a", "a", true),
1028			("a", "a/b", false),
1029			("a", "ab", false),
1030			("*", "a", true),
1031			("*", "", false),
1032			("*", "a/b", false),
1033			("**", "", true),
1034			("**", "a/b/c", true),
1035			("a/**", "a", true),
1036			("a/**", "a/b/c", true),
1037			("a/**", "b", false),
1038			("**/c", "c", true),
1039			("**/c", "a/b/c", true),
1040			("**/c", "a/c/b", false),
1041			("a/**/c", "a/c", true),
1042			("a/**/c", "a/x/y/c", true),
1043			("a/**/c", "a", false),
1044			("a/*/c", "a/x/c", true),
1045			("a/*/c", "a/c", false),
1046			("a/*/**", "a", false),
1047			("a/*/**", "a/b", true),
1048			("**/transcode.pro", "pid/foo.hang/transcode.pro", true),
1049			("**/transcode.pro", "pid/foo.transcode.pro", false),
1050			("**/*.hang", "pid/cam.hang", true),
1051			("**/*.hang", ".hang", true),
1052			("**/*.hang", "pid/cam.hang/x", false),
1053			("foo*", "foo", true),
1054			("foo*", "foobar", true),
1055			("foo*", "fo", false),
1056			("foo.*.hang", "foo..hang", true),
1057			("foo.*.hang", "foo.1.hang", true),
1058			("foo.*.hang", "foo.hang", false),
1059			("a*a", "a", false),
1060			("a*a", "aa", true),
1061		];
1062		for (text, path, expected) in cases {
1063			assert_eq!(pattern(text).matches(path), expected, "{text} vs {path}");
1064		}
1065		// Paths normalize like broadcast paths.
1066		assert!(pattern("a/b").matches("/a//b/"));
1067	}
1068
1069	#[test]
1070	fn contains_is_containment() {
1071		let cases = [
1072			("**", "**", true),
1073			("**", "", true),
1074			("**", "a/*/b", true),
1075			("", "**", false),
1076			("*", "a", true),
1077			("a", "*", false),
1078			("a/**", "a", true),
1079			("a/**", "a/b/**", true),
1080			("a/**", "**", false),
1081			("a/**", "**/a", false),
1082			("**/a", "a", true),
1083			("**/a", "**/b/a", true),
1084			("**/a", "a/**", false),
1085			("*/**", "**", false),
1086			("*/**", "a/**", true),
1087			("*/*/**", "a/**", false),
1088			("*/*/**", "a/b/**", true),
1089			("a/**/c", "a/c", true),
1090			("a/**/c", "a/x/c", true),
1091			("a/**/c", "a/**/x/c", true),
1092			("a/*/**/*", "a/**/b", false),
1093			("a/*/c", "a/b/c", true),
1094			("a/*/c", "a/**/c", false),
1095			("*", "*.hang", true),
1096			("*.hang", "*", false),
1097			("*.hang", "cam.hang", true),
1098			("*.hang", "cam.hang2", false),
1099			("*.hang", "*.hang", true),
1100			("*.hang", "cam*.hang", true),
1101			("*.hang", "cam*hang", false),
1102			("foo*", "foo.*.hang", true),
1103			("foo.*", "foo*", false),
1104			("**/*.hang", "pid/*/cam.hang", true),
1105		];
1106		for (outer, inner, expected) in cases {
1107			assert_eq!(
1108				pattern(outer).contains(&pattern(inner)),
1109				expected,
1110				"{outer} contains {inner}"
1111			);
1112		}
1113	}
1114
1115	#[test]
1116	fn overlaps_is_symmetric_intersection() {
1117		let cases = [
1118			("a", "a", true),
1119			("a", "b", false),
1120			("a", "*", true),
1121			("a", "a/*", false),
1122			("a/**", "**/b", true),
1123			("a/**", "b/**", false),
1124			("a/*", "*/b", true),
1125			("a/*", "b/*", false),
1126			("*/*", "a/**", true),
1127			("*", "a/**", true),
1128			("*", "a/*/**", false),
1129			("**", "", true),
1130			("a/**/b", "**/c", false),
1131			("a/**/b", "**/*", true),
1132			("*.hang", "cam*", true),
1133			("*.hang", "cam.msf", false),
1134			("*.hang", "*.msf", false),
1135			("foo*", "foo.bar*", true),
1136			("foo*", "fob*", false),
1137			("a*b", "ab", true),
1138			("ab*", "*ab", true),
1139			("a/**/b", "x/**", false),
1140			("a/**/b", "**/x", false),
1141		];
1142		for (a, b, expected) in cases {
1143			assert_eq!(pattern(a).overlaps(&pattern(b)), expected, "{a} overlaps {b}");
1144			assert_eq!(pattern(b).overlaps(&pattern(a)), expected, "{b} overlaps {a}");
1145		}
1146	}
1147
1148	#[test]
1149	fn specificity_ranks_by_what_is_pinned_down() {
1150		// Strictly descending.
1151		let ranked = ["a/b/c", "a/b", "a/*.hang", "a/*", "a/**", "*.hang", "*", "**"];
1152		for pair in ranked.windows(2) {
1153			assert!(
1154				pattern(pair[0]).specificity() > pattern(pair[1]).specificity(),
1155				"{} should outrank {}",
1156				pair[0],
1157				pair[1]
1158			);
1159		}
1160		// A longer literal head breaks otherwise equal ties.
1161		assert!(pattern("a/**").specificity() > pattern("**/a").specificity());
1162		// A partial pinning more bytes is more specific than one pinning fewer.
1163		assert!(pattern("cam*.hang").specificity() > pattern("*.hang").specificity());
1164		assert!(pattern("*.hang").specificity() > pattern("*").specificity());
1165		// Equal structure is one tier.
1166		assert_eq!(pattern("*/a").specificity(), pattern("*/b").specificity());
1167		assert_eq!(pattern("*/a/**").specificity(), pattern("*/**/a").specificity());
1168	}
1169
1170	#[test]
1171	fn rebase_is_set_valued() {
1172		let cases: &[(&str, &str, &[&str])] = &[
1173			("**", "a", &["**"]),
1174			("**/a", "a", &["", "**/a"]),
1175			("a/**", "a", &["**"]),
1176			("a/**", "a/b", &["**"]),
1177			("a/**", "b", &[]),
1178			("a/b", "a", &["b"]),
1179			("a/b", "a/b", &[""]),
1180			("a/b", "a/b/c", &[]),
1181			("*/b", "a", &["b"]),
1182			("a/*/c", "a/x", &["c"]),
1183			("a/**/b/c", "a/b", &["**/b/c", "c"]),
1184			("a/**/b", "a/b/b", &["**/b", ""]),
1185			("**/b/c", "b", &["**/b/c", "c"]),
1186			("", "", &[""]),
1187			("", "a", &[]),
1188			("**", "", &["**"]),
1189			("*.hang/**", "cam.hang", &["**"]),
1190			("*.hang/**", "cam.msf", &[]),
1191			("**/*.hang", "a.hang", &["", "**/*.hang"]),
1192		];
1193		for (text, root, expected) in cases {
1194			let got = pattern(text).rebase(root);
1195			let expected: Patterns = expected.iter().map(|e| pattern(e)).collect();
1196			assert_eq!(got, expected, "{text} rebased at {root}");
1197		}
1198	}
1199
1200	#[test]
1201	fn rooted_inverts_rebase() {
1202		assert_eq!(pattern("**").rooted("a/b").unwrap(), pattern("a/b/**"));
1203		assert_eq!(pattern("").rooted("a").unwrap(), pattern("a"));
1204		assert_eq!(pattern("*/c").rooted("").unwrap(), pattern("*/c"));
1205		assert_eq!(
1206			pattern("a").rooted("*"),
1207			Err(InvalidPattern::InvalidSegment("*".into()))
1208		);
1209
1210		let deep = ["a"; Pattern::MAX_SEGMENTS].join("/");
1211		assert_eq!(pattern("b").rooted(&deep), Err(InvalidPattern::TooManySegments));
1212	}
1213
1214	#[test]
1215	fn head_is_the_literal_prefix() {
1216		assert_eq!(pattern("a/b/*/c").head(), "a/b");
1217		assert_eq!(pattern("**/a").head(), "");
1218		assert_eq!(pattern("a/b").head(), "a/b");
1219		assert_eq!(pattern("").head(), "");
1220		assert_eq!(pattern("a/b*/c").head(), "a");
1221		assert!(!pattern("a/b*").is_literal());
1222		assert!(pattern("a/b").is_literal());
1223		assert!(!pattern("a/*").is_literal());
1224		assert!(pattern("a/**").has_globstar());
1225		assert!(!pattern("a/*").has_globstar());
1226	}
1227
1228	#[cfg(feature = "serde")]
1229	#[test]
1230	fn serde_round_trips_as_text() {
1231		let p = pattern("a/*/**");
1232		let json = serde_json::to_string(&p).unwrap();
1233		assert_eq!(json, "\"a/**/*\"");
1234		assert_eq!(serde_json::from_str::<Pattern>(&json).unwrap(), p);
1235		assert!(serde_json::from_str::<Pattern>("\"a//b\"").is_err());
1236	}
1237
1238	#[test]
1239	fn ordering_is_by_text() {
1240		let mut list = [pattern("b"), pattern("**"), pattern("a/*"), pattern("a")];
1241		list.sort();
1242		let texts: Vec<_> = list.iter().map(ToString::to_string).collect();
1243		assert_eq!(texts, ["**", "a", "a/*", "b"]);
1244	}
1245}