Skip to main content

iri_string/template/
context.rs

1//! Template expansion context.
2//!
3//! # Examples
4//!
5//! 1. Define your context type.
6//! 2. Implement [`Context`] trait (and [`Context::visit`] method) for the type.
7//!     1. Get variable name by [`Visitor::var_name`] method.
8//!     2. Feed the corresponding value(s) by one of `Visitor::visit_*` methods.
9//!
10//! Note that contexts should return consistent result across multiple visits for
11//! the same variable. In other words, `Context::visit` should return the same
12//! result for the same `Visitor::var_name()` during the context is borrowed.
13//! If this condition is violated, the URI template processor can return
14//! invalid result or panic at worst.
15//!
16//! ```
17//! use iri_string::template::context::{Context, Visitor, ListVisitor, AssocVisitor};
18//!
19//! struct MyContext {
20//!     name: &'static str,
21//!     id: u64,
22//!     tags: &'static [&'static str],
23//!     children: &'static [(&'static str, usize)],
24//! }
25//!
26//! impl Context for MyContext {
27//!     fn visit<V: Visitor>(&self, visitor: V) -> V::Result {
28//!         let name = visitor.var_name().as_str();
29//!         match name {
30//!             "name" => visitor.visit_string(self.name),
31//!             "id" => visitor.visit_string(self.id),
32//!             "tags" => visitor.visit_list().visit_items_and_finish(self.tags),
33//!             "children" => visitor
34//!                 .visit_assoc()
35//!                 .visit_entries_and_finish(self.children.iter().copied()),
36//!             _ => visitor.visit_undefined(),
37//!         }
38//!    }
39//! }
40//! ```
41//!
42//! Also check [`impl_template_context_naive!`] macro if your visitor
43//! implementation will be straight-forward and most of field types are simple
44//! standard types. The macro might reduce boilerplate.
45//!
46//! [`impl_template_context_naive!`]: `crate::impl_template_context_naive!`
47//
48// # Developers note
49//
50// Visitor types **should not** be cloneable in order to enforce just one
51// visitor is used to visit a variable. If visitors are cloneable, it can make
52// the wrong usage to be available, i.e. storing cloned visitors somewhere and
53// using the wrong one.
54//
55// However, if visitors are made cloneable by any chance, it does not indicate
56// the whole implementation will be broken. Users can only use the visitors
57// through visitor traits (and their API do not allow cloning), so the logic
58// would work as expected if the internal usage of the visitors are correct.
59// Making visitors noncloneable is an optional safety guard (with no overhead).
60
61/// Implement [`VisitValueNaive`] for the type.
62///
63/// # Synopsis
64///
65/// ```text
66/// impl_template_context_naive! {
67///     MyContextType {
68///         my_field [ : "var_name" ] [ => fn_visit ]
69///     }
70/// }
71/// ```
72///
73/// * `my_field` is the field name of `MyContextType`.
74///     + If the context type is tuple, use the index like `0` and `1`.
75/// * `"var_name"` is a `&str` literal for a variable name.
76///     + If omitted, `my_field` is used as a variable name.
77/// * `fn_visit` is an expression of a visitor function for the field.
78///     + If omitted, [`template::context::visit_value_naive`][`visit_value_naive`]
79///       function will be used.
80///     + The signature should be <code>fn fn_visit<V: [Visitor][`Visitor`]>(visitor: V,
81///       my_field_value: &MyFieldType)</code>
82///     + Note that this function cannot be a closure, because it should be
83///       generic over `V: Visitor` parameter and the current Rust (as of Rust
84///       1.94) does not allow closures to be generic.
85///
86/// # Examples
87///
88/// ```
89/// # use iri_string::template::Error;
90/// use iri_string::impl_template_context_naive;
91/// use iri_string::spec::UriSpec;
92/// use iri_string::template::UriTemplateStr;
93/// use iri_string::template::context::Visitor;
94///
95/// struct MyContext {
96///     id: Option<u64>,
97///     name: &'static str,
98///     tags: &'static [&'static str],
99///     children: &'static [(&'static str, usize)],
100///     ignored: i32,
101/// }
102///
103/// impl_template_context_naive! {
104///     MyContext {
105///         // custom styling and custom field name.
106///         id: "user_id" => my_visit_user_id,
107///         // custom field name.
108///         name: "username",
109///         // default styling and default field name.
110///         tags,
111///         // custom styling.
112///         children => my_visit_slice_assoc,
113///     }
114/// }
115///
116/// fn my_visit_user_id<V>(visitor: V, id: &Option<u64>) -> V::Result
117/// where
118///     V: Visitor,
119/// {
120///     match id {
121///         Some(v) => visitor.visit_string(format_args!("{v:04}")),
122///         None => visitor.visit_undefined(),
123///     }
124/// }
125///
126/// fn my_visit_slice_assoc<V, K, T>(visitor: V, entries: &[(K, T)]) -> V::Result
127/// where
128///     V: Visitor,
129///     K: core::fmt::Display,
130///     T: core::fmt::Display,
131/// {
132///     visitor.visit_assoc_direct(entries.iter().map(|(k, v)| (k, v)))
133/// }
134///
135/// let context = MyContext {
136///     id: Some(42),
137///     name: "foo",
138///     tags: &["user", "admin"],
139///     children: &[("bar", 99), ("baz", 314)],
140///     ignored: 12345,
141/// };
142///
143/// // The examples below requires `alloc` feature
144/// // to be enabled (for `.to_string()`).
145/// # #[cfg(feature = "alloc")] {
146/// #
147/// let id_user_template = UriTemplateStr::new("{?user_id,username}").unwrap();
148/// assert_eq!(
149///     id_user_template.expand::<UriSpec, _>(&context)?.to_string(),
150///     "?user_id=0042&username=foo",
151///     "custom styling and custom field name is used"
152/// );
153///
154/// let tags_template = UriTemplateStr::new("{?tags*}").unwrap();
155/// assert_eq!(
156///     tags_template.expand::<UriSpec, _>(&context)?.to_string(),
157///     "?tags=user&tags=admin"
158/// );
159///
160/// let children_template = UriTemplateStr::new("{?children*}").unwrap();
161/// assert_eq!(
162///     children_template.expand::<UriSpec, _>(&context)?.to_string(),
163///     "?bar=99&baz=314"
164/// );
165///
166/// let ignored_template = UriTemplateStr::new("{?ignored}").unwrap();
167/// assert_eq!(
168///     ignored_template.expand::<UriSpec, _>(&context)?.to_string(),
169///     "",
170///     "`ignored` field is not visited so the value is undefined"
171/// );
172/// #
173/// # }
174/// # Ok::<(), Error>(())
175/// ```
176///
177/// For tuple struct:
178///
179/// ```
180/// # use iri_string::template::Error;
181/// use iri_string::impl_template_context_naive;
182/// use iri_string::spec::UriSpec;
183/// use iri_string::template::UriTemplateStr;
184/// use iri_string::template::context::Visitor;
185///
186/// struct Utf8Check(bool);
187///
188/// impl_template_context_naive! {
189///     Utf8Check {
190///         0: "utf8" => my_visit_utf8_bool,
191///     }
192/// }
193///
194/// fn my_visit_utf8_bool<V>(visitor: V, checked: &bool) -> V::Result
195/// where
196///     V: Visitor,
197/// {
198///     if *checked {
199///         // U+2713 CHECK MARK
200///         visitor.visit_string("\u{2713}")
201///     } else {
202///         visitor.visit_undefined()
203///     }
204/// }
205///
206/// let checked_ctx = Utf8Check(true);
207/// let unchecked_ctx = Utf8Check(false);
208///
209/// // The examples below requires `alloc` feature
210/// // to be enabled (for `.to_string()`).
211/// # #[cfg(feature = "alloc")] {
212/// #
213/// let utf8_template = UriTemplateStr::new("{?utf8}").unwrap();
214/// assert_eq!(
215///     utf8_template.expand::<UriSpec, _>(&checked_ctx)?.to_string(),
216///     "?utf8=%E2%9C%93"
217/// );
218/// assert_eq!(
219///     utf8_template.expand::<UriSpec, _>(&unchecked_ctx)?.to_string(),
220///     ""
221/// );
222/// #
223/// # }
224/// # Ok::<(), Error>(())
225/// ```
226#[macro_export]
227macro_rules! impl_template_context_naive {
228    ($ty:ty {
229        $(
230            $field:tt
231            $(: $var_name:literal)?
232            $(=> $fn_visit:expr)?
233        ),*
234        $(,)?
235    }) => {
236        impl $crate::template::context::Context for $ty {
237            fn visit<V>(&self, visitor: V) -> V::Result
238            where
239                V: $crate::template::context::Visitor,
240            {
241                match visitor.var_name().as_str() {
242                    $(
243                        $crate::impl_template_context_naive!(@ var_name $field, $([ $var_name ])?)
244                            => {
245                            let fn_visit = $crate::impl_template_context_naive!(@ fn_visit $($fn_visit)?);
246                            fn_visit(visitor, &self.$field)
247                        },
248                    )*
249                    _ => visitor.visit_undefined(),
250                }
251            }
252        }
253    };
254    (@ var_name $field:tt,) => {
255        stringify!($field)
256    };
257    (@ var_name $field:tt, [ $var_name:literal ]) => {
258        $var_name
259    };
260    (@ fn_visit $fn_visit:expr) => {
261        $fn_visit
262    };
263    (@ fn_visit) => {
264        $crate::template::context::visit_value_naive
265    };
266}
267
268use core::fmt;
269use core::ops::ControlFlow;
270
271pub use crate::template::components::VarName;
272pub use crate::template::value::{visit_value_naive, VisitValueNaive};
273
274/// A trait for types that can behave as a static URI template expansion context.
275///
276/// This type is for use with [`UriTemplateStr::expand`] method.
277///
278/// See [the module documentation][`crate::template`] for usage.
279///
280/// [`UriTemplateStr::expand`]: `crate::template::UriTemplateStr::expand`
281pub trait Context: Sized {
282    /// Visits a variable.
283    ///
284    /// To get variable name, use [`Visitor::var_name()`].
285    #[must_use]
286    fn visit<V: Visitor>(&self, visitor: V) -> V::Result;
287}
288
289/// A trait for types that can behave as a dynamic (mutable) URI template expansion context.
290///
291/// This type is for use with [`UriTemplateStr::expand_dynamic`] method and its
292/// family.
293///
294/// Note that "dynamic" here does not mean that the value of variables can
295/// change during a template expansion. The value should be fixed and consistent
296/// during each expansion, but the context is allowed to mutate itself if it
297/// does not break this rule.
298///
299/// # Exmaples
300///
301/// ```
302/// # #[cfg(feature = "alloc")]
303/// # extern crate alloc;
304/// # use iri_string::template::Error;
305/// # #[cfg(feature = "alloc")] {
306/// # use alloc::string::String;
307/// use iri_string::template::UriTemplateStr;
308/// use iri_string::template::context::{DynamicContext, Visitor, VisitPurpose};
309/// use iri_string::spec::UriSpec;
310///
311/// struct MyContext<'a> {
312///     /// Target path.
313///     target: &'a str,
314///     /// Username.
315///     username: Option<&'a str>,
316///     /// A flag to remember whether the URI template
317///     /// attempted to use `username` variable.
318///     username_visited: bool,
319/// }
320///
321/// impl DynamicContext for MyContext<'_> {
322///     fn on_expansion_start(&mut self) {
323///         // Reset the state.
324///         self.username_visited = false;
325///     }
326///     fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result {
327///         match visitor.var_name().as_str() {
328///             "target" => visitor.visit_string(self.target),
329///             "username" => {
330///                 if visitor.purpose() == VisitPurpose::Expand {
331///                     // The variable `username` is being used
332///                     // on the template expansion.
333///                     // Don't care whether `username` is defined or not.
334///                     self.username_visited = true;
335///                 }
336///                 if let Some(username) = &self.username {
337///                     visitor.visit_string(username)
338///                 } else {
339///                     visitor.visit_undefined()
340///                 }
341///             }
342///             _ => visitor.visit_undefined(),
343///         }
344///     }
345/// }
346///
347/// let mut context = MyContext {
348///     target: "/posts/1",
349///     username: Some("the_admin"),
350///     username_visited: false,
351/// };
352/// let mut buf = String::new();
353///
354/// // No access to the variable `username`.
355/// let template1 = UriTemplateStr::new("{+target}")?;
356/// template1.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?;
357/// assert_eq!(buf, "/posts/1");
358/// assert!(!context.username_visited);
359///
360/// buf.clear();
361/// // Will access to the variable `username`.
362/// let template2 = UriTemplateStr::new("{+target}{?username}")?;
363/// template2.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?;
364/// assert_eq!(buf, "/posts/1?username=the_admin");
365/// assert!(context.username_visited);
366///
367/// buf.clear();
368/// context.username = None;
369/// // Will access to the variable `username` but it is undefined.
370/// template2.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?;
371/// assert_eq!(buf, "/posts/1");
372/// assert!(
373///     context.username_visited,
374///     "`MyContext` can know and remember whether `visit_dynamic()` is called
375///      for `username`, even if its value is undefined"
376/// );
377/// # }
378/// # Ok::<_, Error>(())
379/// ```
380///
381/// [`UriTemplateStr::expand_dynamic`]: `crate::template::UriTemplateStr::expand_dynamic`
382pub trait DynamicContext: Sized {
383    /// Visits a variable.
384    ///
385    /// To get variable name, use [`Visitor::var_name()`].
386    ///
387    /// # Restriction
388    ///
389    /// The visit results should be consistent and unchanged between the last
390    /// time [`on_expansion_start`][`Self::on_expansion_start`] was called and
391    /// the next time [`on_expansion_end`][`Self::on_expansion_end`] will be
392    /// called. If this condition is violated, template expansion will produce
393    /// wrong result or may panic at worst.
394    #[must_use]
395    fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result;
396
397    /// A callback that is called before the expansion of a URI template.
398    #[inline]
399    fn on_expansion_start(&mut self) {}
400
401    /// A callback that is called after the expansion of a URI template.
402    #[inline]
403    fn on_expansion_end(&mut self) {}
404}
405
406impl<C: Context> DynamicContext for C {
407    #[inline]
408    fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result {
409        self.visit(visitor)
410    }
411}
412
413/// A purpose of a visit.
414///
415/// This enum is nonexhaustive since this partially exposes the internal
416/// implementation of the template expansion, and thus this is subject to
417/// change.
418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419#[non_exhaustive]
420pub enum VisitPurpose {
421    /// A visit for type checking.
422    Typecheck,
423    /// A visit for template expansion to retrieve the value.
424    Expand,
425}
426
427/// Variable visitor.
428///
429/// See [the module documentation][self] for usage.
430// NOTE (internal): Visitor types **should not** be cloneable.
431pub trait Visitor: Sized + private::Sealed {
432    /// Result of the visit.
433    type Result;
434    /// List visitor.
435    type ListVisitor: ListVisitor<Result = Self::Result>;
436    /// Associative array visitor.
437    type AssocVisitor: AssocVisitor<Result = Self::Result>;
438
439    /// Returns the name of the variable to visit.
440    #[must_use]
441    fn var_name(&self) -> VarName<'_>;
442    /// Returns the purpose of the visit.
443    ///
444    /// The template expansion algorithm checks the types for some variables
445    /// depending on its usage. To get the usage count correctly, you should
446    /// only count visits with [`VisitPurpose::Expand`].
447    ///
448    /// If you need to know whether the variable is accessed and does not
449    /// need dynamic context generation or access counts, consider using
450    /// [`UriTemplateStr::variables`] method to iterate the variables in the
451    /// URI template.
452    ///
453    /// [`UriTemplateStr::variables`]: `crate::template::UriTemplateStr::variables`
454    #[must_use]
455    fn purpose(&self) -> VisitPurpose;
456    /// Visits an undefined variable, i.e. indicates that the requested variable is unavailable.
457    #[must_use]
458    fn visit_undefined(self) -> Self::Result;
459    /// Visits a string variable.
460    #[must_use]
461    fn visit_string<T: fmt::Display>(self, v: T) -> Self::Result;
462    /// Visits a list variable.
463    ///
464    /// If all the items are in a single iterable value, you can use
465    /// [`visit_list_direct`][`Self::visit_list_direct`] instead for convenience.
466    #[must_use]
467    fn visit_list(self) -> Self::ListVisitor;
468    /// Visits an associative array variable.
469    ///
470    /// If all the entries are in a single iterable value, you can use
471    /// [`visit_assoc_direct`][`Self::visit_assoc_direct`] instead for convenience.
472    #[must_use]
473    fn visit_assoc(self) -> Self::AssocVisitor;
474
475    /// Visits just a single list and finishes the visit right away.
476    fn visit_list_direct<I, T>(self, items: I) -> Self::Result
477    where
478        I: IntoIterator<Item = T>,
479        T: fmt::Display,
480    {
481        self.visit_list().visit_items_and_finish(items)
482    }
483
484    /// Visits just a single associative array and finishes the visit right away.
485    fn visit_assoc_direct<I, K, T>(self, entries: I) -> Self::Result
486    where
487        I: IntoIterator<Item = (K, T)>,
488        K: fmt::Display,
489        T: fmt::Display,
490    {
491        self.visit_assoc().visit_entries_and_finish(entries)
492    }
493}
494
495/// List visitor.
496///
497/// See [the module documentation][self] for usage.
498// NOTE (internal): Visitor types **should not** be cloneable.
499pub trait ListVisitor: Sized + private::Sealed {
500    /// Result of the visit.
501    type Result;
502
503    /// Visits an item.
504    ///
505    /// If this returned `ControlFlow::Break(v)`, [`Context::visit`] should also
506    /// return this `v`.
507    ///
508    /// To feed multiple items at once, do
509    /// `items.into_iter().try_for_each(|item| self.visit_item(item))` for example.
510    fn visit_item<T: fmt::Display>(&mut self, item: T) -> ControlFlow<Self::Result>;
511    /// Finishes visiting the list.
512    #[must_use]
513    fn finish(self) -> Self::Result;
514
515    /// Visits items and finish.
516    #[must_use]
517    fn visit_items_and_finish<T, I>(mut self, items: I) -> Self::Result
518    where
519        T: fmt::Display,
520        I: IntoIterator<Item = T>,
521    {
522        match items.into_iter().try_for_each(|item| self.visit_item(item)) {
523            ControlFlow::Break(v) => v,
524            ControlFlow::Continue(()) => self.finish(),
525        }
526    }
527}
528
529/// Associative array visitor.
530///
531/// See [the module documentation][self] for usage.
532// NOTE (internal): Visitor types **should not** be cloneable.
533pub trait AssocVisitor: Sized + private::Sealed {
534    /// Result of the visit.
535    type Result;
536
537    /// Visits an entry.
538    ///
539    /// If this returned `ControlFlow::Break(v)`, [`Context::visit`] should also
540    /// return this `v`.
541    ///
542    /// To feed multiple items at once, do
543    /// `entries.into_iter().try_for_each(|(key, value)| self.visit_entry(key, value))`
544    /// for example.
545    fn visit_entry<K: fmt::Display, V: fmt::Display>(
546        &mut self,
547        key: K,
548        value: V,
549    ) -> ControlFlow<Self::Result>;
550    /// Finishes visiting the associative array.
551    #[must_use]
552    fn finish(self) -> Self::Result;
553
554    /// Visits entries and finish.
555    #[must_use]
556    fn visit_entries_and_finish<K, V, I>(mut self, entries: I) -> Self::Result
557    where
558        K: fmt::Display,
559        V: fmt::Display,
560        I: IntoIterator<Item = (K, V)>,
561    {
562        match entries
563            .into_iter()
564            .try_for_each(|(key, value)| self.visit_entry(key, value))
565        {
566            ControlFlow::Break(v) => v,
567            ControlFlow::Continue(()) => self.finish(),
568        }
569    }
570}
571
572/// Private module to put the trait to seal.
573pub(super) mod private {
574    /// A trait for visitor types of variables in a context.
575    pub trait Sealed {}
576}