diff options
Diffstat (limited to 'vendor/iri-string/src/template')
| -rw-r--r-- | vendor/iri-string/src/template/components.rs | 332 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/context.rs | 339 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/error.rs | 154 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/expand.rs | 1039 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/parser.rs | 6 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/parser/char.rs | 190 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/parser/validate.rs | 161 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/simple_context.rs | 218 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/string.rs | 647 | ||||
| -rw-r--r-- | vendor/iri-string/src/template/string/owned.rs | 296 |
10 files changed, 3382 insertions, 0 deletions
diff --git a/vendor/iri-string/src/template/components.rs b/vendor/iri-string/src/template/components.rs new file mode 100644 index 00000000..7eb83a58 --- /dev/null +++ b/vendor/iri-string/src/template/components.rs @@ -0,0 +1,332 @@ +//! 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. +/// +/// This does not contain the wrapping braces (`{` and `}`). +#[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..])) + } + } + + /// Returns the raw expression in a string slice. + #[inline] + #[must_use] + pub(super) fn as_str(&self) -> &'a str { + self.0 + } +} + +/// 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 = (usize, 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> { + /// A pair of the length of the varspec and the varspec itself. + type Item = (usize, 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((prefix.len(), VarSpec::parse_trusted(prefix))) + } + None => { + if self.rest.is_empty() { + None + } else { + Some(( + self.rest.len(), + 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, + } + } + + /// Returns the string length of the operator. + #[inline] + #[must_use] + pub(super) const fn len(self) -> usize { + if matches!(self, Self::String) { + 0 + } else { + 1 + } + } +} + +/// 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, +} diff --git a/vendor/iri-string/src/template/context.rs b/vendor/iri-string/src/template/context.rs new file mode 100644 index 00000000..ea3f14bb --- /dev/null +++ b/vendor/iri-string/src/template/context.rs @@ -0,0 +1,339 @@ +//! Template expansion context. +//! +//! # Examples +//! +//! 1. Define your context type. +//! 2. Implement [`Context`] trait (and [`Context::visit`] method) for the type. +//! 1. Get variable name by [`Visitor::var_name`] method. +//! 2. Feed the corresponding value(s) by one of `Visitor::visit_*` methods. +//! +//! Note that contexts should return consistent result across multiple visits for +//! the same variable. In other words, `Context::visit` should return the same +//! result for the same `Visitor::var_name()` during the context is borrowed. +//! If this condition is violated, the URI template processor can return +//! invalid result or panic at worst. +//! +//! ``` +//! use iri_string::template::context::{Context, Visitor, ListVisitor, AssocVisitor}; +//! +//! struct MyContext { +//! name: &'static str, +//! id: u64, +//! tags: &'static [&'static str], +//! children: &'static [(&'static str, usize)], +//! } +//! +//! impl Context for MyContext { +//! fn visit<V: Visitor>(&self, visitor: V) -> V::Result { +//! let name = visitor.var_name().as_str(); +//! match name { +//! "name" => visitor.visit_string(self.name), +//! "id" => visitor.visit_string(self.id), +//! "tags" => visitor.visit_list().visit_items_and_finish(self.tags), +//! "children" => visitor +//! .visit_assoc() +//! .visit_entries_and_finish(self.children.iter().copied()), +//! _ => visitor.visit_undefined(), +//! } +//! } +//! } +//! ``` +// +// # Developers note +// +// Visitor types **should not** be cloneable in order to enforce just one +// visitor is used to visit a variable. If visitors are cloneable, it can make +// the wrong usage to be available, i.e. storing cloned visitors somewhere and +// using the wrong one. +// +// However, if visitors are made cloneable by any chance, it does not indicate +// the whole implementation will be broken. Users can only use the visitors +// through visitor traits (and their API do not allow cloning), so the logic +// would work as expected if the internal usage of the visitors are correct. +// Making visitors noncloneable is an optional safety guard (with no overhead). + +use core::fmt; +use core::ops::ControlFlow; + +pub use crate::template::components::VarName; + +/// A trait for types that can behave as a static URI template expansion context. +/// +/// This type is for use with [`UriTemplateStr::expand`] method. +/// +/// See [the module documentation][`crate::template`] for usage. +/// +/// [`UriTemplateStr::expand`]: `crate::template::UriTemplateStr::expand` +pub trait Context: Sized { + /// Visits a variable. + /// + /// To get variable name, use [`Visitor::var_name()`]. + #[must_use] + fn visit<V: Visitor>(&self, visitor: V) -> V::Result; +} + +/// A trait for types that can behave as a dynamic (mutable) URI template expansion context. +/// +/// This type is for use with [`UriTemplateStr::expand_dynamic`] method and its +/// family. +/// +/// Note that "dynamic" here does not mean that the value of variables can +/// change during a template expansion. The value should be fixed and consistent +/// during each expansion, but the context is allowed to mutate itself if it +/// does not break this rule. +/// +/// # Exmaples +/// +/// ``` +/// # #[cfg(feature = "alloc")] +/// # extern crate alloc; +/// # use iri_string::template::Error; +/// # #[cfg(feature = "alloc")] { +/// # use alloc::string::String; +/// use iri_string::template::UriTemplateStr; +/// use iri_string::template::context::{DynamicContext, Visitor, VisitPurpose}; +/// use iri_string::spec::UriSpec; +/// +/// struct MyContext<'a> { +/// /// Target path. +/// target: &'a str, +/// /// Username. +/// username: Option<&'a str>, +/// /// A flag to remember whether the URI template +/// /// attempted to use `username` variable. +/// username_visited: bool, +/// } +/// +/// impl DynamicContext for MyContext<'_> { +/// fn on_expansion_start(&mut self) { +/// // Reset the state. +/// self.username_visited = false; +/// } +/// fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result { +/// match visitor.var_name().as_str() { +/// "target" => visitor.visit_string(self.target), +/// "username" => { +/// if visitor.purpose() == VisitPurpose::Expand { +/// // The variable `username` is being used +/// // on the template expansion. +/// // Don't care whether `username` is defined or not. +/// self.username_visited = true; +/// } +/// if let Some(username) = &self.username { +/// visitor.visit_string(username) +/// } else { +/// visitor.visit_undefined() +/// } +/// } +/// _ => visitor.visit_undefined(), +/// } +/// } +/// } +/// +/// let mut context = MyContext { +/// target: "/posts/1", +/// username: Some("the_admin"), +/// username_visited: false, +/// }; +/// let mut buf = String::new(); +/// +/// // No access to the variable `username`. +/// let template1 = UriTemplateStr::new("{+target}")?; +/// template1.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?; +/// assert_eq!(buf, "/posts/1"); +/// assert!(!context.username_visited); +/// +/// buf.clear(); +/// // Will access to the variable `username`. +/// let template2 = UriTemplateStr::new("{+target}{?username}")?; +/// template2.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?; +/// assert_eq!(buf, "/posts/1?username=the_admin"); +/// assert!(context.username_visited); +/// +/// buf.clear(); +/// context.username = None; +/// // Will access to the variable `username` but it is undefined. +/// template2.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?; +/// assert_eq!(buf, "/posts/1"); +/// assert!( +/// context.username_visited, +/// "`MyContext` can know and remember whether `visit_dynamic()` is called +/// for `username`, even if its value is undefined" +/// ); +/// # } +/// # Ok::<_, Error>(()) +/// ``` +/// +/// [`UriTemplateStr::expand_dynamic`]: `crate::template::UriTemplateStr::expand_dynamic` +pub trait DynamicContext: Sized { + /// Visits a variable. + /// + /// To get variable name, use [`Visitor::var_name()`]. + /// + /// # Restriction + /// + /// The visit results should be consistent and unchanged between the last + /// time [`on_expansion_start`][`Self::on_expansion_start`] was called and + /// the next time [`on_expansion_end`][`Self::on_expansion_end`] will be + /// called. If this condition is violated, template expansion will produce + /// wrong result or may panic at worst. + #[must_use] + fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result; + + /// A callback that is called before the expansion of a URI template. + #[inline] + fn on_expansion_start(&mut self) {} + + /// A callback that is called after the expansion of a URI template. + #[inline] + fn on_expansion_end(&mut self) {} +} + +impl<C: Context> DynamicContext for C { + #[inline] + fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result { + self.visit(visitor) + } +} + +/// A purpose of a visit. +/// +/// This enum is nonexhaustive since this partially exposes the internal +/// implementation of the template expansion, and thus this is subject to +/// change. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum VisitPurpose { + /// A visit for type checking. + Typecheck, + /// A visit for template expansion to retrieve the value. + Expand, +} + +/// Variable visitor. +/// +/// See [the module documentation][self] for usage. +// NOTE (internal): Visitor types **should not** be cloneable. +pub trait Visitor: Sized + private::Sealed { + /// Result of the visit. + type Result; + /// List visitor. + type ListVisitor: ListVisitor<Result = Self::Result>; + /// Associative array visitor. + type AssocVisitor: AssocVisitor<Result = Self::Result>; + + /// Returns the name of the variable to visit. + #[must_use] + fn var_name(&self) -> VarName<'_>; + /// Returns the purpose of the visit. + /// + /// The template expansion algorithm checks the types for some variables + /// depending on its usage. To get the usage count correctly, you should + /// only count visits with [`VisitPurpose::Expand`]. + /// + /// If you need to know whether the variable is accessed and does not + /// need dynamic context generation or access counts, consider using + /// [`UriTemplateStr::variables`] method to iterate the variables in the + /// URI template. + /// + /// [`UriTemplateStr::variables`]: `crate::template::UriTemplateStr::variables` + #[must_use] + fn purpose(&self) -> VisitPurpose; + /// Visits an undefined variable, i.e. indicates that the requested variable is unavailable. + #[must_use] + fn visit_undefined(self) -> Self::Result; + /// Visits a string variable. + #[must_use] + fn visit_string<T: fmt::Display>(self, v: T) -> Self::Result; + /// Visits a list variable. + #[must_use] + fn visit_list(self) -> Self::ListVisitor; + /// Visits an associative array variable. + #[must_use] + fn visit_assoc(self) -> Self::AssocVisitor; +} + +/// List visitor. +/// +/// See [the module documentation][self] for usage. +// NOTE (internal): Visitor types **should not** be cloneable. +pub trait ListVisitor: Sized + private::Sealed { + /// Result of the visit. + type Result; + + /// Visits an item. + /// + /// If this returned `ControlFlow::Break(v)`, [`Context::visit`] should also + /// return this `v`. + /// + /// To feed multiple items at once, do + /// `items.into_iter().try_for_each(|item| self.visit_item(item))` for example. + #[must_use] + fn visit_item<T: fmt::Display>(&mut self, item: T) -> ControlFlow<Self::Result>; + /// Finishes visiting the list. + #[must_use] + fn finish(self) -> Self::Result; + + /// Visits items and finish. + #[must_use] + fn visit_items_and_finish<T, I>(mut self, items: I) -> Self::Result + where + T: fmt::Display, + I: IntoIterator<Item = T>, + { + match items.into_iter().try_for_each(|item| self.visit_item(item)) { + ControlFlow::Break(v) => v, + ControlFlow::Continue(()) => self.finish(), + } + } +} + +/// Associative array visitor. +/// +/// See [the module documentation][self] for usage. +// NOTE (internal): Visitor types **should not** be cloneable. +pub trait AssocVisitor: Sized + private::Sealed { + /// Result of the visit. + type Result; + + /// Visits an entry. + /// + /// If this returned `ControlFlow::Break(v)`, [`Context::visit`] should also + /// return this `v`. + /// + /// To feed multiple items at once, do + /// `entries.into_iter().try_for_each(|(key, value)| self.visit_entry(key, value))` + /// for example. + #[must_use] + fn visit_entry<K: fmt::Display, V: fmt::Display>( + &mut self, + key: K, + value: V, + ) -> ControlFlow<Self::Result>; + /// Finishes visiting the associative array. + #[must_use] + fn finish(self) -> Self::Result; + + /// Visits entries and finish. + #[must_use] + fn visit_entries_and_finish<K, V, I>(mut self, entries: I) -> Self::Result + where + K: fmt::Display, + V: fmt::Display, + I: IntoIterator<Item = (K, V)>, + { + match entries + .into_iter() + .try_for_each(|(key, value)| self.visit_entry(key, value)) + { + ControlFlow::Break(v) => v, + ControlFlow::Continue(()) => self.finish(), + } + } +} + +/// Private module to put the trait to seal. +pub(super) mod private { + /// A trait for visitor types of variables in a context. + pub trait Sealed {} +} diff --git a/vendor/iri-string/src/template/error.rs b/vendor/iri-string/src/template/error.rs new file mode 100644 index 00000000..f5206a4b --- /dev/null +++ b/vendor/iri-string/src/template/error.rs @@ -0,0 +1,154 @@ +//! Errors related to URI templates. + +use core::fmt; + +#[cfg(feature = "std")] +use std::error; + +/// Template construction and expansion error kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ErrorKind { + /// Cannot write to the backend. + WriteFailed, + /// Expression is not closed. + ExpressionNotClosed, + /// Invalid character. + InvalidCharacter, + /// Invalid expression. + InvalidExpression, + /// Invalid percent-encoded triplets. + InvalidPercentEncoding, + /// Invalid UTF-8 bytes. + InvalidUtf8, + /// Unexpected value type for the variable. + UnexpectedValueType, + /// Unsupported operator, including operators reserved for future. + UnsupportedOperator, +} + +impl ErrorKind { + /// Returns the error message. + #[must_use] + fn as_str(self) -> &'static str { + match self { + Self::WriteFailed => "failed to write to the backend writer", + Self::ExpressionNotClosed => "expression not closed", + Self::InvalidCharacter => "invalid character", + Self::InvalidExpression => "invalid expression", + Self::InvalidPercentEncoding => "invalid percent-encoded triplets", + Self::InvalidUtf8 => "invalid utf-8 byte sequence", + Self::UnexpectedValueType => "unexpected value type for the variable", + Self::UnsupportedOperator => "unsupported operator", + } + } +} + +/// Template construction and expansion error. +/// +// Note that this type should implement `Copy` trait. +// To return additional non-`Copy` data as an error, use wrapper type +// (as `std::string::FromUtf8Error` contains `std::str::Utf8Error`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Error { + /// Error kind. + kind: ErrorKind, + /// Location (byte position of the error). + location: usize, +} + +impl Error { + /// Creates a new `Error`. + /// + /// For internal use. + #[inline] + #[must_use] + pub(super) fn new(kind: ErrorKind, location: usize) -> Self { + Self { kind, location } + } + + /// Returns the byte position the error is detected. + /// + /// NOTE: This is not a part of the public API since the value to be + /// returned (i.e., the definition of the "position" of an error) is not + /// guaranteed to be stable. + #[cfg(test)] + pub(super) fn location(&self) -> usize { + self.location + } +} + +impl fmt::Display for Error { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "invalid URI template: {} (at {}-th byte)", + self.kind.as_str(), + self.location + ) + } +} + +#[cfg(feature = "std")] +impl error::Error for Error {} + +/// Error on conversion into a URI template type. +// TODO: Unifiable to `types::CreationError`? +#[cfg(feature = "alloc")] +pub struct CreationError<T> { + /// Soruce data. + source: T, + /// Validation error. + error: Error, +} + +#[cfg(feature = "alloc")] +impl<T> CreationError<T> { + /// Returns the source data. + #[must_use] + pub fn into_source(self) -> T { + self.source + } + + /// Returns the validation error. + #[must_use] + pub fn validation_error(&self) -> Error { + self.error + } + + /// Creates a new `CreationError`. + #[must_use] + pub(crate) fn new(error: Error, source: T) -> Self { + Self { source, error } + } +} + +#[cfg(feature = "alloc")] +impl<T: fmt::Debug> fmt::Debug for CreationError<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CreationError") + .field("source", &self.source) + .field("error", &self.error) + .finish() + } +} + +#[cfg(feature = "alloc")] +impl<T: Clone> Clone for CreationError<T> { + fn clone(&self) -> Self { + Self { + source: self.source.clone(), + error: self.error, + } + } +} + +#[cfg(feature = "alloc")] +impl<T> fmt::Display for CreationError<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.error.fmt(f) + } +} + +#[cfg(feature = "std")] +impl<T: fmt::Debug> error::Error for CreationError<T> {} diff --git a/vendor/iri-string/src/template/expand.rs b/vendor/iri-string/src/template/expand.rs new file mode 100644 index 00000000..605043ab --- /dev/null +++ b/vendor/iri-string/src/template/expand.rs @@ -0,0 +1,1039 @@ +//! Expansion. + +use core::fmt::{self, Write as _}; +use core::marker::PhantomData; +use core::mem; +use core::ops::ControlFlow; + +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; + +use crate::parser::str::{find_split, find_split_hole}; +use crate::parser::str::{process_percent_encoded_best_effort, PctEncodedFragments}; +use crate::percent_encode::PercentEncoded; +use crate::spec::Spec; +use crate::template::components::{ExprBody, Modifier, Operator, VarName, VarSpec}; +use crate::template::context::{ + private::Sealed as VisitorSealed, AssocVisitor, Context, DynamicContext, ListVisitor, + VisitPurpose, Visitor, +}; +use crate::template::error::{Error, ErrorKind}; +use crate::template::{UriTemplateStr, ValueType}; +#[cfg(feature = "alloc")] +use crate::types; + +/// A chunk in a template string. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum Chunk<'a> { + /// Literal. + Literal(&'a str), + /// Expression excluding the wrapping braces. + Expr(ExprBody<'a>), +} + +/// Iterator of template chunks. +#[derive(Debug, Clone)] +pub(super) struct Chunks<'a> { + /// Template. + template: &'a str, +} + +impl<'a> Chunks<'a> { + /// Creates a new iterator. + #[inline] + #[must_use] + pub(super) fn new(template: &'a UriTemplateStr) -> Self { + Self { + template: template.as_str(), + } + } +} + +impl<'a> Iterator for Chunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option<Self::Item> { + if self.template.is_empty() { + return None; + } + match find_split(self.template, b'{') { + Some(("", _)) => { + let (expr_body, rest) = find_split_hole(&self.template[1..], b'}') + .expect("[validity] expression inside a template must be closed"); + self.template = rest; + Some(Chunk::Expr(ExprBody::new(expr_body))) + } + Some((lit, rest)) => { + self.template = rest; + Some(Chunk::Literal(lit)) + } + None => Some(Chunk::Literal(mem::take(&mut self.template))), + } + } +} + +/// Template expansion result. +#[derive(Debug, Clone, Copy)] +pub struct Expanded<'a, S, C> { + /// Compiled template. + template: &'a UriTemplateStr, + /// Context. + context: &'a C, + /// Spec. + _spec: PhantomData<fn() -> S>, +} + +impl<'a, S: Spec, C: Context> Expanded<'a, S, C> { + /// Creates a new `Expanded` object. + #[inline] + pub(super) fn new(template: &'a UriTemplateStr, context: &'a C) -> Result<Self, Error> { + Self::typecheck_context(template, context)?; + Ok(Self { + template, + context, + _spec: PhantomData, + }) + } + + /// Checks if the types of variables are allowed for the corresponding expressions in the template. + fn typecheck_context(template: &UriTemplateStr, context: &C) -> Result<(), Error> { + let mut pos = 0; + for chunk in Chunks::new(template) { + let (expr_len, (op, varlist)) = match chunk { + Chunk::Expr(expr_body) => (expr_body.as_str().len(), expr_body.decompose()), + Chunk::Literal(lit) => { + pos += lit.len(); + continue; + } + }; + // +2: wrapping braces (`{` and `}`). + let chunk_end_pos = pos + expr_len + 2; + // +1: opening brace `{`. + pos += op.len() + 1; + for (varspec_len, varspec) in varlist { + let ty = context.visit(TypeVisitor::new(varspec.name())); + let modifier = varspec.modifier(); + + if matches!(modifier, Modifier::MaxLen(_)) + && matches!(ty, ValueType::List | ValueType::Assoc) + { + // > Prefix modifiers are not applicable to variables that + // > have composite values. + // + // --- [RFC 6570 Section 2.4.1. Prefix](https://www.rfc-editor.org/rfc/rfc6570.html#section-2.4.1) + return Err(Error::new(ErrorKind::UnexpectedValueType, pos)); + } + + // +1: A trailing comman (`,`) or a closing brace (`}`). + pos += varspec_len + 1; + } + assert_eq!(pos, chunk_end_pos); + } + Ok(()) + } +} + +impl<S: Spec, C: Context> fmt::Display for Expanded<'_, S, C> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for chunk in Chunks::new(self.template) { + let expr = match chunk { + Chunk::Literal(lit) => { + f.write_str(lit)?; + continue; + } + Chunk::Expr(body) => body, + }; + expand::<S, _>(f, expr, self.context)?; + } + + Ok(()) + } +} + +/// Implement `TryFrom<Expanded<...>> for SomeUriStringType`. +macro_rules! impl_try_from_expanded { + ($ty_outer:ident) => { + #[cfg(feature = "alloc")] + impl<S: Spec, C: Context> TryFrom<Expanded<'_, S, C>> for types::$ty_outer<S> { + type Error = types::CreationError<String>; + + #[inline] + fn try_from(v: Expanded<'_, S, C>) -> Result<Self, Self::Error> { + Self::try_from(v.to_string()) + } + } + }; +} + +// Not implementing `TryFrom<Expand<...>>` for query and fragment strings +// since they cannot behave as a query or a fragment only by themselves. +// Query strings in practical starts with `?` prefix but `RiQueryStr{,ing}` +// strips that, and so do fragment strings (but `#` instead of `?`). +// Because of this, query and fragment string types won't be used to represent +// a relative IRIs without combining the prefix. +// +// In contrast, RFC 6570 URI Template expects that the users are constructing a +// "working" IRIs, including the necessary prefixes for syntax components. +// For example, fragment expansion `{#var}`, where `var` is "hello", expands to +// `#hello`, including the prefix `#`. This means that a URI template will be +// used to generate neither `RiQueryStr{,ing}` nor `RiFragmentStr{,ing}` strings. +impl_try_from_expanded!(RiAbsoluteString); +impl_try_from_expanded!(RiReferenceString); +impl_try_from_expanded!(RiRelativeString); +impl_try_from_expanded!(RiString); + +/// Expands the whole template with the dynamic context. +pub(super) fn expand_whole_dynamic<S: Spec, W: fmt::Write, C: DynamicContext>( + template: &UriTemplateStr, + writer: &mut W, + context: &mut C, +) -> Result<(), Error> { + context.on_expansion_start(); + let result = expand_whole_dynamic_impl::<S, W, C>(template, writer, context); + context.on_expansion_end(); + result +} + +/// Expands the whole template with the dynamic context. +/// +/// Note that the caller is responsible to set up or finalize the `context`. +fn expand_whole_dynamic_impl<S: Spec, W: fmt::Write, C: DynamicContext>( + template: &UriTemplateStr, + writer: &mut W, + context: &mut C, +) -> Result<(), Error> { + let mut pos = 0; + for chunk in Chunks::new(template) { + let expr = match chunk { + Chunk::Literal(lit) => { + writer + .write_str(lit) + .map_err(|_| Error::new(ErrorKind::WriteFailed, pos))?; + pos += lit.len(); + continue; + } + Chunk::Expr(body) => body, + }; + expand_expr_mut::<S, _, _>(writer, &mut pos, expr, context)?; + } + + Ok(()) +} + +/// Expands the expression using the given operator and the dynamic context. +fn expand_expr_mut<S: Spec, W: fmt::Write, C: DynamicContext>( + writer: &mut W, + pos: &mut usize, + expr: ExprBody<'_>, + context: &mut C, +) -> Result<(), Error> { + let (op, varlist) = expr.decompose(); + + let mut is_first_varspec = true; + // +2: wrapping braces (`{` and `}`). + let chunk_end_pos = *pos + expr.as_str().len() + 2; + // +1: opening brace `{`. + *pos += op.len() + 1; + for (varspec_len, varspec) in varlist { + // Check the type before the actual expansion. + let ty = context.visit_dynamic(TypeVisitor::new(varspec.name())); + let modifier = varspec.modifier(); + + if matches!(modifier, Modifier::MaxLen(_)) + && matches!(ty, ValueType::List | ValueType::Assoc) + { + // > Prefix modifiers are not applicable to variables that + // > have composite values. + // + // --- [RFC 6570 Section 2.4.1. Prefix](https://www.rfc-editor.org/rfc/rfc6570.html#section-2.4.1) + return Err(Error::new(ErrorKind::UnexpectedValueType, *pos)); + } + + // Typecheck passed. Expand. + let visitor = ValueVisitor::<S, _>::new(writer, varspec, op, &mut is_first_varspec); + let token = context + .visit_dynamic(visitor) + .map_err(|_| Error::new(ErrorKind::WriteFailed, *pos))?; + let writer_ptr = token.writer_ptr(); + if writer_ptr != writer as *mut _ { + // Invalid `VisitDoneToken` was returned. This cannot usually happen + // without intentional unnatural usage. + panic!("invalid `VisitDoneToken` was returned"); + } + + // +1: A trailing comman (`,`) or a closing brace (`}`). + *pos += varspec_len + 1; + } + assert_eq!(*pos, chunk_end_pos); + + Ok(()) +} + +/// Properties of an operator. +/// +/// See [RFC 6570 Appendix A](https://www.rfc-editor.org/rfc/rfc6570#appendix-A). +#[derive(Debug, Clone, Copy)] +struct OpProps { + /// Prefix for the first element. + first: &'static str, + /// Separator. + sep: &'static str, + /// Whether or not the expansion includes the variable or key name. + named: bool, + /// Result string if the variable is empty. + ifemp: &'static str, + /// Whether or not the reserved values can be written without being encoded. + allow_reserved: bool, +} + +impl OpProps { + /// Properties for all known operators. + const PROPS: [Self; 8] = [ + // String + Self { + first: "", + sep: ",", + named: false, + ifemp: "", + allow_reserved: false, + }, + // Reserved + Self { + first: "", + sep: ",", + named: false, + ifemp: "", + allow_reserved: true, + }, + // Fragment + Self { + first: "#", + sep: ",", + named: false, + ifemp: "", + allow_reserved: true, + }, + // Label + Self { + first: ".", + sep: ".", + named: false, + ifemp: "", + allow_reserved: false, + }, + // PathSegments + Self { + first: "/", + sep: "/", + named: false, + ifemp: "", + allow_reserved: false, + }, + // PathParams + Self { + first: ";", + sep: ";", + named: true, + ifemp: "", + allow_reserved: false, + }, + // FormQuery + Self { + first: "?", + sep: "&", + named: true, + ifemp: "=", + allow_reserved: false, + }, + // FormQueryCont + Self { + first: "&", + sep: "&", + named: true, + ifemp: "=", + allow_reserved: false, + }, + ]; + + /// Returns the properties for the operator. + #[must_use] + #[inline] + pub(super) fn from_op(op: Operator) -> &'static Self { + let index = match op { + Operator::String => 0, + Operator::Reserved => 1, + Operator::Fragment => 2, + Operator::Label => 3, + Operator::PathSegments => 4, + Operator::PathParams => 5, + Operator::FormQuery => 6, + Operator::FormQueryCont => 7, + }; + &Self::PROPS[index] + } +} + +/// Expands the expression using the given operator. +fn expand<S: Spec, C: Context>( + f: &mut fmt::Formatter<'_>, + expr: ExprBody<'_>, + context: &C, +) -> fmt::Result { + let (op, varlist) = expr.decompose(); + + let mut is_first_varspec = true; + for (_varspec_len, varspec) in varlist { + let visitor = ValueVisitor::<S, _>::new(f, varspec, op, &mut is_first_varspec); + let token = context.visit(visitor)?; + let writer_ptr = token.writer_ptr(); + if writer_ptr != f as *mut _ { + // Invalid `VisitDoneToken` was returned. This cannot usually happen + // without intentional unnatural usage. + panic!("invalid `VisitDoneToken` was returned"); + } + } + + Ok(()) +} + +/// Escapes the given value and writes it. +#[inline] +fn escape_write<S: Spec, T: fmt::Display, W: fmt::Write>( + f: &mut W, + v: T, + allow_reserved: bool, +) -> fmt::Result { + if allow_reserved { + let result = process_percent_encoded_best_effort(v, |frag| { + let result = match frag { + PctEncodedFragments::Char(s, _) => f.write_str(s), + PctEncodedFragments::NoPctStr(s) => { + write!(f, "{}", PercentEncoded::<_, S>::characters(s)) + } + PctEncodedFragments::StrayPercent => f.write_str("%25"), + PctEncodedFragments::InvalidUtf8PctTriplets(s) => f.write_str(s), + }; + if result.is_err() { + return ControlFlow::Break(result); + } + ControlFlow::Continue(()) + }); + match result { + Ok(ControlFlow::Break(Ok(_)) | ControlFlow::Continue(_)) => Ok(()), + Ok(ControlFlow::Break(Err(e))) | Err(e) => Err(e), + } + } else { + /// Writer that escapes the unreserved characters and writes them. + struct UnreservePercentEncodeWriter<'a, S, W> { + /// Inner writer. + writer: &'a mut W, + /// Spec. + _spec: PhantomData<fn() -> S>, + } + impl<S: Spec, W: fmt::Write> fmt::Write for UnreservePercentEncodeWriter<'_, S, W> { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + write!(self.writer, "{}", PercentEncoded::<_, S>::unreserve(s)) + } + } + let mut writer = UnreservePercentEncodeWriter::<S, W> { + writer: f, + _spec: PhantomData, + }; + write!(writer, "{v}") + } +} + +/// Truncates the given value as a string, escapes the value, and writes it. +fn escape_write_with_maxlen<S: Spec, T: fmt::Display, W: fmt::Write>( + writer: &mut PrefixOnceWriter<'_, W>, + v: T, + allow_reserved: bool, + max_len: Option<u16>, +) -> fmt::Result { + if allow_reserved { + let mut max_len = max_len.map_or(usize::MAX, usize::from); + let result = process_percent_encoded_best_effort(v, |frag| { + if max_len == 0 { + return ControlFlow::Break(Ok(())); + } + let result = + match frag { + PctEncodedFragments::Char(s, _) => { + max_len -= 1; + writer.write_str(s) + } + PctEncodedFragments::NoPctStr(s) => { + let mut chars = s.char_indices(); + let count = + chars.by_ref().take(max_len).last().map(|(i, _)| i).expect( + "[consistency] decomposed string fragment must not be empty", + ); + let sub_len = s.len() - chars.as_str().len(); + max_len -= count; + write!( + writer, + "{}", + PercentEncoded::<_, S>::characters(&s[..sub_len]) + ) + } + PctEncodedFragments::StrayPercent => { + max_len -= 1; + writer.write_str("%25") + } + PctEncodedFragments::InvalidUtf8PctTriplets(s) => { + let count = max_len.min(s.len() / 3); + let sub_len = count * 3; + max_len -= count; + writer.write_str(&s[..sub_len]) + } + }; + if result.is_err() { + return ControlFlow::Break(result); + } + ControlFlow::Continue(()) + }); + match result { + Ok(ControlFlow::Break(Ok(_)) | ControlFlow::Continue(_)) => Ok(()), + Ok(ControlFlow::Break(Err(e))) | Err(e) => Err(e), + } + } else { + match max_len { + Some(max_len) => { + let mut writer = TruncatePercentEncodeWriter::<S, _> { + inner: writer, + rest_num_chars: usize::from(max_len), + _spec: PhantomData, + }; + write!(writer, "{v}") + } + None => write!(writer, "{}", PercentEncoded::<_, S>::unreserve(v)), + } + } +} + +/// A writer that truncates the input to the given length and writes to the backend. +struct TruncatePercentEncodeWriter<'a, S, W> { + /// Inner writer. + inner: &'a mut W, + /// Maximum number of characters to be written. + rest_num_chars: usize, + /// Spec. + _spec: PhantomData<fn() -> S>, +} + +impl<S: Spec, W: fmt::Write> fmt::Write for TruncatePercentEncodeWriter<'_, S, W> { + fn write_str(&mut self, s: &str) -> fmt::Result { + if self.rest_num_chars == 0 { + return Ok(()); + } + let mut chars = s.char_indices(); + let skip_count = chars + .by_ref() + .take(self.rest_num_chars) + .last() + .map_or(0, |(i, _)| i + 1); + let len = s.len() - chars.as_str().len(); + let truncated = &s[..len]; + write!( + self.inner, + "{}", + PercentEncoded::<_, S>::unreserve(truncated) + )?; + self.rest_num_chars -= skip_count; + Ok(()) + } +} + +/// A writer that writes a prefix only once if and only if some value is written. +struct PrefixOnceWriter<'a, W> { + /// Inner writer. + inner: &'a mut W, + /// Prefix to write. + prefix: Option<&'a str>, +} + +impl<'a, W: fmt::Write> PrefixOnceWriter<'a, W> { + /// Creates a new writer with no prefix. + #[inline] + #[must_use] + fn new(inner: &'a mut W) -> Self { + Self { + inner, + prefix: None, + } + } + + /// Creates a new writer with a prefix. + #[inline] + #[must_use] + fn with_prefix(inner: &'a mut W, prefix: &'a str) -> Self { + Self { + inner, + prefix: Some(prefix), + } + } + + /// Returns true if the writer have not yet written the prefix. + #[inline] + #[must_use] + fn has_unwritten_prefix(&self) -> bool { + self.prefix.is_some() + } +} + +impl<W: fmt::Write> fmt::Write for PrefixOnceWriter<'_, W> { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + if let Some(prefix) = self.prefix.take() { + self.inner.write_str(prefix)?; + } + self.inner.write_str(s) + } +} + +/// An opaque token value that proves some variable is visited. +// This should not be able to be created by any means other than `VarVisitor::visit_foo()`. +// Do not derive any traits that allows the value to be generated or cloned. +struct VisitDoneToken<'a, S, W>(ValueVisitor<'a, S, W>); + +impl<'a, S: Spec, W: fmt::Write> VisitDoneToken<'a, S, W> { + /// Creates a new token. + #[inline] + #[must_use] + fn new(visitor: ValueVisitor<'a, S, W>) -> Self { + Self(visitor) + } + + /// Returns the raw pointer to the backend formatter. + #[inline] + #[must_use] + fn writer_ptr(&self) -> *const W { + self.0.writer_ptr() + } +} + +impl<S: Spec, W: fmt::Write> fmt::Debug for VisitDoneToken<'_, S, W> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("VisitDoneToken") + } +} + +/// Visitor to retrieve a variable value. +// Single `ValueVisitor` should be used for single expansion. +// Do not derive any traits that allows the value to be generated or cloned. +struct ValueVisitor<'a, S, W> { + /// Formatter. + writer: &'a mut W, + /// Varspec. + varspec: VarSpec<'a>, + /// Operator. + op: Operator, + /// Whether the variable to visit is the first one in an expression. + is_first_varspec: &'a mut bool, + /// Spec. + _spec: PhantomData<fn() -> S>, +} + +impl<'a, S: Spec, W: fmt::Write> ValueVisitor<'a, S, W> { + /// Creates a visitor. + #[inline] + #[must_use] + fn new( + f: &'a mut W, + varspec: VarSpec<'a>, + op: Operator, + is_first_varspec: &'a mut bool, + ) -> Self { + Self { + writer: f, + varspec, + op, + is_first_varspec, + _spec: PhantomData, + } + } + + /// Returns the raw pointer to the backend formatter. + #[inline] + #[must_use] + fn writer_ptr(&self) -> *const W { + self.writer as &_ as *const _ + } +} + +impl<S: Spec, W: fmt::Write> VisitorSealed for ValueVisitor<'_, S, W> {} + +impl<'a, S: Spec, W: fmt::Write> Visitor for ValueVisitor<'a, S, W> { + type Result = Result<VisitDoneToken<'a, S, W>, fmt::Error>; + type ListVisitor = ListValueVisitor<'a, S, W>; + type AssocVisitor = AssocValueVisitor<'a, S, W>; + + /// Returns the name of the variable to visit. + #[inline] + #[must_use] + fn var_name(&self) -> VarName<'a> { + self.varspec.name() + } + + #[inline] + fn purpose(&self) -> VisitPurpose { + VisitPurpose::Expand + } + + /// Visits an undefined variable, i.e. indicates that the requested variable is unavailable. + #[inline] + fn visit_undefined(self) -> Self::Result { + Ok(VisitDoneToken::new(self)) + } + + /// Visits a string variable. + #[inline] + fn visit_string<T: fmt::Display>(self, v: T) -> Self::Result { + let oppr = OpProps::from_op(self.op); + + if mem::replace(self.is_first_varspec, false) { + self.writer.write_str(oppr.first)?; + } else { + self.writer.write_str(oppr.sep)?; + } + let mut writer = if oppr.named { + self.writer.write_str(self.varspec.name().as_str())?; + PrefixOnceWriter::with_prefix(self.writer, "=") + } else { + PrefixOnceWriter::new(self.writer) + }; + + let max_len = match self.varspec.modifier() { + Modifier::None | Modifier::Explode => None, + Modifier::MaxLen(max_len) => Some(max_len), + }; + escape_write_with_maxlen::<S, T, W>(&mut writer, v, oppr.allow_reserved, max_len)?; + if writer.has_unwritten_prefix() { + self.writer.write_str(oppr.ifemp)?; + } + Ok(VisitDoneToken::new(self)) + } + + /// Visits a list variable. + #[inline] + #[must_use] + fn visit_list(self) -> Self::ListVisitor { + let oppr = OpProps::from_op(self.op); + ListValueVisitor { + visitor: self, + num_elems: 0, + oppr, + } + } + + /// Visits an associative array variable. + #[inline] + #[must_use] + fn visit_assoc(self) -> Self::AssocVisitor { + let oppr = OpProps::from_op(self.op); + AssocValueVisitor { + visitor: self, + num_elems: 0, + oppr, + } + } +} + +/// Visitor to retrieve value of a list variable. +// RFC 6570 section 2.3: +// +// > A variable defined as a list value is considered undefined if the +// > list contains zero members. A variable defined as an associative +// > array of (name, value) pairs is considered undefined if the array +// > contains zero members or if all member names in the array are +// > associated with undefined values. +// +// Single variable visitor should be used for single expansion. +// Do not derive any traits that allows the value to be generated or cloned. +struct ListValueVisitor<'a, S, W> { + /// Visitor. + visitor: ValueVisitor<'a, S, W>, + /// Number of already emitted elements. + num_elems: usize, + /// Operator props. + oppr: &'static OpProps, +} + +impl<S: Spec, W: fmt::Write> ListValueVisitor<'_, S, W> { + /// Visits an item. + fn visit_item_impl<T: fmt::Display>(&mut self, item: T) -> fmt::Result { + let modifier = self.visitor.varspec.modifier(); + let is_explode = match modifier { + Modifier::MaxLen(_) => panic!( + "value type changed since `UriTemplateStr::expand()`: \ + prefix modifier is not applicable to a list" + ), + Modifier::None => false, + Modifier::Explode => true, + }; + + // Write prefix for each variable. + if self.num_elems == 0 { + if mem::replace(self.visitor.is_first_varspec, false) { + self.visitor.writer.write_str(self.oppr.first)?; + } else { + self.visitor.writer.write_str(self.oppr.sep)?; + } + if self.oppr.named { + self.visitor + .writer + .write_str(self.visitor.varspec.name().as_str())?; + self.visitor.writer.write_char('=')?; + } + } else { + // Write prefix for the non-first item. + match (self.oppr.named, is_explode) { + (_, false) => self.visitor.writer.write_char(',')?, + (false, true) => self.visitor.writer.write_str(self.oppr.sep)?, + (true, true) => { + self.visitor.writer.write_str(self.oppr.sep)?; + escape_write::<S, _, _>( + self.visitor.writer, + self.visitor.varspec.name().as_str(), + self.oppr.allow_reserved, + )?; + self.visitor.writer.write_char('=')?; + } + } + } + + escape_write::<S, _, _>(self.visitor.writer, item, self.oppr.allow_reserved)?; + + self.num_elems += 1; + Ok(()) + } +} + +impl<S: Spec, W: fmt::Write> VisitorSealed for ListValueVisitor<'_, S, W> {} + +impl<'a, S: Spec, W: fmt::Write> ListVisitor for ListValueVisitor<'a, S, W> { + type Result = Result<VisitDoneToken<'a, S, W>, fmt::Error>; + + /// Visits an item. + #[inline] + fn visit_item<T: fmt::Display>(&mut self, item: T) -> ControlFlow<Self::Result> { + match self.visit_item_impl(item) { + Ok(_) => ControlFlow::Continue(()), + Err(e) => ControlFlow::Break(Err(e)), + } + } + + /// Finishes visiting the list. + #[inline] + fn finish(self) -> Self::Result { + Ok(VisitDoneToken::new(self.visitor)) + } +} + +/// Visitor to retrieve entries of an associative array variable. +// RFC 6570 section 2.3: +// +// > A variable defined as a list value is considered undefined if the +// > list contains zero members. A variable defined as an associative +// > array of (name, value) pairs is considered undefined if the array +// > contains zero members or if all member names in the array are +// > associated with undefined values. +// +// Single variable visitor should be used for single expansion. +// Do not derive any traits that allows the value to be generated or cloned. +struct AssocValueVisitor<'a, S, W> { + /// Visitor. + visitor: ValueVisitor<'a, S, W>, + /// Number of already emitted elements. + num_elems: usize, + /// Operator props. + oppr: &'static OpProps, +} + +impl<S: Spec, W: fmt::Write> AssocValueVisitor<'_, S, W> { + /// Visits an entry. + fn visit_entry_impl<K: fmt::Display, V: fmt::Display>( + &mut self, + key: K, + value: V, + ) -> fmt::Result { + let modifier = self.visitor.varspec.modifier(); + let is_explode = match modifier { + Modifier::MaxLen(_) => panic!( + "value type changed since `UriTemplateStr::expand()`: \ + prefix modifier is not applicable to an associative array" + ), + Modifier::None => false, + Modifier::Explode => true, + }; + + // Write prefix for each variable. + if self.num_elems == 0 { + if mem::replace(self.visitor.is_first_varspec, false) { + self.visitor.writer.write_str(self.oppr.first)?; + } else { + self.visitor.writer.write_str(self.oppr.sep)?; + } + if is_explode { + escape_write::<S, _, _>(self.visitor.writer, key, self.oppr.allow_reserved)?; + self.visitor.writer.write_char('=')?; + } else { + if self.oppr.named { + escape_write::<S, _, _>( + self.visitor.writer, + self.visitor.varspec.name().as_str(), + self.oppr.allow_reserved, + )?; + self.visitor.writer.write_char('=')?; + } + escape_write::<S, _, _>(self.visitor.writer, key, self.oppr.allow_reserved)?; + self.visitor.writer.write_char(',')?; + } + } else { + // Write prefix for the non-first item. + match (self.oppr.named, is_explode) { + (_, false) => { + self.visitor.writer.write_char(',')?; + escape_write::<S, _, _>(self.visitor.writer, key, self.oppr.allow_reserved)?; + self.visitor.writer.write_char(',')?; + } + (false, true) => { + self.visitor.writer.write_str(self.oppr.sep)?; + escape_write::<S, _, _>(self.visitor.writer, key, self.oppr.allow_reserved)?; + self.visitor.writer.write_char('=')?; + } + (true, true) => { + self.visitor.writer.write_str(self.oppr.sep)?; + escape_write::<S, _, _>(self.visitor.writer, key, self.oppr.allow_reserved)?; + self.visitor.writer.write_char('=')?; + } + } + } + + escape_write::<S, _, _>(self.visitor.writer, value, self.oppr.allow_reserved)?; + + self.num_elems += 1; + Ok(()) + } +} + +impl<S: Spec, W: fmt::Write> VisitorSealed for AssocValueVisitor<'_, S, W> {} + +impl<'a, S: Spec, W: fmt::Write> AssocVisitor for AssocValueVisitor<'a, S, W> { + type Result = Result<VisitDoneToken<'a, S, W>, fmt::Error>; + + /// Visits an entry. + #[inline] + fn visit_entry<K: fmt::Display, V: fmt::Display>( + &mut self, + key: K, + value: V, + ) -> ControlFlow<Self::Result> { + match self.visit_entry_impl(key, value) { + Ok(_) => ControlFlow::Continue(()), + Err(e) => ControlFlow::Break(Err(e)), + } + } + + /// Finishes visiting the associative array. + #[inline] + fn finish(self) -> Self::Result { + Ok(VisitDoneToken::new(self.visitor)) + } +} + +/// Visitor to retrieve effective type of a variable. +struct TypeVisitor<'a> { + /// Variable name. + var_name: VarName<'a>, +} + +impl<'a> TypeVisitor<'a> { + /// Creates a new type visitor. + #[inline] + #[must_use] + fn new(var_name: VarName<'a>) -> Self { + Self { var_name } + } +} + +impl VisitorSealed for TypeVisitor<'_> {} + +impl<'a> Visitor for TypeVisitor<'a> { + type Result = ValueType; + type ListVisitor = ListTypeVisitor; + type AssocVisitor = AssocTypeVisitor; + + #[inline] + fn var_name(&self) -> VarName<'a> { + self.var_name + } + #[inline] + fn purpose(&self) -> VisitPurpose { + VisitPurpose::Typecheck + } + #[inline] + fn visit_undefined(self) -> Self::Result { + ValueType::undefined() + } + #[inline] + fn visit_string<T: fmt::Display>(self, _: T) -> Self::Result { + ValueType::string() + } + #[inline] + fn visit_list(self) -> Self::ListVisitor { + ListTypeVisitor + } + #[inline] + fn visit_assoc(self) -> Self::AssocVisitor { + AssocTypeVisitor + } +} + +/// Visitor to retrieve effective type of a list variable. +struct ListTypeVisitor; + +impl VisitorSealed for ListTypeVisitor {} + +impl ListVisitor for ListTypeVisitor { + type Result = ValueType; + + /// Visits an item. + #[inline] + fn visit_item<T: fmt::Display>(&mut self, _item: T) -> ControlFlow<Self::Result> { + ControlFlow::Break(ValueType::nonempty_list()) + } + + /// Finishes visiting the list. + #[inline] + fn finish(self) -> Self::Result { + ValueType::empty_list() + } +} + +/// Visitor to retrieve effective type of an associative array variable. +struct AssocTypeVisitor; + +impl VisitorSealed for AssocTypeVisitor {} + +impl AssocVisitor for AssocTypeVisitor { + type Result = ValueType; + + /// Visits an item. + #[inline] + fn visit_entry<K: fmt::Display, V: fmt::Display>( + &mut self, + _key: K, + _value: V, + ) -> ControlFlow<Self::Result> { + ControlFlow::Break(ValueType::nonempty_assoc()) + } + + /// Finishes visiting the list. + #[inline] + fn finish(self) -> Self::Result { + ValueType::empty_assoc() + } +} diff --git a/vendor/iri-string/src/template/parser.rs b/vendor/iri-string/src/template/parser.rs new file mode 100644 index 00000000..6d5443a8 --- /dev/null +++ b/vendor/iri-string/src/template/parser.rs @@ -0,0 +1,6 @@ +//! URI Template parser. + +pub(super) mod char; +pub(super) mod validate; + +pub(super) use self::validate::validate_template_str; diff --git a/vendor/iri-string/src/template/parser/char.rs b/vendor/iri-string/src/template/parser/char.rs new file mode 100644 index 00000000..9ad4a6d8 --- /dev/null +++ b/vendor/iri-string/src/template/parser/char.rs @@ -0,0 +1,190 @@ +//! Characters. + +/// Properties of ASCII characters. +/// +/// About `'` (single quote) being considered as a literal: see +/// [Errata ID 6937](https://www.rfc-editor.org/errata/eid6937). +const CHARS_TABLE: [u8; 128] = [ + 0b_0000_0000, // NUL + 0b_0000_0000, // SOH + 0b_0000_0000, // STX + 0b_0000_0000, // ETX + 0b_0000_0000, // EOT + 0b_0000_0000, // ENQ + 0b_0000_0000, // ACK + 0b_0000_0000, // BEL + 0b_0000_0000, // BS + 0b_0000_0000, // HT + 0b_0000_0000, // LF + 0b_0000_0000, // VT + 0b_0000_0000, // FF + 0b_0000_0000, // CR + 0b_0000_0000, // SO + 0b_0000_0000, // SI + 0b_0000_0000, // DLE + 0b_0000_0000, // DC1 + 0b_0000_0000, // DC2 + 0b_0000_0000, // DC3 + 0b_0000_0000, // DC4 + 0b_0000_0000, // NAK + 0b_0000_0000, // SYN + 0b_0000_0000, // ETB + 0b_0000_0000, // CAN + 0b_0000_0000, // EM + 0b_0000_0000, // SUB + 0b_0000_0000, // ESC + 0b_0000_0000, // FS + 0b_0000_0000, // GS + 0b_0000_0000, // RS + 0b_0000_0000, // US + 0b_0000_0000, // SPACE + 0b_0000_0001, // ! + 0b_0000_0000, // " + 0b_0000_0001, // # + 0b_0000_0001, // $ + 0b_0000_0000, // % + 0b_0000_0001, // & + 0b_0000_0001, // ' + 0b_0000_0001, // ( + 0b_0000_0001, // ) + 0b_0000_0001, // * + 0b_0000_0001, // + + 0b_0000_0001, // , + 0b_0000_0001, // - + 0b_0000_0101, // . + 0b_0000_0001, // / + 0b_0000_0111, // 0 + 0b_0000_0111, // 1 + 0b_0000_0111, // 2 + 0b_0000_0111, // 3 + 0b_0000_0111, // 4 + 0b_0000_0111, // 5 + 0b_0000_0111, // 6 + 0b_0000_0111, // 7 + 0b_0000_0111, // 8 + 0b_0000_0111, // 9 + 0b_0000_0001, // : + 0b_0000_0001, // ; + 0b_0000_0000, // < + 0b_0000_0001, // = + 0b_0000_0000, // > + 0b_0000_0001, // ? + 0b_0000_0001, // @ + 0b_0000_0111, // A + 0b_0000_0111, // B + 0b_0000_0111, // C + 0b_0000_0111, // D + 0b_0000_0111, // E + 0b_0000_0111, // F + 0b_0000_0111, // G + 0b_0000_0111, // H + 0b_0000_0111, // I + 0b_0000_0111, // J + 0b_0000_0111, // K + 0b_0000_0111, // L + 0b_0000_0111, // M + 0b_0000_0111, // N + 0b_0000_0111, // O + 0b_0000_0111, // P + 0b_0000_0111, // Q + 0b_0000_0111, // R + 0b_0000_0111, // S + 0b_0000_0111, // T + 0b_0000_0111, // U + 0b_0000_0111, // V + 0b_0000_0111, // W + 0b_0000_0111, // X + 0b_0000_0111, // Y + 0b_0000_0111, // Z + 0b_0000_0001, // [ + 0b_0000_0000, // \ + 0b_0000_0001, // ] + 0b_0000_0000, // ^ + 0b_0000_0111, // _ + 0b_0000_0000, // ` + 0b_0000_0111, // a + 0b_0000_0111, // b + 0b_0000_0111, // c + 0b_0000_0111, // d + 0b_0000_0111, // e + 0b_0000_0111, // f + 0b_0000_0111, // g + 0b_0000_0111, // h + 0b_0000_0111, // i + 0b_0000_0111, // j + 0b_0000_0111, // k + 0b_0000_0111, // l + 0b_0000_0111, // m + 0b_0000_0111, // n + 0b_0000_0111, // o + 0b_0000_0111, // p + 0b_0000_0111, // q + 0b_0000_0111, // r + 0b_0000_0111, // s + 0b_0000_0111, // t + 0b_0000_0111, // u + 0b_0000_0111, // v + 0b_0000_0111, // w + 0b_0000_0111, // x + 0b_0000_0111, // y + 0b_0000_0111, // z + 0b_0000_0000, // { + 0b_0000_0000, // | + 0b_0000_0000, // } + 0b_0000_0001, // ~ + 0b_0000_0000, // DEL +]; + +/// A mask to test whether the character matches `literals` rule defined in [RFC 6570]. +/// +/// [RFC 6570]: https://www.rfc-editor.org/rfc/rfc6570.html#section-2.1 +const CHARS_TABLE_MASK_LITERAL: u8 = 1 << 0; + +/// A mask to test whether the character matches `varchar` rule defined in [RFC 6570]. +/// +/// [RFC 6570]: https://www.rfc-editor.org/rfc/rfc6570.html#section-2.3 +const CHARS_TABLE_MASK_VARCHAR_START: u8 = 1 << 1; + +/// A mask to test whether the character matches `varchar` rule defined in [RFC 6570] or a period. +/// +/// [RFC 6570]: https://www.rfc-editor.org/rfc/rfc6570.html#section-2.3 +const CHARS_TABLE_MASK_VARCHAR_CONTINUE: u8 = 1 << 2; + +/// Returns true if the given ASCII character is allowed in a literal string. +/// +/// # Precondition +/// +/// The given byte should be an ASCII character, i.e. should be less than 128. +#[inline] +#[must_use] +pub(super) const fn is_ascii_literal_char(c: u8) -> bool { + (CHARS_TABLE[c as usize] & CHARS_TABLE_MASK_LITERAL) != 0 +} + +/// Returns true if the given ASCII character is allowed as the beginning of the `varname`. +/// +/// Note that this does not return true for `%` character. It is caller's +/// responsibility to test validity of percent-encoded triplets. +/// +/// # Precondition +/// +/// The given byte should be an ASCII character, i.e. should be less than 128. +#[inline] +#[must_use] +pub(super) const fn is_ascii_varchar_start(c: u8) -> bool { + (CHARS_TABLE[c as usize] & CHARS_TABLE_MASK_VARCHAR_START) != 0 +} + +/// Returns true if the given ASCII character is allowed as the non-beginning of the `varname`. +/// +/// Note that this does not return true for `%` character. It is caller's +/// responsibility to test validity of percent-encoded triplets. +/// +/// # Precondition +/// +/// The given byte should be an ASCII character, i.e. should be less than 128. +#[inline] +#[must_use] +pub(super) const fn is_ascii_varchar_continue(c: u8) -> bool { + (CHARS_TABLE[c as usize] & CHARS_TABLE_MASK_VARCHAR_CONTINUE) != 0 +} diff --git a/vendor/iri-string/src/template/parser/validate.rs b/vendor/iri-string/src/template/parser/validate.rs new file mode 100644 index 00000000..67ab6c01 --- /dev/null +++ b/vendor/iri-string/src/template/parser/validate.rs @@ -0,0 +1,161 @@ +//! Validating parsers. + +use crate::parser::str::{ + find_split2_hole, find_split_hole, satisfy_chars_with_pct_encoded, starts_with_double_hexdigits, +}; +use crate::template::components::MaybeOperator; +use crate::template::error::{Error, ErrorKind}; + +use crate::template::parser::char::{ + is_ascii_literal_char, is_ascii_varchar_continue, is_ascii_varchar_start, +}; + +/// Returns `Ok(())` if the given string is a valid literal. +fn validate_literal(s: &str, offset: usize) -> Result<(), Error> { + match s + .chars() + .position(|c| !c.is_ascii() || !is_ascii_literal_char(c as u8)) + { + Some(pos) => Err(Error::new(ErrorKind::InvalidCharacter, offset + pos)), + None => Ok(()), + } +} + +/// Returns `Ok(())` if the given string is a valid varspec. +fn validate_varspec(s: &str, offset: usize) -> Result<(), Error> { + match find_split2_hole(s, b':', b'*') { + Some((maybe_varname, b':', maybe_len)) => { + validate_varname(maybe_varname, offset)?; + if !(1..=5).contains(&maybe_len.len()) { + return Err(Error::new( + ErrorKind::InvalidExpression, + offset + maybe_varname.len() + 2, + )); + } + if let Some(pos) = maybe_len.bytes().position(|b| !b.is_ascii_digit()) { + return Err(Error::new( + ErrorKind::InvalidExpression, + offset + maybe_varname.len() + 2 + pos, + )); + } + } + Some((maybe_varname, b'*', extra)) => { + validate_varname(maybe_varname, offset)?; + if !extra.is_empty() { + return Err(Error::new( + ErrorKind::InvalidExpression, + offset + maybe_varname.len() + 1, + )); + } + } + Some((_, sep, _)) => unreachable!("[consistency] the byte {sep:#02x} is not searched"), + None => validate_varname(s, offset)?, + } + Ok(()) +} + +/// Returns `Ok(())` if the given string is a valid varname. +pub(crate) fn validate_varname(s: &str, offset: usize) -> Result<(), Error> { + let rest = match s.as_bytes().first() { + Some(b'%') if starts_with_double_hexdigits(&s.as_bytes()[1..]) => &s[3..], + Some(b) if b.is_ascii() && is_ascii_varchar_start(*b) => &s[1..], + _ => return Err(Error::new(ErrorKind::InvalidExpression, offset)), + }; + let is_valid = satisfy_chars_with_pct_encoded(rest, is_ascii_varchar_continue, |_| false); + if !is_valid { + return Err(Error::new(ErrorKind::InvalidExpression, offset)); + } + Ok(()) +} + +/// Returns `Ok(())` if the given string is a valid expression. +/// +/// "Expression" here is the expression body inside `{` and `}`, but not including braces. +fn validate_expr_body(s: &str, mut offset: usize) -> Result<(), Error> { + if s.is_empty() { + return Err(Error::new(ErrorKind::InvalidExpression, offset)); + } + + // Skip the operator. + let maybe_variable_list = match MaybeOperator::from_byte(s.as_bytes()[0]) { + Some(MaybeOperator::Operator(_)) => { + offset += 1; + &s[1..] + } + Some(MaybeOperator::Reserved(_)) => { + return Err(Error::new(ErrorKind::UnsupportedOperator, offset)); + } + None => s, + }; + + // Validate varspecs. + for (spec_i, maybe_varspec) in maybe_variable_list.split(',').enumerate() { + if spec_i != 0 { + // Add the length of the leading separator `,`. + offset += 1; + } + validate_varspec(maybe_varspec, offset)?; + offset += maybe_varspec.len(); + } + + Ok(()) +} + +/// Validates whether the given string is valid as a URI template. +/// +/// Returns `Ok(())` if the given string is a valid URI template. +pub(in crate::template) fn validate_template_str(s: &str) -> Result<(), Error> { + let mut rest = s; + let mut offset = 0; + while !rest.is_empty() { + rest = match find_split2_hole(rest, b'%', b'{') { + Some((literal, b'%', xdigits2_and_rest)) => { + validate_literal(literal, offset)?; + + if xdigits2_and_rest.len() < 2 { + return Err(Error::new( + ErrorKind::InvalidPercentEncoding, + offset + literal.len(), + )); + } + let (xdigits2, new_rest) = xdigits2_and_rest.split_at(2); + if !xdigits2.as_bytes()[0].is_ascii_hexdigit() { + return Err(Error::new( + ErrorKind::InvalidPercentEncoding, + offset + literal.len() + 1, + )); + } + if !xdigits2.as_bytes()[1].is_ascii_hexdigit() { + return Err(Error::new( + ErrorKind::InvalidPercentEncoding, + offset + literal.len() + 2, + )); + } + new_rest + } + Some((literal, b'{', expr_and_rest)) => { + validate_literal(literal, offset)?; + + let (expr, new_rest) = match find_split_hole(expr_and_rest, b'}') { + Some(v) => v, + None => { + return Err(Error::new( + ErrorKind::ExpressionNotClosed, + offset + literal.len(), + )) + } + }; + + // +1 is `+ "{".len()`. + validate_expr_body(expr, offset + literal.len() + 1)?; + + new_rest + } + Some(_) => unreachable!("[consistency] searching only `%` and `{{`"), + None => return validate_literal(rest, offset), + }; + offset = s.len() - rest.len(); + } + + Ok(()) +} diff --git a/vendor/iri-string/src/template/simple_context.rs b/vendor/iri-string/src/template/simple_context.rs new file mode 100644 index 00000000..5c19dc79 --- /dev/null +++ b/vendor/iri-string/src/template/simple_context.rs @@ -0,0 +1,218 @@ +//! Simple general-purpose context type. + +use core::ops::ControlFlow; + +use alloc::collections::BTreeMap; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::String; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; + +use crate::template::context::{Context, VarName, Visitor}; + +/// Value. +#[derive(Debug, Clone)] +pub enum Value { + /// Undefined (i.e. null). + Undefined, + /// String value. + String(String), + /// List. + List(Vec<String>), + /// Associative array. + Assoc(Vec<(String, String)>), +} + +impl From<&str> for Value { + #[inline] + fn from(v: &str) -> Self { + Self::String(v.into()) + } +} + +impl From<String> for Value { + #[inline] + fn from(v: String) -> Self { + Self::String(v) + } +} + +/// Simple template expansion context. +#[derive(Default, Debug, Clone)] +pub struct SimpleContext { + /// Variable values. + // Any map types (including `HashMap`) is ok, but the hash map is not provided by `alloc`. + // + // QUESTION: Should hexdigits in percent-encoded triplets in varnames be + // compared case sensitively? + variables: BTreeMap<String, Value>, +} + +impl SimpleContext { + /// Creates a new empty context. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// # #[cfg(feature = "alloc")] { + /// use iri_string::spec::UriSpec; + /// use iri_string::template::UriTemplateStr; + /// use iri_string::template::simple_context::SimpleContext; + /// + /// let empty_ctx = SimpleContext::new(); + /// let template = UriTemplateStr::new("{no_such_variable}")?; + /// let expanded = template.expand::<UriSpec, _>(&empty_ctx)?; + /// + /// assert_eq!( + /// expanded.to_string(), + /// "" + /// ); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Inserts a variable. + /// + /// Passing [`Value::Undefined`] removes the value from the context. + /// + /// The entry will be inserted or removed even if the key is invalid as a + /// variable name. Such entries will be simply ignored on expansion. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// # #[cfg(feature = "alloc")] { + /// use iri_string::spec::UriSpec; + /// use iri_string::template::UriTemplateStr; + /// use iri_string::template::simple_context::SimpleContext; + /// + /// let mut context = SimpleContext::new(); + /// context.insert("username", "foo"); + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// let expanded = template.expand::<UriSpec, _>(&context)?; + /// + /// assert_eq!( + /// expanded.to_string(), + /// "/users/foo" + /// ); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + /// + /// Passing [`Value::Undefined`] removes the value from the context. + /// + /// ``` + /// # use iri_string::template::Error; + /// ## [cfg(feature = "alloc")] { + /// use iri_string::spec::UriSpec; + /// use iri_string::template::UriTemplateStr; + /// use iri_string::template::simple_context::{SimpleContext, Value}; + /// + /// let mut context = SimpleContext::new(); + /// context.insert("username", "foo"); + /// context.insert("username", Value::Undefined); + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// let expanded = template.expand::<UriSpec, _>(&context)?; + /// + /// assert_eq!( + /// expanded.to_string(), + /// "/users/" + /// ); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<Value> + where + K: Into<String>, + V: Into<Value>, + { + let key = key.into(); + match value.into() { + Value::Undefined => self.variables.remove(&key), + value => self.variables.insert(key, value), + } + } + + /// Removes all entries in the context. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// # #[cfg(feature = "alloc")] { + /// use iri_string::spec::UriSpec; + /// use iri_string::template::UriTemplateStr; + /// use iri_string::template::simple_context::SimpleContext; + /// + /// let template = UriTemplateStr::new("{foo,bar}")?; + /// let mut context = SimpleContext::new(); + /// + /// context.insert("foo", "FOO"); + /// context.insert("bar", "BAR"); + /// assert_eq!( + /// template.expand::<UriSpec, _>(&context)?.to_string(), + /// "FOO,BAR" + /// ); + /// + /// context.clear(); + /// assert_eq!( + /// template.expand::<UriSpec, _>(&context)?.to_string(), + /// "" + /// ); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + pub fn clear(&mut self) { + self.variables.clear(); + } + + /// Returns a reference to the value for the key. + // + // QUESTION: Should hexdigits in percent-encoded triplets in varnames be + // compared case sensitively? + #[inline] + #[must_use] + pub fn get(&self, key: VarName<'_>) -> Option<&Value> { + self.variables.get(key.as_str()) + } +} + +impl Context for SimpleContext { + fn visit<V: Visitor>(&self, visitor: V) -> V::Result { + use crate::template::context::{AssocVisitor, ListVisitor}; + + let name = visitor.var_name().as_str(); + match self.variables.get(name) { + None | Some(Value::Undefined) => visitor.visit_undefined(), + Some(Value::String(s)) => visitor.visit_string(s), + Some(Value::List(list)) => { + let mut visitor = visitor.visit_list(); + if let ControlFlow::Break(res) = + list.iter().try_for_each(|item| visitor.visit_item(item)) + { + return res; + } + visitor.finish() + } + Some(Value::Assoc(list)) => { + let mut visitor = visitor.visit_assoc(); + if let ControlFlow::Break(res) = + list.iter().try_for_each(|(k, v)| visitor.visit_entry(k, v)) + { + return res; + } + visitor.finish() + } + } + } +} diff --git a/vendor/iri-string/src/template/string.rs b/vendor/iri-string/src/template/string.rs new file mode 100644 index 00000000..9ba53a75 --- /dev/null +++ b/vendor/iri-string/src/template/string.rs @@ -0,0 +1,647 @@ +//! Template string types. + +use core::fmt; + +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::rc::Rc; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::sync::Arc; + +use crate::spec::Spec; +use crate::template::components::{VarListIter, VarName}; +use crate::template::context::{Context, DynamicContext}; +use crate::template::error::{Error, ErrorKind}; +use crate::template::expand::{expand_whole_dynamic, Chunk, Chunks, Expanded}; +use crate::template::parser::validate_template_str; + +#[cfg(feature = "alloc")] +pub use self::owned::UriTemplateString; + +/// Implements `PartialEq` and `PartialOrd`. +macro_rules! impl_cmp { + ($ty_common:ty, $ty_lhs:ty, $ty_rhs:ty) => { + impl PartialEq<$ty_rhs> for $ty_lhs { + #[inline] + fn eq(&self, o: &$ty_rhs) -> bool { + <$ty_common as PartialEq<$ty_common>>::eq(self.as_ref(), o.as_ref()) + } + } + impl PartialEq<$ty_lhs> for $ty_rhs { + #[inline] + fn eq(&self, o: &$ty_lhs) -> bool { + <$ty_common as PartialEq<$ty_common>>::eq(self.as_ref(), o.as_ref()) + } + } + impl PartialOrd<$ty_rhs> for $ty_lhs { + #[inline] + fn partial_cmp(&self, o: &$ty_rhs) -> Option<core::cmp::Ordering> { + <$ty_common as PartialOrd<$ty_common>>::partial_cmp(self.as_ref(), o.as_ref()) + } + } + impl PartialOrd<$ty_lhs> for $ty_rhs { + #[inline] + fn partial_cmp(&self, o: &$ty_lhs) -> Option<core::cmp::Ordering> { + <$ty_common as PartialOrd<$ty_common>>::partial_cmp(self.as_ref(), o.as_ref()) + } + } + }; +} + +#[cfg(feature = "alloc")] +mod owned; + +/// A borrowed slice of a URI template. +/// +/// URI Template is defined by [RFC 6570]. +/// +/// Note that "URI Template" can also be used for IRI. +/// +/// [RFC 6570]: https://www.rfc-editor.org/rfc/rfc6570.html +/// +/// # Valid values +/// +/// This type can have a URI template string. +/// +/// # Applied errata +/// +/// [Errata ID 6937](https://www.rfc-editor.org/errata/eid6937) is applied, so +/// single quotes are allowed to appear in an URI template. +/// +/// ``` +/// # use iri_string::template::Error; +/// use iri_string::template::UriTemplateStr; +/// +/// let template = UriTemplateStr::new("'quoted'")?; +/// # Ok::<_, Error>(()) +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[repr(transparent)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UriTemplateStr { + /// The raw string. + inner: str, +} + +impl UriTemplateStr { + /// Creates a new string. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// use iri_string::template::UriTemplateStr; + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + pub fn new(s: &str) -> Result<&Self, Error> { + TryFrom::try_from(s) + } + + /// Creates a new string without validation. + /// + /// This does not validate the given string, so it is caller's + /// responsibility to ensure the given string is valid. + /// + /// # Safety + /// + /// The given string must be syntactically valid as `Self` type. + /// If not, any use of the returned value or the call of this + /// function itself may result in undefined behavior. + #[inline] + #[must_use] + pub unsafe fn new_unchecked(s: &str) -> &Self { + // SAFETY: `new_always_unchecked` requires the same precondition + // as `new_always_unchecked`. + unsafe { Self::new_always_unchecked(s) } + } + + /// Creates a new string without any validation. + /// + /// This does not validate the given string at any time. + /// + /// Intended for internal use. + /// + /// # Safety + /// + /// The given string must be valid. + #[inline] + #[must_use] + unsafe fn new_always_unchecked(s: &str) -> &Self { + // SAFETY: the cast is safe since `Self` type has `repr(transparent)` + // attribute and the content is guaranteed as valid by the + // precondition of the function. + unsafe { &*(s as *const str as *const Self) } + } + + /// Returns the template as a plain `&str`. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// use iri_string::template::UriTemplateStr; + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// assert_eq!(template.as_str(), "/users/{username}"); + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn as_str(&self) -> &str { + self.as_ref() + } + + /// Returns the template string length. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// use iri_string::template::UriTemplateStr; + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// assert_eq!(template.len(), "/users/{username}".len()); + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.as_str().len() + } + + /// Returns whether the string is empty. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// use iri_string::template::UriTemplateStr; + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// assert!(!template.is_empty()); + /// + /// let empty = UriTemplateStr::new("")?; + /// assert!(empty.is_empty()); + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.as_str().is_empty() + } +} + +impl UriTemplateStr { + /// Expands the template with the given context. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// # #[cfg(feature = "alloc")] { + /// use iri_string::spec::UriSpec; + /// use iri_string::template::UriTemplateStr; + /// use iri_string::template::simple_context::SimpleContext; + /// + /// let mut context = SimpleContext::new(); + /// context.insert("username", "foo"); + /// + /// let template = UriTemplateStr::new("/users/{username}")?; + /// let expanded = template.expand::<UriSpec, _>(&context)?; + /// + /// assert_eq!( + /// expanded.to_string(), + /// "/users/foo" + /// ); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + /// + /// You can control allowed characters in the output by changing spec type. + /// + /// ``` + /// # use iri_string::template::Error; + /// # #[cfg(feature = "alloc")] { + /// use iri_string::spec::{IriSpec, UriSpec}; + /// use iri_string::template::UriTemplateStr; + /// use iri_string::template::simple_context::SimpleContext; + /// + /// let mut context = SimpleContext::new(); + /// context.insert("alpha", "\u{03B1}"); + /// + /// let template = UriTemplateStr::new("{?alpha}")?; + /// + /// assert_eq!( + /// template.expand::<UriSpec, _>(&context)?.to_string(), + /// "?alpha=%CE%B1", + /// "a URI cannot contain Unicode alpha (U+03B1), so it should be escaped" + /// ); + /// assert_eq!( + /// template.expand::<IriSpec, _>(&context)?.to_string(), + /// "?alpha=\u{03B1}", + /// "an IRI can contain Unicode alpha (U+03B1), so it written as is" + /// ); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + pub fn expand<'a, S: Spec, C: Context>( + &'a self, + context: &'a C, + ) -> Result<Expanded<'a, S, C>, Error> { + Expanded::new(self, context) + } + + /// Expands the template with the given dynamic context. + /// + #[cfg_attr( + feature = "alloc", + doc = concat!( + "If you need the allocated [`String`], use", + "[`expand_dynamic_to_string`][`Self::expand_dynamic_to_string`]." + ) + )] + /// + /// See the documentation for [`DynamicContext`] for usage. + pub fn expand_dynamic<S: Spec, W: fmt::Write, C: DynamicContext>( + &self, + writer: &mut W, + context: &mut C, + ) -> Result<(), Error> { + expand_whole_dynamic::<S, _, _>(self, writer, context) + } + + /// Expands the template into a string, with the given dynamic context. + /// + /// This is basically [`expand_dynamic`][`Self::expand_dynamic`] method + /// that returns an owned string instead of writing to the given writer. + /// + /// See the documentation for [`DynamicContext`] for usage. + /// + /// # Examples + /// + /// ``` + /// # #[cfg(feature = "alloc")] + /// # extern crate alloc; + /// # use iri_string::template::Error; + /// # #[cfg(feature = "alloc")] { + /// # use alloc::string::String; + /// use iri_string::template::UriTemplateStr; + /// # use iri_string::template::context::{DynamicContext, Visitor, VisitPurpose}; + /// use iri_string::spec::UriSpec; + /// + /// struct MyContext<'a> { + /// // See the documentation for `DynamicContext`. + /// # /// Target path. + /// # target: &'a str, + /// # /// Username. + /// # username: Option<&'a str>, + /// # /// A flag to remember whether the URI template + /// # /// attempted to use `username` variable. + /// # username_visited: bool, + /// } + /// # + /// # impl DynamicContext for MyContext<'_> { + /// # fn on_expansion_start(&mut self) { + /// # // Reset the state. + /// # self.username_visited = false; + /// # } + /// # fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result { + /// # match visitor.var_name().as_str() { + /// # "target" => visitor.visit_string(self.target), + /// # "username" => { + /// # if visitor.purpose() == VisitPurpose::Expand { + /// # // The variable `username` is being used + /// # // on the template expansion. + /// # // Don't care whether `username` is defined or not. + /// # self.username_visited = true; + /// # } + /// # if let Some(username) = &self.username { + /// # visitor.visit_string(username) + /// # } else { + /// # visitor.visit_undefined() + /// # } + /// # } + /// # _ => visitor.visit_undefined(), + /// # } + /// # } + /// # } + /// + /// let mut context = MyContext { + /// target: "/posts/1", + /// username: Some("the_admin"), + /// username_visited: false, + /// }; + /// + /// // No access to the variable `username`. + /// let template = UriTemplateStr::new("{+target}{?username}")?; + /// let s = template.expand_dynamic_to_string::<UriSpec, _>(&mut context)?; + /// assert_eq!(s, "/posts/1?username=the_admin"); + /// assert!(context.username_visited); + /// # } + /// # Ok::<_, Error>(()) + /// ``` + #[cfg(feature = "alloc")] + pub fn expand_dynamic_to_string<S: Spec, C: DynamicContext>( + &self, + context: &mut C, + ) -> Result<String, Error> { + let mut buf = String::new(); + expand_whole_dynamic::<S, _, _>(self, &mut buf, context)?; + Ok(buf) + } + + /// Returns an iterator of variables in the template. + /// + /// # Examples + /// + /// ``` + /// # use iri_string::template::Error; + /// use iri_string::template::UriTemplateStr; + /// + /// let template = UriTemplateStr::new("foo{/bar*,baz:4}{?qux}{&bar*}")?; + /// let mut vars = template.variables(); + /// assert_eq!(vars.next().map(|var| var.as_str()), Some("bar")); + /// assert_eq!(vars.next().map(|var| var.as_str()), Some("baz")); + /// assert_eq!(vars.next().map(|var| var.as_str()), Some("qux")); + /// assert_eq!(vars.next().map(|var| var.as_str()), Some("bar")); + /// # Ok::<_, Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn variables(&self) -> UriTemplateVariables<'_> { + UriTemplateVariables::new(self) + } +} + +impl fmt::Debug for UriTemplateStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("UriTemplateStr").field(&&self.inner).finish() + } +} + +impl AsRef<str> for UriTemplateStr { + #[inline] + fn as_ref(&self) -> &str { + &self.inner + } +} + +impl AsRef<UriTemplateStr> for UriTemplateStr { + #[inline] + fn as_ref(&self) -> &UriTemplateStr { + self + } +} + +#[cfg(feature = "alloc")] +impl<'a> From<&'a UriTemplateStr> for Cow<'a, UriTemplateStr> { + #[inline] + fn from(s: &'a UriTemplateStr) -> Self { + Cow::Borrowed(s) + } +} + +#[cfg(feature = "alloc")] +impl From<&UriTemplateStr> for Arc<UriTemplateStr> { + fn from(s: &UriTemplateStr) -> Self { + let inner: &str = s.as_str(); + let buf = Arc::<str>::from(inner); + // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so + // the memory layouts of `Arc<str>` and `Arc<UriTemplateStr>` are + // compatible. + unsafe { + let raw: *const str = Arc::into_raw(buf); + Self::from_raw(raw as *const UriTemplateStr) + } + } +} + +#[cfg(feature = "alloc")] +impl From<&UriTemplateStr> for Box<UriTemplateStr> { + fn from(s: &UriTemplateStr) -> Self { + let inner: &str = s.as_str(); + let buf = Box::<str>::from(inner); + // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so + // the memory layouts of `Box<str>` and `Box<UriTemplateStr>` are + // compatible. + unsafe { + let raw: *mut str = Box::into_raw(buf); + Self::from_raw(raw as *mut UriTemplateStr) + } + } +} + +#[cfg(feature = "alloc")] +impl From<&UriTemplateStr> for Rc<UriTemplateStr> { + fn from(s: &UriTemplateStr) -> Self { + let inner: &str = s.as_str(); + let buf = Rc::<str>::from(inner); + // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so + // the memory layouts of `Rc<str>` and `Rc<UriTemplateStr>` are + // compatible. + unsafe { + let raw: *const str = Rc::into_raw(buf); + Self::from_raw(raw as *const UriTemplateStr) + } + } +} + +impl<'a> From<&'a UriTemplateStr> for &'a str { + #[inline] + fn from(s: &'a UriTemplateStr) -> &'a str { + s.as_ref() + } +} + +impl<'a> TryFrom<&'a str> for &'a UriTemplateStr { + type Error = Error; + + #[inline] + fn try_from(s: &'a str) -> Result<Self, Self::Error> { + match validate_template_str(s) { + // SAFETY: just checked the string is valid. + Ok(()) => Ok(unsafe { UriTemplateStr::new_always_unchecked(s) }), + Err(e) => Err(e), + } + } +} + +impl<'a> TryFrom<&'a [u8]> for &'a UriTemplateStr { + type Error = Error; + + #[inline] + fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> { + let s = core::str::from_utf8(bytes) + .map_err(|e| Error::new(ErrorKind::InvalidUtf8, e.valid_up_to()))?; + match validate_template_str(s) { + // SAFETY: just checked the string is valid. + Ok(()) => Ok(unsafe { UriTemplateStr::new_always_unchecked(s) }), + Err(e) => Err(e), + } + } +} + +impl_cmp!(str, str, UriTemplateStr); +impl_cmp!(str, &str, UriTemplateStr); +impl_cmp!(str, str, &UriTemplateStr); + +impl fmt::Display for UriTemplateStr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Serde deserializer implementation. +#[cfg(feature = "serde")] +mod __serde_slice { + use super::UriTemplateStr; + + use core::fmt; + + use serde::{ + de::{self, Visitor}, + Deserialize, Deserializer, + }; + + /// Custom borrowed string visitor. + #[derive(Debug, Clone, Copy)] + struct CustomStrVisitor; + + impl<'de> Visitor<'de> for CustomStrVisitor { + type Value = &'de UriTemplateStr; + + #[inline] + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("URI template string") + } + + #[inline] + fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> + where + E: de::Error, + { + <&'de UriTemplateStr as TryFrom<&'de str>>::try_from(v).map_err(E::custom) + } + } + + // About `'de` and `'a`, see + // <https://serde.rs/lifetimes.html#the-deserializede-lifetime>. + impl<'a, 'de: 'a> Deserialize<'de> for &'a UriTemplateStr { + #[inline] + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_string(CustomStrVisitor) + } + } +} + +/// An iterator of variables in a URI template. +#[derive(Debug, Clone)] +pub struct UriTemplateVariables<'a> { + /// Chunks iterator. + chunks: Chunks<'a>, + /// Variables in the last chunk. + vars_in_chunk: Option<VarListIter<'a>>, +} + +impl<'a> UriTemplateVariables<'a> { + /// Creates a variables iterator from the URI template. + #[inline] + #[must_use] + fn new(template: &'a UriTemplateStr) -> Self { + Self { + chunks: Chunks::new(template), + vars_in_chunk: None, + } + } +} + +impl<'a> Iterator for UriTemplateVariables<'a> { + type Item = VarName<'a>; + + fn next(&mut self) -> Option<Self::Item> { + loop { + if let Some(vars) = &mut self.vars_in_chunk { + match vars.next() { + Some((_len, spec)) => return Some(spec.name()), + None => self.vars_in_chunk = None, + } + } + let expr = self.chunks.find_map(|chunk| match chunk { + Chunk::Literal(_) => None, + Chunk::Expr(v) => Some(v), + }); + self.vars_in_chunk = match expr { + Some(expr) => Some(expr.decompose().1.into_iter()), + None => return None, + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::spec::IriSpec; + use crate::template::context::{AssocVisitor, ListVisitor, Visitor}; + + struct TestContext; + impl Context for TestContext { + fn visit<V: Visitor>(&self, visitor: V) -> V::Result { + match visitor.var_name().as_str() { + "str" => visitor.visit_string("string"), + "list" => visitor + .visit_list() + .visit_items_and_finish(["item0", "item1", "item2"]), + "assoc" => visitor + .visit_assoc() + .visit_entries_and_finish([("key0", "value0"), ("key1", "value1")]), + _ => visitor.visit_undefined(), + } + } + } + + #[test] + fn expand_error_pos() { + { + let e = UriTemplateStr::new("foo{list:4}") + .unwrap() + .expand::<IriSpec, _>(&TestContext) + .err() + .map(|e| e.location()); + assert_eq!(e, Some("foo{".len())); + } + + { + let e = UriTemplateStr::new("foo{/list*,list:4}") + .unwrap() + .expand::<IriSpec, _>(&TestContext) + .err() + .map(|e| e.location()); + assert_eq!(e, Some("foo{/list*,".len())); + } + + { + let e = UriTemplateStr::new("foo{/str:3,list*,assoc:4}") + .unwrap() + .expand::<IriSpec, _>(&TestContext) + .err() + .map(|e| e.location()); + assert_eq!(e, Some("foo{/str:3,list*,".len())); + } + } +} diff --git a/vendor/iri-string/src/template/string/owned.rs b/vendor/iri-string/src/template/string/owned.rs new file mode 100644 index 00000000..afd201b3 --- /dev/null +++ b/vendor/iri-string/src/template/string/owned.rs @@ -0,0 +1,296 @@ +//! Owned `UriTemplateString`. + +use core::fmt; + +use alloc::borrow::Cow; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::borrow::ToOwned; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::boxed::Box; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::String; + +use crate::template::error::{CreationError, Error, ErrorKind}; +use crate::template::parser::validate_template_str; +use crate::template::string::UriTemplateStr; + +/// An owned slice of a URI template. +/// +/// URI Template is defined by [RFC 6570]. +/// +/// Note that "URI Template" can also be used for IRI. +/// +/// [RFC 6570]: https://www.rfc-editor.org/rfc/rfc6570.html +/// +/// # Valid values +/// +/// This type can have a URI template string. +// Note that `From<$ty> for {Arc,Rc}<$slice>` is currently not implemented since +// this won't reuse allocated memory and hides internal memory reallocation. See +// <https://github.com/lo48576/iri-string/issues/20#issuecomment-1105207849>. +// However, this is not decided with firm belief or opinion, so there would be +// a chance that they are implemented in future. +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UriTemplateString { + /// Inner data. + inner: String, +} + +impl UriTemplateString { + /// Creates a new string without validation. + /// + /// This does not validate the given string, so it is caller's + /// responsibility to ensure the given string is valid. + /// + /// # Safety + /// + /// The given string must be syntactically valid as `Self` type. + /// If not, any use of the returned value or the call of this + /// function itself may result in undefined behavior. + #[inline] + #[must_use] + pub unsafe fn new_unchecked(s: alloc::string::String) -> Self { + // The construction itself can be written in safe Rust, but + // every other place including unsafe functions expects + // `self.inner` to be syntactically valid as `Self`. In order to + // make them safe, the construction should validate the value + // or at least should require users to validate the value by + // making the function `unsafe`. + Self { inner: s } + } + + /// Shrinks the capacity of the inner buffer to match its length. + #[inline] + pub fn shrink_to_fit(&mut self) { + self.inner.shrink_to_fit() + } + + /// Returns the internal buffer capacity in bytes. + #[inline] + #[must_use] + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + /// Returns the borrowed IRI string slice. + /// + /// This is equivalent to `&*self`. + #[inline] + #[must_use] + pub fn as_slice(&self) -> &UriTemplateStr { + self.as_ref() + } + + /// Appends the template string. + #[inline] + pub fn append(&mut self, other: &UriTemplateStr) { + self.inner.push_str(other.as_str()); + debug_assert!(validate_template_str(self.as_str()).is_ok()); + } +} + +impl AsRef<str> for UriTemplateString { + #[inline] + fn as_ref(&self) -> &str { + &self.inner + } +} + +impl AsRef<UriTemplateStr> for UriTemplateString { + #[inline] + fn as_ref(&self) -> &UriTemplateStr { + // SAFETY: `UriTemplateString and `UriTemplateStr` requires same validation, + // so the content of `self: &UriTemplateString` must be valid as `UriTemplateStr`. + unsafe { UriTemplateStr::new_always_unchecked(AsRef::<str>::as_ref(self)) } + } +} + +impl core::borrow::Borrow<str> for UriTemplateString { + #[inline] + fn borrow(&self) -> &str { + self.as_ref() + } +} + +impl core::borrow::Borrow<UriTemplateStr> for UriTemplateString { + #[inline] + fn borrow(&self) -> &UriTemplateStr { + self.as_ref() + } +} + +impl ToOwned for UriTemplateStr { + type Owned = UriTemplateString; + + #[inline] + fn to_owned(&self) -> Self::Owned { + self.into() + } +} + +impl From<&'_ UriTemplateStr> for UriTemplateString { + #[inline] + fn from(s: &UriTemplateStr) -> Self { + // This is safe because `s` must be valid. + Self { + inner: alloc::string::String::from(s.as_str()), + } + } +} + +impl From<UriTemplateString> for alloc::string::String { + #[inline] + fn from(s: UriTemplateString) -> Self { + s.inner + } +} + +impl<'a> From<UriTemplateString> for Cow<'a, UriTemplateStr> { + #[inline] + fn from(s: UriTemplateString) -> Cow<'a, UriTemplateStr> { + Cow::Owned(s) + } +} + +impl From<UriTemplateString> for Box<UriTemplateStr> { + #[inline] + fn from(s: UriTemplateString) -> Box<UriTemplateStr> { + let inner: String = s.into(); + let buf = Box::<str>::from(inner); + // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so + // the memory layouts of `Box<str>` and `Box<UriTemplateStr>` are + // compatible. Additionally, `UriTemplateString` and `UriTemplateStr` + // require the same syntax. + unsafe { + let raw: *mut str = Box::into_raw(buf); + Box::<UriTemplateStr>::from_raw(raw as *mut UriTemplateStr) + } + } +} + +impl TryFrom<&'_ str> for UriTemplateString { + type Error = Error; + + #[inline] + fn try_from(s: &str) -> Result<Self, Self::Error> { + <&UriTemplateStr>::try_from(s).map(Into::into) + } +} + +impl TryFrom<&'_ [u8]> for UriTemplateString { + type Error = Error; + + #[inline] + fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { + let s = core::str::from_utf8(bytes) + .map_err(|e| Error::new(ErrorKind::InvalidUtf8, e.valid_up_to()))?; + <&UriTemplateStr>::try_from(s).map(Into::into) + } +} + +impl core::convert::TryFrom<alloc::string::String> for UriTemplateString { + type Error = CreationError<String>; + + #[inline] + fn try_from(s: alloc::string::String) -> Result<Self, Self::Error> { + match <&UriTemplateStr>::try_from(s.as_str()) { + Ok(_) => { + // This is safe because `<&UriTemplateStr>::try_from(s)?` ensures + // that the string `s` is valid. + Ok(Self { inner: s }) + } + Err(e) => Err(CreationError::new(e, s)), + } + } +} + +impl alloc::str::FromStr for UriTemplateString { + type Err = Error; + + #[inline] + fn from_str(s: &str) -> Result<Self, Self::Err> { + TryFrom::try_from(s) + } +} + +impl core::ops::Deref for UriTemplateString { + type Target = UriTemplateStr; + + #[inline] + fn deref(&self) -> &UriTemplateStr { + self.as_ref() + } +} + +impl_cmp!(str, UriTemplateStr, Cow<'_, str>); +impl_cmp!(str, &UriTemplateStr, Cow<'_, str>); + +impl_cmp!(str, str, UriTemplateString); +impl_cmp!(str, &str, UriTemplateString); +impl_cmp!(str, Cow<'_, str>, UriTemplateString); +impl_cmp!(str, String, UriTemplateString); + +impl fmt::Display for UriTemplateString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Serde deserializer implementation. +#[cfg(feature = "serde")] +mod __serde_owned { + use super::UriTemplateString; + + use core::fmt; + + #[cfg(all(feature = "alloc", feature = "serde", not(feature = "std")))] + use alloc::string::String; + + use serde::{ + de::{self, Visitor}, + Deserialize, Deserializer, + }; + + /// Custom owned string visitor. + #[derive(Debug, Clone, Copy)] + struct CustomStringVisitor; + + impl Visitor<'_> for CustomStringVisitor { + type Value = UriTemplateString; + + #[inline] + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("URI template string") + } + + #[inline] + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: de::Error, + { + <UriTemplateString as TryFrom<&str>>::try_from(v).map_err(E::custom) + } + + #[cfg(feature = "serde")] + #[inline] + fn visit_string<E>(self, v: String) -> Result<Self::Value, E> + where + E: de::Error, + { + <UriTemplateString as TryFrom<String>>::try_from(v).map_err(E::custom) + } + } + + impl<'de> Deserialize<'de> for UriTemplateString { + #[inline] + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(CustomStringVisitor) + } + } +} |
