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}