moq_proto/message/
filter.rs

1use std::fmt;
2
3use crate::coding::{Decode, DecodeError, Encode};
4
5#[derive(Debug, Clone)]
6pub enum Filter {
7	// Allow all paths.
8	Any,
9
10	// Match an exact string.
11	Exact(String),
12
13	// Match a string prefix.
14	Prefix(String),
15
16	// Match a string suffix.
17	Suffix(String),
18
19	// Match a string with a wildcard in the middle.
20	// The capture may be empty.
21	Wildcard { prefix: String, suffix: String },
22}
23
24impl Filter {
25	pub fn new(pattern: &str) -> Self {
26		if pattern.is_empty() {
27			return Self::Any;
28		}
29
30		if let Some((prefix, suffix)) = pattern.split_once("*") {
31			return match (prefix.is_empty(), suffix.is_empty()) {
32				(true, true) => Self::Any,
33				(true, false) => Self::Suffix(suffix.to_string()),
34				(false, true) => Self::Prefix(prefix.to_string()),
35				(false, false) => Self::Wildcard {
36					prefix: prefix.to_string(),
37					suffix: suffix.to_string(),
38				},
39			};
40		}
41
42		Self::Exact(pattern.to_string())
43	}
44
45	/// Check if the input matches the filter.
46	///
47	/// Returns a [FilterMatch] that contains both the captured wildcard and the full match.
48	pub fn matches<'a>(&self, input: &'a str) -> Option<FilterMatch<'a>> {
49		match self {
50			Self::Any => Some(FilterMatch {
51				full: input,
52				capture: (0, input.len()),
53			}),
54			Self::Exact(pattern) if input == pattern => Some(FilterMatch {
55				full: input,
56				capture: (0, 0),
57			}),
58			Self::Prefix(prefix) if input.starts_with(prefix) => Some(FilterMatch {
59				full: input,
60				capture: (prefix.len(), input.len()),
61			}),
62			Self::Suffix(suffix) if input.ends_with(suffix) => Some(FilterMatch {
63				full: input,
64				capture: (0, input.len() - suffix.len()),
65			}),
66			Self::Wildcard { prefix, suffix }
67				if input.len() >= prefix.len() + suffix.len()
68					&& input.starts_with(prefix)
69					&& input.ends_with(suffix) =>
70			{
71				Some(FilterMatch {
72					full: input,
73					capture: (prefix.len(), input.len() - suffix.len()),
74				})
75			}
76			_ => None,
77		}
78	}
79
80	// Given a capture, reconstructs the full path.
81	pub fn reconstruct(&self, capture: &str) -> String {
82		match self {
83			Self::Any => capture.to_string(),
84			Self::Exact(pattern) => pattern.to_string(),
85			Self::Prefix(prefix) => format!("{}{}", prefix, capture),
86			Self::Suffix(suffix) => format!("{}{}", capture, suffix),
87			Self::Wildcard { prefix, suffix } => format!("{}{}{}", prefix, capture, suffix),
88		}
89	}
90}
91
92impl<T: AsRef<str>> From<T> for Filter {
93	fn from(pattern: T) -> Self {
94		Self::new(pattern.as_ref())
95	}
96}
97
98impl Encode for Filter {
99	fn encode<W: bytes::BufMut>(&self, w: &mut W) {
100		match self {
101			Self::Any => {
102				0u64.encode(w);
103			}
104			Self::Exact(pattern) => {
105				pattern.encode(w);
106			}
107			Self::Prefix(prefix) => {
108				(prefix.len() + 1).encode(w);
109				w.put(prefix.as_bytes());
110				w.put(&b"*"[..]);
111			}
112			Self::Suffix(suffix) => {
113				(suffix.len() + 1).encode(w);
114				w.put(&b"*"[..]);
115				w.put(suffix.as_bytes());
116			}
117			Self::Wildcard { prefix, suffix } => {
118				(prefix.len() + suffix.len() + 1).encode(w);
119				w.put(prefix.as_bytes());
120				w.put(&b"*"[..]);
121				w.put(suffix.as_bytes());
122			}
123		}
124	}
125}
126
127impl Decode for Filter {
128	fn decode<R: bytes::Buf>(r: &mut R) -> Result<Self, DecodeError> {
129		let pattern = String::decode(r)?;
130		Ok(Self::new(&pattern))
131	}
132}
133
134#[cfg(test)]
135impl Filter {
136	fn assert(&self, input: &str, expected: Option<&str>) {
137		let fm = self.matches(input).map(|r| r.capture());
138		assert_eq!(fm, expected);
139	}
140}
141
142#[derive(PartialEq, Eq)]
143pub struct FilterMatch<'a> {
144	full: &'a str,
145	// An index into the string.
146	capture: (usize, usize),
147}
148
149impl<'a> FilterMatch<'a> {
150	pub fn full(&self) -> &'a str {
151		self.full
152	}
153
154	pub fn capture(&self) -> &'a str {
155		&self.full[self.capture.0..self.capture.1]
156	}
157
158	/// Returns the (start..end) index of the capture
159	pub fn capture_index(&self) -> (usize, usize) {
160		self.capture
161	}
162}
163
164impl fmt::Debug for FilterMatch<'_> {
165	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166		f.debug_struct("FilterMatch")
167			.field("full", &self.full())
168			.field("capture", &self.capture())
169			.finish()
170	}
171}
172
173#[cfg(test)]
174mod test {
175	use super::*;
176
177	#[test]
178	fn prefix() {
179		let filter = Filter::new("*/bar/baz");
180		filter.assert("foo/bar/baz", Some("foo"));
181		filter.assert("foo/bar/", None);
182		filter.assert("foo/bar/baz/qux", None);
183		filter.assert("zoo/bar/baz", Some("zoo"));
184	}
185
186	#[test]
187	fn middle() {
188		let filter = Filter::new("foo/*/baz");
189		filter.assert("foo/bar/baz", Some("bar"));
190		filter.assert("foo/bar/", None);
191		filter.assert("foo/bar/baz/qux", None);
192		filter.assert("zoo/bar/baz", None);
193	}
194
195	#[test]
196	fn suffix() {
197		let filter = Filter::new("foo/bar/*");
198		filter.assert("foo/bar/baz", Some("baz"));
199		filter.assert("foo/bar/", Some(""));
200		filter.assert("foo/bar/baz/qux", Some("baz/qux"));
201		filter.assert("zoo/bar/baz", None);
202	}
203
204	#[test]
205	fn literal() {
206		let filter = Filter::new("foo/bar/baz");
207		filter.assert("foo/bar/baz", Some(""));
208		filter.assert("foo/bar/", None);
209		filter.assert("foo/bar/baz/qux", None);
210		filter.assert("zoo/bar/baz", None);
211	}
212}