iri-string 0.7.2

IRI as string types
Documentation
//! Syntax components of URI templates.

use core::mem;

use crate::parser::str::find_split_hole;
use crate::template::error::Error;
use crate::template::parser::validate as validate_parser;

/// Expression body.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct ExprBody<'a>(&'a str);

impl<'a> ExprBody<'a> {
    /// Creates a new expression body.
    ///
    /// # Precondition
    ///
    /// The given string should be a valid expression body.
    #[inline]
    #[must_use]
    pub(super) fn new(s: &'a str) -> Self {
        debug_assert!(
            !s.is_empty(),
            "[precondition] valid expression body is not empty"
        );

        Self(s)
    }

    /// Decomposes the expression into an `operator` and `variable-list`.
    ///
    /// # Panics
    ///
    /// May panic if the input is invalid.
    #[must_use]
    pub(super) fn decompose(&self) -> (Operator, VarListStr<'a>) {
        debug_assert!(
            !self.0.is_empty(),
            "[precondition] valid expression body is not empty"
        );
        let first = self.0.as_bytes()[0];
        if first.is_ascii_alphanumeric() || (first == b'_') || (first == b'%') {
            // The first byte is a part of the variable list.
            (Operator::String, VarListStr::new(self.0))
        } else {
            let op = Operator::from_byte(first).unwrap_or_else(|| {
                unreachable!(
                    "[precondition] valid expression has (optional) \
                     valid operator, but got a byte {first:#02x?}"
                )
            });
            (op, VarListStr::new(&self.0[1..]))
        }
    }
}

/// Variable name.
// QUESTION: Should hexdigits in percent-encoded triplets be compared case sensitively?
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VarName<'a>(&'a str);

impl<'a> VarName<'a> {
    /// Creates a `VarName` from the trusted string.
    ///
    /// # Precondition
    ///
    /// The given string should be a valid variable name.
    #[inline]
    #[must_use]
    pub(super) fn from_trusted(s: &'a str) -> Self {
        Self(s)
    }

    /// Creates a `VarName` from the string.
    ///
    /// # Examples
    ///
    /// ```
    /// # use iri_string::template::Error;
    /// use iri_string::template::context::VarName;
    ///
    /// let name = VarName::new("hello")?;
    /// assert_eq!(name.as_str(), "hello");
    ///
    /// assert!(VarName::new("0+non-variable-name").is_err());
    ///
    /// # Ok::<_, Error>(())
    /// ```
    #[inline]
    pub fn new(s: &'a str) -> Result<Self, Error> {
        match validate_parser::validate_varname(s, 0) {
            Ok(_) => Ok(Self::from_trusted(s)),
            Err(e) => Err(e),
        }
    }

    /// Returns the varibale name.
    #[inline]
    #[must_use]
    pub fn as_str(&self) -> &'a str {
        self.0
    }
}

/// Variable specifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VarSpec<'a> {
    /// Variable name.
    name: VarName<'a>,
    /// Variable modifier.
    modifier: Modifier,
}

impl<'a> VarSpec<'a> {
    /// Returns the varibale name.
    #[inline]
    #[must_use]
    pub(super) fn name(&self) -> VarName<'a> {
        self.name
    }

    /// Returns the modifier.
    #[inline]
    #[must_use]
    pub(super) fn modifier(&self) -> Modifier {
        self.modifier
    }

    /// Parses the trusted varspec string.
    ///
    /// # Panics
    ///
    /// May panic if the input is invalid.
    #[must_use]
    pub(super) fn parse_trusted(s: &'a str) -> Self {
        if let Some(varname) = s.strip_suffix('*') {
            // `varname "*"`.
            return Self {
                name: VarName::from_trusted(varname),
                modifier: Modifier::Explode,
            };
        }
        // `varname ":" max-length` or `varname`.
        match find_split_hole(s, b':') {
            Some((varname, max_len)) => {
                let max_len: u16 = max_len
                    .parse()
                    .expect("[precondition] the input should be valid `varspec`");
                Self {
                    name: VarName::from_trusted(varname),
                    modifier: Modifier::MaxLen(max_len),
                }
            }
            None => Self {
                name: VarName(s),
                modifier: Modifier::None,
            },
        }
    }
}

/// Variable list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct VarListStr<'a>(&'a str);

impl<'a> VarListStr<'a> {
    /// Creates a new variable list.
    ///
    /// # Precondition
    ///
    /// The given string should be a valid variable list.
    #[inline]
    #[must_use]
    pub(super) fn new(s: &'a str) -> Self {
        Self(s)
    }
}

impl<'a> IntoIterator for VarListStr<'a> {
    type IntoIter = VarListIter<'a>;
    type Item = VarSpec<'a>;

    #[inline]
    fn into_iter(self) -> Self::IntoIter {
        VarListIter { rest: self.0 }
    }
}

/// Iterator of variable specs.
#[derive(Debug, Clone)]
pub(super) struct VarListIter<'a> {
    /// Remaining input.
    rest: &'a str,
}

impl<'a> Iterator for VarListIter<'a> {
    type Item = VarSpec<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        match find_split_hole(self.rest, b',') {
            Some((prefix, new_rest)) => {
                self.rest = new_rest;
                Some(VarSpec::parse_trusted(prefix))
            }
            None => {
                if self.rest.is_empty() {
                    None
                } else {
                    Some(VarSpec::parse_trusted(mem::take(&mut self.rest)))
                }
            }
        }
    }
}

/// Variable modifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum Modifier {
    /// No modifiers.
    None,
    /// Max length, greater than 0 and less than 10000.
    MaxLen(u16),
    /// Explode the variable, e.g. the var spec has `*`.
    Explode,
}

/// Operator that is possibly reserved for future extension.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum MaybeOperator {
    /// Working operator.
    Operator(Operator),
    /// Reserved for future extensions.
    Reserved(OperatorReservedForFuture),
}

impl MaybeOperator {
    /// Returns the operator for the given character.
    pub(super) fn from_byte(b: u8) -> Option<Self> {
        match b {
            b'+' => Some(Self::Operator(Operator::Reserved)),
            b'#' => Some(Self::Operator(Operator::Fragment)),
            b'.' => Some(Self::Operator(Operator::Label)),
            b'/' => Some(Self::Operator(Operator::PathSegments)),
            b';' => Some(Self::Operator(Operator::PathParams)),
            b'?' => Some(Self::Operator(Operator::FormQuery)),
            b'&' => Some(Self::Operator(Operator::FormQueryCont)),
            b'=' => Some(Self::Reserved(OperatorReservedForFuture::Equals)),
            b',' => Some(Self::Reserved(OperatorReservedForFuture::Comma)),
            b'!' => Some(Self::Reserved(OperatorReservedForFuture::Exclamation)),
            b'@' => Some(Self::Reserved(OperatorReservedForFuture::AtSign)),
            b'|' => Some(Self::Reserved(OperatorReservedForFuture::Pipe)),
            _ => None,
        }
    }
}

/// Working operator.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum Operator {
    /// No operator. String expansion.
    String,
    /// Reserved expansion by `+`.
    Reserved,
    /// Fragment expansion by `#`.
    Fragment,
    /// Label expansion by `.`.
    Label,
    /// Path segments by `/`.
    PathSegments,
    /// Path-style parameters by `;`.
    PathParams,
    /// Form-style query by `?`.
    FormQuery,
    /// Form-style query continuation by `&`.
    FormQueryCont,
}

impl Operator {
    /// Returns the operator for the given character.
    #[must_use]
    pub(super) fn from_byte(b: u8) -> Option<Self> {
        match b {
            b'+' => Some(Self::Reserved),
            b'#' => Some(Self::Fragment),
            b'.' => Some(Self::Label),
            b'/' => Some(Self::PathSegments),
            b';' => Some(Self::PathParams),
            b'?' => Some(Self::FormQuery),
            b'&' => Some(Self::FormQueryCont),
            _ => None,
        }
    }
}

/// Operator reserved for future extension.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum OperatorReservedForFuture {
    /// Reserved `=` operator.
    Equals,
    /// Reserved `,` operator.
    Comma,
    /// Reserved `!` operator.
    Exclamation,
    /// Reserved `@` operator.
    AtSign,
    /// Reserved `|` operator.
    Pipe,
}