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// # Developers note
43//
44// Visitor types **should not** be cloneable in order to enforce just one
45// visitor is used to visit a variable. If visitors are cloneable, it can make
46// the wrong usage to be available, i.e. storing cloned visitors somewhere and
47// using the wrong one.
48//
49// However, if visitors are made cloneable by any chance, it does not indicate
50// the whole implementation will be broken. Users can only use the visitors
51// through visitor traits (and their API do not allow cloning), so the logic
52// would work as expected if the internal usage of the visitors are correct.
53// Making visitors noncloneable is an optional safety guard (with no overhead).
54
55use core::fmt;
56use core::ops::ControlFlow;
57
58pub use crate::template::components::VarName;
59
60/// A trait for types that can behave as a static URI template expansion context.
61///
62/// This type is for use with [`UriTemplateStr::expand`] method.
63///
64/// See [the module documentation][`crate::template`] for usage.
65///
66/// [`UriTemplateStr::expand`]: `crate::template::UriTemplateStr::expand`
67pub trait Context: Sized {
68 /// Visits a variable.
69 ///
70 /// To get variable name, use [`Visitor::var_name()`].
71 #[must_use]
72 fn visit<V: Visitor>(&self, visitor: V) -> V::Result;
73}
74
75/// A trait for types that can behave as a dynamic (mutable) URI template expansion context.
76///
77/// This type is for use with [`UriTemplateStr::expand_dynamic`] method and its
78/// family.
79///
80/// Note that "dynamic" here does not mean that the value of variables can
81/// change during a template expansion. The value should be fixed and consistent
82/// during each expansion, but the context is allowed to mutate itself if it
83/// does not break this rule.
84///
85/// # Exmaples
86///
87/// ```
88/// # #[cfg(feature = "alloc")]
89/// # extern crate alloc;
90/// # use iri_string::template::Error;
91/// # #[cfg(feature = "alloc")] {
92/// # use alloc::string::String;
93/// use iri_string::template::UriTemplateStr;
94/// use iri_string::template::context::{DynamicContext, Visitor, VisitPurpose};
95/// use iri_string::spec::UriSpec;
96///
97/// struct MyContext<'a> {
98/// /// Target path.
99/// target: &'a str,
100/// /// Username.
101/// username: Option<&'a str>,
102/// /// A flag to remember whether the URI template
103/// /// attempted to use `username` variable.
104/// username_visited: bool,
105/// }
106///
107/// impl DynamicContext for MyContext<'_> {
108/// fn on_expansion_start(&mut self) {
109/// // Reset the state.
110/// self.username_visited = false;
111/// }
112/// fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result {
113/// match visitor.var_name().as_str() {
114/// "target" => visitor.visit_string(self.target),
115/// "username" => {
116/// if visitor.purpose() == VisitPurpose::Expand {
117/// // The variable `username` is being used
118/// // on the template expansion.
119/// // Don't care whether `username` is defined or not.
120/// self.username_visited = true;
121/// }
122/// if let Some(username) = &self.username {
123/// visitor.visit_string(username)
124/// } else {
125/// visitor.visit_undefined()
126/// }
127/// }
128/// _ => visitor.visit_undefined(),
129/// }
130/// }
131/// }
132///
133/// let mut context = MyContext {
134/// target: "/posts/1",
135/// username: Some("the_admin"),
136/// username_visited: false,
137/// };
138/// let mut buf = String::new();
139///
140/// // No access to the variable `username`.
141/// let template1 = UriTemplateStr::new("{+target}")?;
142/// template1.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?;
143/// assert_eq!(buf, "/posts/1");
144/// assert!(!context.username_visited);
145///
146/// buf.clear();
147/// // Will access to the variable `username`.
148/// let template2 = UriTemplateStr::new("{+target}{?username}")?;
149/// template2.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?;
150/// assert_eq!(buf, "/posts/1?username=the_admin");
151/// assert!(context.username_visited);
152///
153/// buf.clear();
154/// context.username = None;
155/// // Will access to the variable `username` but it is undefined.
156/// template2.expand_dynamic::<UriSpec, _, _>(&mut buf, &mut context)?;
157/// assert_eq!(buf, "/posts/1");
158/// assert!(
159/// context.username_visited,
160/// "`MyContext` can know and remember whether `visit_dynamic()` is called
161/// for `username`, even if its value is undefined"
162/// );
163/// # }
164/// # Ok::<_, Error>(())
165/// ```
166///
167/// [`UriTemplateStr::expand_dynamic`]: `crate::template::UriTemplateStr::expand_dynamic`
168pub trait DynamicContext: Sized {
169 /// Visits a variable.
170 ///
171 /// To get variable name, use [`Visitor::var_name()`].
172 ///
173 /// # Restriction
174 ///
175 /// The visit results should be consistent and unchanged between the last
176 /// time [`on_expansion_start`][`Self::on_expansion_start`] was called and
177 /// the next time [`on_expansion_end`][`Self::on_expansion_end`] will be
178 /// called. If this condition is violated, template expansion will produce
179 /// wrong result or may panic at worst.
180 #[must_use]
181 fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result;
182
183 /// A callback that is called before the expansion of a URI template.
184 #[inline]
185 fn on_expansion_start(&mut self) {}
186
187 /// A callback that is called after the expansion of a URI template.
188 #[inline]
189 fn on_expansion_end(&mut self) {}
190}
191
192impl<C: Context> DynamicContext for C {
193 #[inline]
194 fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result {
195 self.visit(visitor)
196 }
197}
198
199/// A purpose of a visit.
200///
201/// This enum is nonexhaustive since this partially exposes the internal
202/// implementation of the template expansion, and thus this is subject to
203/// change.
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205#[non_exhaustive]
206pub enum VisitPurpose {
207 /// A visit for type checking.
208 Typecheck,
209 /// A visit for template expansion to retrieve the value.
210 Expand,
211}
212
213/// Variable visitor.
214///
215/// See [the module documentation][self] for usage.
216// NOTE (internal): Visitor types **should not** be cloneable.
217pub trait Visitor: Sized + private::Sealed {
218 /// Result of the visit.
219 type Result;
220 /// List visitor.
221 type ListVisitor: ListVisitor<Result = Self::Result>;
222 /// Associative array visitor.
223 type AssocVisitor: AssocVisitor<Result = Self::Result>;
224
225 /// Returns the name of the variable to visit.
226 #[must_use]
227 fn var_name(&self) -> VarName<'_>;
228 /// Returns the purpose of the visit.
229 ///
230 /// The template expansion algorithm checks the types for some variables
231 /// depending on its usage. To get the usage count correctly, you should
232 /// only count visits with [`VisitPurpose::Expand`].
233 ///
234 /// If you need to know whether the variable is accessed and does not
235 /// need dynamic context generation or access counts, consider using
236 /// [`UriTemplateStr::variables`] method to iterate the variables in the
237 /// URI template.
238 ///
239 /// [`UriTemplateStr::variables`]: `crate::template::UriTemplateStr::variables`
240 #[must_use]
241 fn purpose(&self) -> VisitPurpose;
242 /// Visits an undefined variable, i.e. indicates that the requested variable is unavailable.
243 #[must_use]
244 fn visit_undefined(self) -> Self::Result;
245 /// Visits a string variable.
246 #[must_use]
247 fn visit_string<T: fmt::Display>(self, v: T) -> Self::Result;
248 /// Visits a list variable.
249 ///
250 /// If all the items are in a single iterable value, you can use
251 /// [`visit_list_direct`][`Self::visit_list_direct`] instead for convenience.
252 #[must_use]
253 fn visit_list(self) -> Self::ListVisitor;
254 /// Visits an associative array variable.
255 ///
256 /// If all the entries are in a single iterable value, you can use
257 /// [`visit_assoc_direct`][`Self::visit_assoc_direct`] instead for convenience.
258 #[must_use]
259 fn visit_assoc(self) -> Self::AssocVisitor;
260
261 /// Visits just a single list and finishes the visit right away.
262 fn visit_list_direct<I, T>(self, items: I) -> Self::Result
263 where
264 I: IntoIterator<Item = T>,
265 T: fmt::Display,
266 {
267 self.visit_list().visit_items_and_finish(items)
268 }
269
270 /// Visits just a single associative array and finishes the visit right away.
271 fn visit_assoc_direct<I, K, T>(self, entries: I) -> Self::Result
272 where
273 I: IntoIterator<Item = (K, T)>,
274 K: fmt::Display,
275 T: fmt::Display,
276 {
277 self.visit_assoc().visit_entries_and_finish(entries)
278 }
279}
280
281/// List visitor.
282///
283/// See [the module documentation][self] for usage.
284// NOTE (internal): Visitor types **should not** be cloneable.
285pub trait ListVisitor: Sized + private::Sealed {
286 /// Result of the visit.
287 type Result;
288
289 /// Visits an item.
290 ///
291 /// If this returned `ControlFlow::Break(v)`, [`Context::visit`] should also
292 /// return this `v`.
293 ///
294 /// To feed multiple items at once, do
295 /// `items.into_iter().try_for_each(|item| self.visit_item(item))` for example.
296 fn visit_item<T: fmt::Display>(&mut self, item: T) -> ControlFlow<Self::Result>;
297 /// Finishes visiting the list.
298 #[must_use]
299 fn finish(self) -> Self::Result;
300
301 /// Visits items and finish.
302 #[must_use]
303 fn visit_items_and_finish<T, I>(mut self, items: I) -> Self::Result
304 where
305 T: fmt::Display,
306 I: IntoIterator<Item = T>,
307 {
308 match items.into_iter().try_for_each(|item| self.visit_item(item)) {
309 ControlFlow::Break(v) => v,
310 ControlFlow::Continue(()) => self.finish(),
311 }
312 }
313}
314
315/// Associative array visitor.
316///
317/// See [the module documentation][self] for usage.
318// NOTE (internal): Visitor types **should not** be cloneable.
319pub trait AssocVisitor: Sized + private::Sealed {
320 /// Result of the visit.
321 type Result;
322
323 /// Visits an entry.
324 ///
325 /// If this returned `ControlFlow::Break(v)`, [`Context::visit`] should also
326 /// return this `v`.
327 ///
328 /// To feed multiple items at once, do
329 /// `entries.into_iter().try_for_each(|(key, value)| self.visit_entry(key, value))`
330 /// for example.
331 fn visit_entry<K: fmt::Display, V: fmt::Display>(
332 &mut self,
333 key: K,
334 value: V,
335 ) -> ControlFlow<Self::Result>;
336 /// Finishes visiting the associative array.
337 #[must_use]
338 fn finish(self) -> Self::Result;
339
340 /// Visits entries and finish.
341 #[must_use]
342 fn visit_entries_and_finish<K, V, I>(mut self, entries: I) -> Self::Result
343 where
344 K: fmt::Display,
345 V: fmt::Display,
346 I: IntoIterator<Item = (K, V)>,
347 {
348 match entries
349 .into_iter()
350 .try_for_each(|(key, value)| self.visit_entry(key, value))
351 {
352 ControlFlow::Break(v) => v,
353 ControlFlow::Continue(()) => self.finish(),
354 }
355 }
356}
357
358/// Private module to put the trait to seal.
359pub(super) mod private {
360 /// A trait for visitor types of variables in a context.
361 pub trait Sealed {}
362}