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}