Skip to main content

moq_pattern/
patterns.rs

1use super::{IntersectionError, InvalidPattern, Pattern};
2
3/// A union of patterns, reduced so no member is contained by another.
4///
5/// This is the shape of a grant (the paths a token may publish) and of a rebased
6/// pattern (see [`Pattern::rebase`]). Order is canonical, so two unions describing the
7/// same reduced set compare equal.
8///
9/// Containment is per member: [`contains`](Self::contains) holds when one pattern in the
10/// union contains the candidate. A candidate covered only jointly by several members
11/// (`a/**` against `a`, `a/*`, and `a/*/**`) is refused, which keeps the check linear
12/// and its answer easy to predict. A grant that means a subtree writes `a/**`.
13#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
14pub struct Patterns(Vec<Pattern>);
15
16impl Patterns {
17	/// The empty union, which matches nothing.
18	pub fn new() -> Self {
19		Self::default()
20	}
21
22	/// Add a pattern, dropping members it contains.
23	///
24	/// Returns `false` when a member already contains it, leaving the union unchanged.
25	pub fn insert(&mut self, pattern: Pattern) -> bool {
26		if self.contains(&pattern) {
27			return false;
28		}
29		self.0.retain(|member| !pattern.contains(member));
30		let at = self.0.partition_point(|member| member < &pattern);
31		self.0.insert(at, pattern);
32		true
33	}
34
35	/// Whether any member matches `path`.
36	pub fn matches(&self, path: &str) -> bool {
37		self.0.iter().any(|member| member.matches(path))
38	}
39
40	/// Whether some member contains `pattern`. See the type docs for why this is per
41	/// member rather than joint.
42	pub fn contains(&self, pattern: &Pattern) -> bool {
43		self.0.iter().any(|member| member.contains(pattern))
44	}
45
46	/// Whether every member of `other` is [contained](Self::contains) here: `other`
47	/// grants nothing this union does not. The empty union is covered by anything.
48	pub fn covers(&self, other: &Self) -> bool {
49		other.0.iter().all(|pattern| self.contains(pattern))
50	}
51
52	/// Whether any member overlaps `pattern`.
53	pub fn overlaps(&self, pattern: &Pattern) -> bool {
54		self.0.iter().any(|member| member.overlaps(pattern))
55	}
56
57	/// The paths in both unions, as one union: every member of this one intersected
58	/// with every member of `other`. See [`Pattern::intersect`].
59	pub fn intersect(&self, other: &Self) -> Result<Self, IntersectionError> {
60		let mut out = Self::new();
61		for member in &self.0 {
62			for candidate in &other.0 {
63				for pattern in member.intersect(candidate)? {
64					out.insert(pattern);
65					if out.len() > Pattern::MAX_INTERSECTIONS {
66						return Err(IntersectionError::TooManyPatterns);
67					}
68				}
69			}
70		}
71		Ok(out)
72	}
73
74	/// Every member rebased at `root`, as one union. See [`Pattern::rebase`].
75	pub fn rebase(&self, root: &str) -> Self {
76		self.0.iter().flat_map(|member| member.rebase(root)).collect()
77	}
78
79	/// Every member placed beneath `root`. See [`Pattern::rooted`].
80	pub fn rooted(&self, root: &str) -> Result<Self, InvalidPattern> {
81		self.0.iter().map(|member| member.rooted(root)).collect()
82	}
83
84	/// The members, in canonical order.
85	pub fn iter(&self) -> std::slice::Iter<'_, Pattern> {
86		self.0.iter()
87	}
88
89	/// The number of members.
90	pub fn len(&self) -> usize {
91		self.0.len()
92	}
93
94	/// Whether there are no members, so nothing matches.
95	pub fn is_empty(&self) -> bool {
96		self.0.is_empty()
97	}
98}
99
100#[cfg(feature = "serde")]
101impl serde::Serialize for Patterns {
102	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
103		serializer.collect_seq(self.iter())
104	}
105}
106
107#[cfg(feature = "serde")]
108impl<'de> serde::Deserialize<'de> for Patterns {
109	/// Reads a list and reduces it, so a persisted union is canonical after a round trip.
110	fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
111		Ok(Vec::<Pattern>::deserialize(deserializer)?.into_iter().collect())
112	}
113}
114
115impl std::ops::Deref for Patterns {
116	type Target = [Pattern];
117
118	fn deref(&self) -> &[Pattern] {
119		&self.0
120	}
121}
122
123impl From<Pattern> for Patterns {
124	fn from(pattern: Pattern) -> Self {
125		Self(vec![pattern])
126	}
127}
128
129impl FromIterator<Pattern> for Patterns {
130	fn from_iter<I: IntoIterator<Item = Pattern>>(iter: I) -> Self {
131		let mut set = Self::new();
132		set.extend(iter);
133		set
134	}
135}
136
137impl Extend<Pattern> for Patterns {
138	fn extend<I: IntoIterator<Item = Pattern>>(&mut self, iter: I) {
139		for pattern in iter {
140			self.insert(pattern);
141		}
142	}
143}
144
145impl IntoIterator for Patterns {
146	type Item = Pattern;
147	type IntoIter = std::vec::IntoIter<Pattern>;
148
149	fn into_iter(self) -> Self::IntoIter {
150		self.0.into_iter()
151	}
152}
153
154impl<'a> IntoIterator for &'a Patterns {
155	type Item = &'a Pattern;
156	type IntoIter = std::slice::Iter<'a, Pattern>;
157
158	fn into_iter(self) -> Self::IntoIter {
159		self.0.iter()
160	}
161}
162
163#[cfg(test)]
164mod tests {
165	use super::*;
166
167	fn pattern(text: &str) -> Pattern {
168		text.parse().unwrap()
169	}
170
171	fn patterns(texts: &[&str]) -> Patterns {
172		texts.iter().map(|t| pattern(t)).collect()
173	}
174
175	#[test]
176	fn insert_reduces_by_containment() {
177		let mut set = Patterns::new();
178		assert!(set.insert(pattern("a/b")));
179		assert!(set.insert(pattern("a/c")));
180		assert!(!set.insert(pattern("a/b")));
181		assert!(set.insert(pattern("a/*")));
182		assert_eq!(set, patterns(&["a/*"]));
183		assert!(!set.insert(pattern("a/d")));
184		assert!(set.insert(pattern("**/x")));
185		assert_eq!(set.len(), 2);
186		assert!(set.insert(pattern("**")));
187		assert_eq!(set, patterns(&["**"]));
188	}
189
190	#[test]
191	fn equality_is_set_equality() {
192		assert_eq!(patterns(&["a", "b"]), patterns(&["b", "a"]));
193		assert_eq!(patterns(&["a", "a/**", "b"]), patterns(&["b", "a/**"]));
194		assert_ne!(patterns(&["a"]), patterns(&["a", "b"]));
195	}
196
197	#[test]
198	fn joint_coverage_is_not_containment() {
199		let set = patterns(&["a", "a/*", "a/*/**"]);
200		assert!(!set.contains(&pattern("a/**")));
201		assert!(set.contains(&pattern("a/x/**")));
202		assert!(set.covers(&patterns(&["a", "a/x/y"])));
203		assert!(!set.covers(&patterns(&["a", "b"])));
204		assert!(set.covers(&Patterns::new()));
205		assert!(!Patterns::new().covers(&set));
206	}
207
208	#[test]
209	fn matches_and_overlaps_any_member() {
210		let set = patterns(&["a/**", "**/c"]);
211		assert!(set.matches("a"));
212		assert!(set.matches("x/c"));
213		assert!(!set.matches("x/y"));
214		assert!(set.overlaps(&pattern("*/c")));
215		assert!(!set.overlaps(&pattern("b/d")));
216		assert!(!Patterns::new().matches(""));
217	}
218
219	#[cfg(feature = "serde")]
220	#[test]
221	fn serde_round_trips_reduced() {
222		let set: Patterns = serde_json::from_str(r#"["a/b", "a/*", "**/c"]"#).unwrap();
223		assert_eq!(serde_json::to_string(&set).unwrap(), r#"["**/c","a/*"]"#);
224	}
225
226	#[test]
227	fn intersect_is_pairwise() {
228		let grant = patterns(&["a/*", "b/**"]);
229		let claim = patterns(&["*/c", "b/d/**"]);
230		assert_eq!(grant.intersect(&claim).unwrap(), patterns(&["a/c", "b/c", "b/d/**"]));
231		assert!(grant.intersect(&patterns(&["x"])).unwrap().is_empty());
232		assert!(Patterns::new().intersect(&grant).unwrap().is_empty());
233	}
234
235	#[test]
236	fn rebase_and_rooted_round_trip() {
237		let set = patterns(&["**/a", "b/**"]);
238		assert_eq!(set.rebase("a"), patterns(&["", "**/a"]));
239		assert_eq!(set.rebase("b/c"), patterns(&["**"]));
240		assert_eq!(set.rebase("x"), patterns(&["**/a"]));
241		assert_eq!(set.rooted("r").unwrap(), patterns(&["r/**/a", "r/b/**"]));
242		assert!(set.rooted("*").is_err());
243	}
244}