use std::fmt::{self, Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use revision::{DeserializeRevisioned, Revisioned, SerializeRevisioned};
use surrealdb_types::{SqlFormat, ToSql, write_sql};
use crate::err::Error;
use crate::expr::Kind;
use crate::fmt::{Fmt, fmt_separated_by};
use crate::syn;
use crate::types::PublicKind;
use crate::val::{Array, Object, Value};
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
pub(crate) struct Path(pub Vec<Segment>);
impl<'a> Path {
pub fn fit(&'a self, segments: &'a [&'a str]) -> Option<Object> {
if segments.len() < self.len() && !matches!(self.last(), Some(Segment::Rest(_))) {
return None;
}
let mut obj = Object::default();
for (i, segment) in self.iter().enumerate() {
if let Some(res) = segment.fit(&segments[i..]) {
if let Some((k, v)) = res {
obj.insert(k, v);
}
} else {
return None;
}
}
if segments.len() == self.len() || matches!(self.last(), Some(Segment::Rest(_))) {
Some(obj)
} else {
None
}
}
pub fn specificity(&self) -> u8 {
self.iter().map(|s| s.specificity()).sum()
}
}
impl From<Vec<Segment>> for Path {
fn from(segments: Vec<Segment>) -> Self {
Path(segments)
}
}
impl Deref for Path {
type Target = Vec<Segment>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl IntoIterator for Path {
type Item = Segment;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Display for Path {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.to_sql().fmt(f)
}
}
impl ToSql for Path {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
f.push('/');
Fmt::new(self.iter(), fmt_separated_by("/")).fmt_sql(f, fmt);
}
}
impl FromStr for Path {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(Error::InvalidPath("Path cannot be empty".into()));
}
let mut chars = s.chars().peekable();
let mut segments: Vec<Segment> = Vec::new();
while let Some(c) = chars.next() {
if c != '/' {
return Err(Error::InvalidPath("Segment should start with /".into()));
}
let mut scratch = String::new();
let mut kind: Option<PublicKind> = None;
'segment: while let Some(c) = chars.peek() {
match c {
'/' if scratch.is_empty() => {
chars.next();
continue 'segment;
}
'\\' if scratch.is_empty() => {
chars.next();
if let Some(x @ ':' | x @ '*') = chars.next() {
scratch.push('\\');
scratch.push(x);
continue 'segment;
} else {
return Err(Error::InvalidPath("Expected an instruction symbol `:` or `*` to follow after an escape character".into()));
}
}
x if x.is_ascii_alphanumeric() => (),
'.' | '-' | '_' | '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+'
| ',' | ';' | '=' | ':' | '@' => (),
'<' if scratch.starts_with(':') => {
if scratch.len() == 1 {
return Err(Error::InvalidPath(
"Encountered a type, but expected a name or content for this segment first".into(),
));
}
chars.next();
let mut balance = 0;
let mut inner = String::new();
'kind: loop {
let Some(c) = chars.next() else {
return Err(Error::InvalidPath(
"Kind segment did not close".into(),
));
};
if c == '<' {
balance += 1;
} else if c == '>' {
if balance == 0 {
break 'kind;
} else {
balance -= 1;
}
}
inner.push(c);
}
kind = Some(
syn::kind(&inner)
.map_err(|e| Error::InvalidPath(e.to_string()))?
.into(),
);
break 'segment;
}
_ => {
break 'segment;
}
}
if let Some(c) = chars.next() {
scratch.push(c);
} else {
return Err(Error::Unreachable(
"Expected to find a character as we peeked it before".into(),
));
}
}
let (segment, done) = if scratch.is_empty() {
break;
} else if (scratch.starts_with(':')
|| scratch.starts_with('*')
|| scratch.starts_with('\\'))
&& scratch[1..].is_empty()
{
return Err(Error::InvalidPath(
"Expected a name or content for this segment".into(),
));
} else if let Some(name) = scratch.strip_prefix(':') {
let segment = Segment::Dynamic(name.to_string(), kind.map(Into::into));
(segment, false)
} else if let Some(name) = scratch.strip_prefix('*') {
let segment = Segment::Rest(name.to_string());
(segment, true)
} else if let Some(name) = scratch.strip_prefix('\\') {
let segment = Segment::Fixed(name.to_string());
(segment, false)
} else {
let segment = Segment::Fixed(scratch.clone());
(segment, false)
};
segments.push(segment);
if done {
break;
}
}
if chars.peek().is_some() {
return Err(Error::InvalidPath("Path not finished".into()));
}
if segments.len() > MAX_PATH_SEGMENTS as usize {
return Err(Error::InvalidPath(format!(
"Path cannot have more than {MAX_PATH_SEGMENTS} segments"
)));
}
Ok(Self(segments))
}
}
impl Revisioned for Path {
fn revision() -> u16 {
1
}
}
impl SerializeRevisioned for Path {
fn serialize_revisioned<W: std::io::Write>(
&self,
writer: &mut W,
) -> Result<(), revision::Error> {
SerializeRevisioned::serialize_revisioned(&self.to_string(), writer)
}
}
impl DeserializeRevisioned for Path {
fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, revision::Error> {
let path: String = DeserializeRevisioned::deserialize_revisioned(reader)?;
path.parse().map_err(|err: Error| revision::Error::Conversion(err.to_string()))
}
}
impl revision::SkipRevisioned for Path {
fn skip_revisioned<R: std::io::Read>(reader: &mut R) -> Result<(), revision::Error> {
<String as revision::SkipRevisioned>::skip_revisioned(reader)
}
}
impl revision::WalkRevisioned for Path {
type Walker<'r, R: revision::BorrowedReader + 'r> = revision::LeafWalker<'r, Path, R>;
fn walk_revisioned<'r, R: revision::BorrowedReader>(
reader: &'r mut R,
) -> Result<Self::Walker<'r, R>, revision::Error> {
Ok(revision::LeafWalker::new(reader))
}
}
impl revision::LengthPrefixedBytes for Path {}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum Segment {
Fixed(String),
Dynamic(String, Option<Kind>),
Rest(String),
}
pub const MAX_PATH_SPECIFICITY: u8 = 255;
pub const MAX_PATH_SEGMENTS: u8 = MAX_PATH_SPECIFICITY / 3;
impl Segment {
fn fit(&self, segments: &[&str]) -> Option<Option<(String, Value)>> {
if let Some(current) = segments.first() {
match self {
Self::Fixed(x) if x == current => Some(None),
Self::Dynamic(x, k) => {
let val: Value = (*current).into();
let val: Option<Value> = match k {
None => Some(val),
Some(k) => val.cast_to_kind(k).ok(),
};
val.map(|val| Some((x.to_owned(), val)))
}
Self::Rest(x) => {
let values = segments
.iter()
.copied()
.map(|x| Value::String((*x).into()))
.collect::<Vec<_>>();
Some(Some((x.to_owned(), Value::Array(Array(values)))))
}
_ => None,
}
} else {
None
}
}
fn specificity(&self) -> u8 {
match self {
Self::Fixed(_) => 3,
Self::Dynamic(_, _) => 2,
Self::Rest(_) => 1,
}
}
}
impl ToSql for Segment {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
match self {
Self::Fixed(v) => v.fmt_sql(f, fmt),
Self::Dynamic(v, k) => {
write_sql!(f, fmt, ":{v}");
if let Some(k) = k {
write_sql!(f, fmt, "<{k}>");
}
}
Self::Rest(v) => write_sql!(f, fmt, "*{v}"),
}
}
}
#[cfg(test)]
mod length_prefixed_bytes_tests {
use std::str::FromStr;
use revision::{SerializeRevisioned, WalkRevisioned};
use super::Path;
#[test]
fn path_with_bytes_matches_serialize() {
let path = Path::from_str("/hello/world").unwrap();
let mut bytes = Vec::new();
path.serialize_revisioned(&mut bytes).unwrap();
let wire_text = path.to_string();
let mut r = bytes.as_slice();
let walker = Path::walk_revisioned(&mut r).unwrap();
let observed = walker.with_bytes(|raw| raw.to_vec()).unwrap();
assert_eq!(observed.as_slice(), wire_text.as_bytes());
assert!(r.is_empty());
}
}