iri_string/template/
simple_context.rs

1//! Simple general-purpose context type.
2
3use core::ops::ControlFlow;
4
5use alloc::collections::BTreeMap;
6#[cfg(all(feature = "alloc", not(feature = "std")))]
7use alloc::string::String;
8#[cfg(all(feature = "alloc", not(feature = "std")))]
9use alloc::vec::Vec;
10
11use crate::template::context::{Context, VarName, Visitor};
12
13/// Value.
14#[derive(Debug, Clone)]
15pub enum Value {
16    /// Undefined (i.e. null).
17    Undefined,
18    /// String value.
19    String(String),
20    /// List.
21    List(Vec<String>),
22    /// Associative array.
23    Assoc(Vec<(String, String)>),
24}
25
26impl From<&str> for Value {
27    #[inline]
28    fn from(v: &str) -> Self {
29        Self::String(v.into())
30    }
31}
32
33impl From<String> for Value {
34    #[inline]
35    fn from(v: String) -> Self {
36        Self::String(v)
37    }
38}
39
40/// Simple template expansion context.
41#[derive(Default, Debug, Clone)]
42pub struct SimpleContext {
43    /// Variable values.
44    // Any map types (including `HashMap`) is ok, but the hash map is not provided by `alloc`.
45    //
46    // QUESTION: Should hexdigits in percent-encoded triplets in varnames be
47    // compared case sensitively?
48    variables: BTreeMap<String, Value>,
49}
50
51impl SimpleContext {
52    /// Creates a new empty context.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// # use iri_string::template::Error;
58    /// # #[cfg(feature = "alloc")] {
59    /// use iri_string::spec::UriSpec;
60    /// use iri_string::template::UriTemplateStr;
61    /// use iri_string::template::simple_context::SimpleContext;
62    ///
63    /// let empty_ctx = SimpleContext::new();
64    /// let template = UriTemplateStr::new("{no_such_variable}")?;
65    /// let expanded = template.expand::<UriSpec, _>(&empty_ctx)?;
66    ///
67    /// assert_eq!(
68    ///     expanded.to_string(),
69    ///     ""
70    /// );
71    /// # }
72    /// # Ok::<_, Error>(())
73    /// ```
74    #[inline]
75    #[must_use]
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    /// Inserts a variable.
81    ///
82    /// Passing [`Value::Undefined`] removes the value from the context.
83    ///
84    /// The entry will be inserted or removed even if the key is invalid as a
85    /// variable name. Such entries will be simply ignored on expansion.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// # use iri_string::template::Error;
91    /// # #[cfg(feature = "alloc")] {
92    /// use iri_string::spec::UriSpec;
93    /// use iri_string::template::UriTemplateStr;
94    /// use iri_string::template::simple_context::SimpleContext;
95    ///
96    /// let mut context = SimpleContext::new();
97    /// context.insert("username", "foo");
98    ///
99    /// let template = UriTemplateStr::new("/users/{username}")?;
100    /// let expanded = template.expand::<UriSpec, _>(&context)?;
101    ///
102    /// assert_eq!(
103    ///     expanded.to_string(),
104    ///     "/users/foo"
105    /// );
106    /// # }
107    /// # Ok::<_, Error>(())
108    /// ```
109    ///
110    /// Passing [`Value::Undefined`] removes the value from the context.
111    ///
112    /// ```
113    /// # use iri_string::template::Error;
114    /// ## [cfg(feature = "alloc")] {
115    /// use iri_string::spec::UriSpec;
116    /// use iri_string::template::UriTemplateStr;
117    /// use iri_string::template::simple_context::{SimpleContext, Value};
118    ///
119    /// let mut context = SimpleContext::new();
120    /// context.insert("username", "foo");
121    /// context.insert("username", Value::Undefined);
122    ///
123    /// let template = UriTemplateStr::new("/users/{username}")?;
124    /// let expanded = template.expand::<UriSpec, _>(&context)?;
125    ///
126    /// assert_eq!(
127    ///     expanded.to_string(),
128    ///     "/users/"
129    /// );
130    /// # }
131    /// # Ok::<_, Error>(())
132    /// ```
133    pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<Value>
134    where
135        K: Into<String>,
136        V: Into<Value>,
137    {
138        let key = key.into();
139        match value.into() {
140            Value::Undefined => self.variables.remove(&key),
141            value => self.variables.insert(key, value),
142        }
143    }
144
145    /// Removes all entries in the context.
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// # use iri_string::template::Error;
151    /// # #[cfg(feature = "alloc")] {
152    /// use iri_string::spec::UriSpec;
153    /// use iri_string::template::UriTemplateStr;
154    /// use iri_string::template::simple_context::SimpleContext;
155    ///
156    /// let template = UriTemplateStr::new("{foo,bar}")?;
157    /// let mut context = SimpleContext::new();
158    ///
159    /// context.insert("foo", "FOO");
160    /// context.insert("bar", "BAR");
161    /// assert_eq!(
162    ///     template.expand::<UriSpec, _>(&context)?.to_string(),
163    ///     "FOO,BAR"
164    /// );
165    ///
166    /// context.clear();
167    /// assert_eq!(
168    ///     template.expand::<UriSpec, _>(&context)?.to_string(),
169    ///     ""
170    /// );
171    /// # }
172    /// # Ok::<_, Error>(())
173    /// ```
174    #[inline]
175    pub fn clear(&mut self) {
176        self.variables.clear();
177    }
178
179    /// Returns a reference to the value for the key.
180    //
181    // QUESTION: Should hexdigits in percent-encoded triplets in varnames be
182    // compared case sensitively?
183    #[inline]
184    #[must_use]
185    pub fn get(&self, key: VarName<'_>) -> Option<&Value> {
186        self.variables.get(key.as_str())
187    }
188}
189
190impl Context for SimpleContext {
191    fn visit<V: Visitor>(&self, visitor: V) -> V::Result {
192        use crate::template::context::{AssocVisitor, ListVisitor};
193
194        let name = visitor.var_name().as_str();
195        match self.variables.get(name) {
196            None | Some(Value::Undefined) => visitor.visit_undefined(),
197            Some(Value::String(s)) => visitor.visit_string(s),
198            Some(Value::List(list)) => {
199                let mut visitor = visitor.visit_list();
200                if let ControlFlow::Break(res) =
201                    list.iter().try_for_each(|item| visitor.visit_item(item))
202                {
203                    return res;
204                }
205                visitor.finish()
206            }
207            Some(Value::Assoc(list)) => {
208                let mut visitor = visitor.visit_assoc();
209                if let ControlFlow::Break(res) =
210                    list.iter().try_for_each(|(k, v)| visitor.visit_entry(k, v))
211                {
212                    return res;
213                }
214                visitor.finish()
215            }
216        }
217    }
218}