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}