surrealdb_core/api/
path.rs

1use std::fmt::{self, Display, Formatter};
2use std::ops::Deref;
3use std::str::FromStr;
4
5use revision::revisioned;
6use serde::{Deserialize, Serialize};
7
8use crate::err::Error;
9use crate::expr::Kind;
10use crate::expr::fmt::{Fmt, fmt_separated_by};
11use crate::syn;
12use crate::val::{Array, Object, Strand, Value};
13
14#[revisioned(revision = 1)]
15#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
16pub struct Path(pub Vec<Segment>);
17
18impl<'a> Path {
19	/// Attempts to fit a passed URL into a already parsed Path Segments.
20	/// A segment can be fixed, be a dynamic variable or a collect the rest of
21	/// the url Considering path the parsed path of an API, and url the current
22	/// subject, this method:
23	///  - iterates over each path segment (divided by `/`)
24	///  - attempts to to match against url segment
25	///  - extracting variables where instructed by the path segment
26	///  - when we no longer match, or when the url is to short, we return None
27	///  - when the url is too long and there is no rest segment, we return None
28	pub fn fit(&'a self, segments: &'a [&'a str]) -> Option<Object> {
29		let mut obj = Object::default();
30		for (i, segment) in self.iter().enumerate() {
31			if let Some(res) = segment.fit(&segments[i..]) {
32				if let Some((k, v)) = res {
33					obj.insert(k, v);
34				}
35			} else {
36				return None;
37			}
38		}
39
40		if segments.len() == self.len() || matches!(self.last(), Some(Segment::Rest(_))) {
41			Some(obj)
42		} else {
43			None
44		}
45	}
46
47	pub fn specificity(&self) -> u8 {
48		self.iter().map(|s| s.specificity()).sum()
49	}
50}
51
52impl From<Vec<Segment>> for Path {
53	fn from(segments: Vec<Segment>) -> Self {
54		Path(segments)
55	}
56}
57
58impl Deref for Path {
59	type Target = Vec<Segment>;
60	fn deref(&self) -> &Self::Target {
61		&self.0
62	}
63}
64
65impl IntoIterator for Path {
66	type Item = Segment;
67	type IntoIter = std::vec::IntoIter<Self::Item>;
68	fn into_iter(self) -> Self::IntoIter {
69		self.0.into_iter()
70	}
71}
72
73impl Display for Path {
74	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
75		write!(f, "/")?;
76		Display::fmt(&Fmt::new(self.iter(), fmt_separated_by("/")), f)
77	}
78}
79
80impl FromStr for Path {
81	type Err = Error;
82	fn from_str(s: &str) -> Result<Self, Self::Err> {
83		if s.is_empty() {
84			return Err(Error::InvalidPath("Path cannot be empty".into()));
85		}
86
87		let mut chars = s.chars().peekable();
88		let mut segments: Vec<Segment> = Vec::new();
89
90		while let Some(c) = chars.next() {
91			if c != '/' {
92				return Err(Error::InvalidPath("Segment should start with /".into()));
93			}
94
95			let mut scratch = String::new();
96			let mut kind: Option<Kind> = None;
97
98			'segment: while let Some(c) = chars.peek() {
99				match c {
100					'/' if scratch.is_empty() => {
101						chars.next();
102						continue 'segment;
103					}
104
105					// We allow the first character to be an escape character to ignore potential
106					// otherwise instruction characters
107					'\\' if scratch.is_empty() => {
108						chars.next();
109						if let Some(x @ ':' | x @ '*') = chars.next() {
110							scratch.push('\\');
111							scratch.push(x);
112							continue 'segment;
113						} else {
114							return Err(Error::InvalidPath("Expected an instruction symbol `:` or `*` to follow after an escape character".into()));
115						}
116					}
117
118					// Valid segment characters
119					x if x.is_ascii_alphanumeric() => (),
120					'.' | '-' | '_' | '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+'
121					| ',' | ';' | '=' | ':' | '@' => (),
122
123					// We found a kind
124					'<' if scratch.starts_with(':') => {
125						if scratch.len() == 1 {
126							return Err(Error::InvalidPath(
127								"Encountered a type, but expected a name or content for this segment first".into(),
128							));
129						}
130
131						// Eat the '<'
132						chars.next();
133
134						let mut balance = 0;
135						let mut inner = String::new();
136
137						'kind: loop {
138							let Some(c) = chars.next() else {
139								return Err(Error::InvalidPath(
140									"Kind segment did not close".into(),
141								));
142							};
143
144							// Keep track of the balance
145							if c == '<' {
146								balance += 1;
147							} else if c == '>' {
148								if balance == 0 {
149									break 'kind;
150								} else {
151									balance -= 1;
152								}
153							}
154
155							inner.push(c);
156						}
157
158						kind = Some(
159							syn::kind(&inner)
160								.map_err(|e| Error::InvalidPath(e.to_string()))?
161								.into(),
162						);
163
164						break 'segment;
165					}
166
167					// We did not encounter a valid character
168					_ => {
169						break 'segment;
170					}
171				}
172
173				if let Some(c) = chars.next() {
174					scratch.push(c);
175				} else {
176					return Err(Error::Unreachable(
177						"Expected to find a character as we peeked it before".into(),
178					));
179				}
180			}
181
182			let (segment, done) = if scratch.is_empty() {
183				break;
184			} else if (scratch.starts_with(':')
185				|| scratch.starts_with('*')
186				|| scratch.starts_with('\\'))
187				&& scratch[1..].is_empty()
188			{
189				// We encountered a segment which starts with an instruction, but is empty
190				// Let's error
191				return Err(Error::InvalidPath(
192					"Expected a name or content for this segment".into(),
193				));
194			} else if let Some(name) = scratch.strip_prefix(':') {
195				let segment = Segment::Dynamic(name.to_string(), kind);
196				(segment, false)
197			} else if let Some(name) = scratch.strip_prefix('*') {
198				let segment = Segment::Rest(name.to_string());
199				(segment, true)
200			} else if let Some(name) = scratch.strip_prefix('\\') {
201				let segment = Segment::Fixed(name.to_string());
202				(segment, false)
203			} else {
204				let segment = Segment::Fixed(scratch.to_string());
205				(segment, false)
206			};
207
208			segments.push(segment);
209
210			if done {
211				break;
212			}
213		}
214
215		if chars.peek().is_some() {
216			return Err(Error::InvalidPath("Path not finished".into()));
217		}
218
219		if segments.len() > MAX_PATH_SEGMENTS as usize {
220			return Err(Error::InvalidPath(format!(
221				"Path cannot have more than {MAX_PATH_SEGMENTS} segments"
222			)));
223		}
224
225		Ok(Self(segments))
226	}
227}
228
229#[revisioned(revision = 1)]
230#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
231pub enum Segment {
232	Fixed(String),
233	Dynamic(String, Option<Kind>),
234	Rest(String),
235}
236
237pub const MAX_PATH_SPECIFICITY: u8 = 255;
238pub const MAX_PATH_SEGMENTS: u8 = MAX_PATH_SPECIFICITY / 3; // 3 is the maximum specificity of a segment
239
240impl Segment {
241	fn fit(&self, segments: &[&str]) -> Option<Option<(String, Value)>> {
242		if let Some(current) = segments.first() {
243			match self {
244				Self::Fixed(x) if x == current => Some(None),
245				Self::Dynamic(x, k) => {
246					let val: Value = current.to_owned().into();
247					let val: Option<Value> = match k {
248						None => Some(val),
249						Some(k) => val.cast_to_kind(k).ok(),
250					};
251
252					val.map(|val| Some((x.to_owned(), val)))
253				}
254				Self::Rest(x) => {
255					// TODO: Null byte validity
256					let values = segments
257						.iter()
258						.copied()
259						.map(|x| Value::Strand(Strand::new(x.to_owned()).unwrap()))
260						.collect::<Vec<_>>();
261
262					Some(Some((x.to_owned(), Value::Array(Array(values)))))
263				}
264				_ => None,
265			}
266		} else {
267			None
268		}
269	}
270
271	fn specificity(&self) -> u8 {
272		match self {
273			Self::Fixed(_) => 3,
274			Self::Dynamic(_, _) => 2,
275			Self::Rest(_) => 1,
276		}
277	}
278}
279
280impl Display for Segment {
281	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
282		match self {
283			Self::Fixed(v) => write!(f, "{v}"),
284			Self::Dynamic(v, k) => {
285				write!(f, ":{v}")?;
286				if let Some(k) = k {
287					write!(f, "<{k}>")?;
288				}
289
290				Ok(())
291			}
292			Self::Rest(v) => write!(f, "*{v}"),
293		}
294	}
295}