summaryrefslogtreecommitdiff
path: root/vendor/iri-string/src/template/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/iri-string/src/template/context.rs')
-rw-r--r--vendor/iri-string/src/template/context.rs339
1 files changed, 339 insertions, 0 deletions
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 {}
+}