Skip to main content

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    /// If the key is invalid as a variable name, returns `None` without doing
88    /// anything.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// # use iri_string::template::Error;
94    /// # #[cfg(feature = "alloc")] {
95    /// use iri_string::spec::UriSpec;
96    /// use iri_string::template::UriTemplateStr;
97    /// use iri_string::template::simple_context::SimpleContext;
98    ///
99    /// let mut context = SimpleContext::new();
100    /// context.insert("username", "foo");
101    ///
102    /// let template = UriTemplateStr::new("/users/{username}")?;
103    /// let expanded = template.expand::<UriSpec, _>(&context)?;
104    ///
105    /// assert_eq!(
106    ///     expanded.to_string(),
107    ///     "/users/foo"
108    /// );
109    /// # }
110    /// # Ok::<_, Error>(())
111    /// ```
112    ///
113    /// Passing [`Value::Undefined`] removes the value from the context.
114    ///
115    /// ```
116    /// # use iri_string::template::Error;
117    /// # #[cfg(feature = "alloc")] {
118    /// use iri_string::spec::UriSpec;
119    /// use iri_string::template::UriTemplateStr;
120    /// use iri_string::template::simple_context::{SimpleContext, Value};
121    ///
122    /// let mut context = SimpleContext::new();
123    /// context.insert("username", "foo");
124    /// context.insert("username", Value::Undefined);
125    ///
126    /// let template = UriTemplateStr::new("/users/{username}")?;
127    /// let expanded = template.expand::<UriSpec, _>(&context)?;
128    ///
129    /// assert_eq!(
130    ///     expanded.to_string(),
131    ///     "/users/"
132    /// );
133    /// # }
134    /// # Ok::<_, Error>(())
135    /// ```
136    pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<Value>
137    where
138        K: Into<String>,
139        V: Into<Value>,
140    {
141        let key = key.into();
142        if VarName::new(&key).is_err() {
143            // The key is not valid as a variable name. `get()` only accepts
144            // `VarName<'_>` as a key, so the entry will never be looked up.
145            // Ignore this and do nothing.
146            return None;
147        }
148        match value.into() {
149            Value::Undefined => self.variables.remove(&key),
150            value => self.variables.insert(key, value),
151        }
152    }
153
154    /// Removes all entries in the context.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// # use iri_string::template::Error;
160    /// # #[cfg(feature = "alloc")] {
161    /// use iri_string::spec::UriSpec;
162    /// use iri_string::template::UriTemplateStr;
163    /// use iri_string::template::simple_context::SimpleContext;
164    ///
165    /// let template = UriTemplateStr::new("{foo,bar}")?;
166    /// let mut context = SimpleContext::new();
167    ///
168    /// context.insert("foo", "FOO");
169    /// context.insert("bar", "BAR");
170    /// assert_eq!(
171    ///     template.expand::<UriSpec, _>(&context)?.to_string(),
172    ///     "FOO,BAR"
173    /// );
174    ///
175    /// context.clear();
176    /// assert_eq!(
177    ///     template.expand::<UriSpec, _>(&context)?.to_string(),
178    ///     ""
179    /// );
180    /// # }
181    /// # Ok::<_, Error>(())
182    /// ```
183    #[inline]
184    pub fn clear(&mut self) {
185        self.variables.clear();
186    }
187
188    /// Returns a reference to the value for the key.
189    //
190    // QUESTION: Should hexdigits in percent-encoded triplets in varnames be
191    // compared case sensitively?
192    #[inline]
193    #[must_use]
194    pub fn get(&self, key: VarName<'_>) -> Option<&Value> {
195        self.variables.get(key.as_str())
196    }
197}
198
199impl Context for SimpleContext {
200    fn visit<V: Visitor>(&self, visitor: V) -> V::Result {
201        use crate::template::context::{AssocVisitor, ListVisitor};
202
203        let name = visitor.var_name().as_str();
204        match self.variables.get(name) {
205            None | Some(Value::Undefined) => visitor.visit_undefined(),
206            Some(Value::String(s)) => visitor.visit_string(s),
207            Some(Value::List(list)) => {
208                let mut visitor = visitor.visit_list();
209                if let ControlFlow::Break(res) =
210                    list.iter().try_for_each(|item| visitor.visit_item(item))
211                {
212                    return res;
213                }
214                visitor.finish()
215            }
216            Some(Value::Assoc(list)) => {
217                let mut visitor = visitor.visit_assoc();
218                if let ControlFlow::Break(res) =
219                    list.iter().try_for_each(|(k, v)| visitor.visit_entry(k, v))
220                {
221                    return res;
222                }
223                visitor.finish()
224            }
225        }
226    }
227}