//! 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(&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(&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(&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::(&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::(&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::(&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(&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 DynamicContext for C { #[inline] fn visit_dynamic(&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; /// Associative array visitor. type AssocVisitor: AssocVisitor; /// 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(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(&mut self, item: T) -> ControlFlow; /// Finishes visiting the list. #[must_use] fn finish(self) -> Self::Result; /// Visits items and finish. #[must_use] fn visit_items_and_finish(mut self, items: I) -> Self::Result where T: fmt::Display, I: IntoIterator, { 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( &mut self, key: K, value: V, ) -> ControlFlow; /// Finishes visiting the associative array. #[must_use] fn finish(self) -> Self::Result; /// Visits entries and finish. #[must_use] fn visit_entries_and_finish(mut self, entries: I) -> Self::Result where K: fmt::Display, V: fmt::Display, I: IntoIterator, { 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 {} }