use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::error::{ParseError, ParseErrorKind};
use crate::grammar::Grammar;
use crate::segment::Segment;
use crate::variable::Variable;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathTemplate<'a> {
segments: Box<[Segment<'a>]>,
verb: Option<&'a str>,
}
impl<'a> PathTemplate<'a> {
pub fn parse(template: &'a str, grammar: Grammar) -> Result<Self, ParseError> {
Self::parse_inner(template, grammar)
}
fn parse_inner(template: &'a str, grammar: Grammar) -> Result<Self, ParseError> {
if template.as_bytes().first() != Some(&b'/') {
return Err(ParseError::new(ParseErrorKind::MissingLeadingSlash));
}
let (body, verb) = split_verb(template)?;
let rest = &body[1..];
let segments = if rest.is_empty() {
Box::default()
} else {
split_and_parse_segments(rest, grammar.segment_affixes())?
};
validate_rest_position(&segments)?;
Ok(Self { segments, verb })
}
#[must_use]
pub fn segments(&self) -> &[Segment<'a>] {
&self.segments
}
#[must_use]
pub fn verb(&self) -> Option<&'a str> {
self.verb
}
}
impl core::fmt::Display for PathTemplate<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("/")?;
for (idx, segment) in self.segments.iter().enumerate() {
if idx != 0 {
f.write_str("/")?;
}
write_segment(f, segment)?;
}
if let Some(verb) = &self.verb {
write!(f, ":{verb}")?;
}
Ok(())
}
}
fn write_segment(f: &mut core::fmt::Formatter<'_>, segment: &Segment<'_>) -> core::fmt::Result {
match segment {
Segment::Literal(text) => f.write_str(text),
Segment::Single => f.write_str("*"),
Segment::Rest => f.write_str("**"),
Segment::Variable(variable) => write_variable(f, variable),
Segment::Affix { prefix, name, suffix } => {
f.write_str(prefix)?;
f.write_str("{")?;
f.write_str(name)?;
f.write_str("}")?;
f.write_str(suffix)
}
}
}
fn write_variable(f: &mut core::fmt::Formatter<'_>, variable: &Variable<'_>) -> core::fmt::Result {
f.write_str("{")?;
f.write_str(variable.field_path())?;
let sub = variable.sub();
if sub != "*" {
f.write_str("=")?;
f.write_str(sub)?;
}
f.write_str("}")
}
fn validate_rest_position(segments: &[Segment]) -> Result<(), ParseError> {
let mut seen_rest = false;
for seg in segments {
match seg {
Segment::Literal(_) | Segment::Single | Segment::Affix { .. } => {
if seen_rest {
return Err(ParseError::new(ParseErrorKind::RestNotLast));
}
}
Segment::Rest => {
if seen_rest {
return Err(ParseError::new(ParseErrorKind::RestNotLast));
}
seen_rest = true;
}
Segment::Variable(var) => {
for sub in var.segments() {
if seen_rest {
return Err(ParseError::new(ParseErrorKind::RestNotLast));
}
if matches!(sub, Segment::Rest) {
seen_rest = true;
}
}
}
}
}
Ok(())
}
fn split_verb(template: &str) -> Result<(&str, Option<&str>), ParseError> {
let mut depth = 0_usize;
let mut verb_at = None;
for (idx, &b) in template.as_bytes().iter().enumerate() {
match b {
b'{' => depth += 1,
b'}' if depth > 0 => depth -= 1,
b'}' if verb_at.is_none() => {
return Err(ParseError::new(ParseErrorKind::UnbalancedBraces));
}
b':' if depth == 0 => {
if verb_at.is_some() {
return Err(ParseError::new(ParseErrorKind::MultipleVerbs));
}
verb_at = Some(idx);
}
_ => {}
}
}
match verb_at {
Some(idx) => {
let verb = &template[idx + 1..];
if verb.is_empty() {
return Err(ParseError::new(ParseErrorKind::EmptyVerb));
}
if !is_valid_literal(verb) {
return Err(ParseError::new(ParseErrorKind::InvalidVerb));
}
Ok((&template[..idx], Some(verb)))
}
None => Ok((template, None)),
}
}
fn split_and_parse_segments(body: &str, extended: bool) -> Result<Box<[Segment<'_>]>, ParseError> {
let mut out = Vec::with_capacity(segment_count_hint(body));
let mut depth = 0_usize;
let mut start = 0_usize;
for (idx, &b) in body.as_bytes().iter().enumerate() {
match b {
b'{' => depth += 1,
b'}' => depth -= 1,
b'/' if depth == 0 => {
push_parsed_segment(&mut out, &body[start..idx], extended)?;
start = idx + 1;
}
_ => {}
}
}
if depth != 0 {
return Err(ParseError::new(ParseErrorKind::UnbalancedBraces));
}
push_parsed_segment(&mut out, &body[start..], extended)?;
Ok(out.into_boxed_slice())
}
#[cfg_attr(test, mutants::skip)]
fn segment_count_hint(body: &str) -> usize {
let mut depth = 0_usize;
let mut count = 1_usize;
for &b in body.as_bytes() {
match b {
b'{' => depth += 1,
b'}' => depth = depth.saturating_sub(1),
b'/' if depth == 0 => count += 1,
_ => {}
}
}
count
}
fn push_parsed_segment<'a>(out: &mut Vec<Segment<'a>>, seg: &'a str, extended: bool) -> Result<(), ParseError> {
if seg.is_empty() {
return Err(ParseError::new(ParseErrorKind::EmptySegment));
}
out.push(parse_segment(seg, extended)?);
Ok(())
}
fn parse_segment(seg: &str, extended: bool) -> Result<Segment<'_>, ParseError> {
if seg == "*" {
return Ok(Segment::Single);
}
if seg == "**" {
return Ok(Segment::Rest);
}
let bytes = seg.as_bytes();
if bytes.first() == Some(&b'{') && bytes.last() == Some(&b'}') {
return parse_variable(&seg[1..seg.len() - 1]);
}
if extended && bytes.contains(&b'{') {
return parse_affix(seg);
}
if !is_valid_literal(seg) {
return Err(ParseError::new(ParseErrorKind::InvalidLiteral));
}
Ok(Segment::Literal(seg))
}
fn parse_affix(seg: &str) -> Result<Segment<'_>, ParseError> {
let mut open = None;
let mut close = None;
let mut braces = 0_usize;
for (idx, &byte) in seg.as_bytes().iter().enumerate() {
match byte {
b'{' => {
braces += 1;
if open.is_none() {
open = Some(idx);
}
}
b'}' => {
braces += 1;
if close.is_none() {
close = Some(idx);
}
}
_ => {}
}
}
if braces != 2 {
return Err(ParseError::new(ParseErrorKind::UnbalancedBraces));
}
let open = open.expect("one '{' counted above");
let close = close.expect("one '}' counted above");
let prefix = &seg[..open];
let field_str = &seg[open + 1..close];
let suffix = &seg[close + 1..];
if !is_valid_literal(prefix) || !is_valid_literal(suffix) {
return Err(ParseError::new(ParseErrorKind::InvalidLiteral));
}
if field_str.is_empty() {
return Err(ParseError::new(ParseErrorKind::EmptyFieldPath));
}
for ident in field_str.as_bytes().split(|&b| b == b'.') {
if !is_valid_ident(ident) {
return Err(ParseError::new(ParseErrorKind::InvalidFieldName));
}
}
Ok(Segment::Affix {
prefix,
name: field_str,
suffix,
})
}
fn parse_variable(inner: &str) -> Result<Segment<'_>, ParseError> {
let (field_str, sub_str) = match inner.as_bytes().iter().position(|&b| b == b'=') {
Some(idx) => (&inner[..idx], Some(&inner[idx + 1..])),
None => (inner, None),
};
if field_str.is_empty() {
return Err(ParseError::new(ParseErrorKind::EmptyFieldPath));
}
for ident in field_str.as_bytes().split(|&b| b == b'.') {
if !is_valid_ident(ident) {
return Err(ParseError::new(ParseErrorKind::InvalidFieldName));
}
}
let sub = match sub_str {
None => "*",
Some(sub) => {
if sub.is_empty() {
return Err(ParseError::new(ParseErrorKind::EmptySegment));
}
for seg in sub.as_bytes().split(|&b| b == b'/') {
if seg.is_empty() {
return Err(ParseError::new(ParseErrorKind::EmptySegment));
}
if seg.contains(&b'{') {
return Err(ParseError::new(ParseErrorKind::NestedVariable));
}
if seg != b"*" && seg != b"**" && !is_valid_literal_bytes(seg) {
return Err(ParseError::new(ParseErrorKind::InvalidLiteral));
}
}
sub
}
};
Ok(Segment::Variable(Variable::new(field_str, sub)))
}
fn is_valid_ident(ident: &[u8]) -> bool {
match ident.first() {
Some(&b) if b == b'_' || b.is_ascii_alphabetic() => {}
_ => return false,
}
ident[1..].iter().all(|&b| b == b'_' || b.is_ascii_alphanumeric())
}
fn is_valid_literal(literal: &str) -> bool {
is_valid_literal_bytes(literal.as_bytes())
}
fn is_valid_literal_bytes(bytes: &[u8]) -> bool {
let mut remaining = bytes;
while let Some((&byte, tail)) = remaining.split_first() {
if byte == b'%' {
let Some((&hi, tail)) = tail.split_first() else {
return false;
};
let Some((&lo, tail)) = tail.split_first() else {
return false;
};
if !hi.is_ascii_hexdigit() || !lo.is_ascii_hexdigit() {
return false;
}
remaining = tail;
continue;
}
if !(byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'-' | b'.' | b'_' | b'~' | b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'+' | b',' | b';' | b'=' | b':' | b'@'
))
{
return false;
}
remaining = tail;
}
true
}
#[cfg(test)]
mod literal_tests {
use super::is_valid_literal_bytes;
#[test]
fn valid_percent_escapes_advance_to_following_bytes() {
assert!(is_valid_literal_bytes(b"%41"));
assert!(is_valid_literal_bytes(b"a%41z"));
assert!(is_valid_literal_bytes(b"%41%42"));
}
}