surrealdb_core/api/
path.rs

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