]>
Commit | Line | Data |
---|---|---|
2c00a5a8 XL |
1 | /// The Toml Insert extensions |
2 | ||
9fa01778 XL |
3 | #[cfg(feature = "typed")] |
4 | use serde::Serialize; | |
2c00a5a8 XL |
5 | use toml::Value; |
6 | ||
dc9dc135 XL |
7 | use crate::tokenizer::Token; |
8 | use crate::tokenizer::tokenize_with_seperator; | |
9 | use crate::error::{Error, Result}; | |
2c00a5a8 XL |
10 | |
11 | pub trait TomlValueInsertExt { | |
12 | ||
13 | /// Extension function for inserting a value in the current toml::Value document | |
14 | /// using a custom seperator. | |
15 | /// | |
16 | /// For difference to TomlSetExt::set() and friends, read [#semantics]. | |
17 | /// | |
18 | /// # Semantics | |
19 | /// | |
20 | /// The function automatically creates intermediate data structures based on the query string. | |
21 | /// That means, if the query string is `"a.b.c.[0]"`, but only a table `"a"` exists in the | |
22 | /// document, the function automatically creates a table `"b"` inside `"a"` and `"c"` inside | |
23 | /// `"b"`, and an array in `"c"`. The array index is ignored if the array is created. | |
24 | /// | |
25 | /// If an Array exists, but the specified index is larger than the last index, the array will | |
26 | /// be expanded by one element: If the array has a length of 3, but the query string specifies | |
27 | /// that the element should be put at 1000, the function ignores the large index and simply | |
28 | /// appends the value to the index. | |
29 | /// | |
30 | /// If a Value is inserted into an Array, the array indexes are shifted. Semantically this is | |
31 | /// the same as doing a `array.insert(4, _)` (see the standard library). | |
32 | /// | |
33 | /// ## Known Bugs | |
34 | /// | |
35 | /// The current implementation does _not_ create intermediate Arrays as described above. | |
36 | /// This is a known bug. So queries like "foo.bar.[0].baz" (or any query which has an array | |
37 | /// element) will fail with an error rather than work. | |
38 | /// | |
39 | /// # Return value | |
40 | /// | |
41 | /// If the insert operation worked correctly, `Ok(None)` is returned. | |
42 | /// If the insert operation replaced an existing value `Ok(Some(old_value))` is returned | |
43 | /// On failure, `Err(e)` is returned | |
44 | /// | |
45 | /// # Examples | |
46 | /// | |
47 | /// The following example shows a working `insert_with_seperator()` call on an empty toml | |
48 | /// document. The Value is inserted as `"foo.bar = 1"` in the document. | |
49 | /// | |
50 | /// ```rust | |
51 | /// extern crate toml; | |
52 | /// extern crate toml_query; | |
53 | /// | |
54 | /// let mut toml : toml::Value = toml::from_str("").unwrap(); | |
55 | /// let query = "foo.bar"; | |
56 | /// let sep = '.'; | |
57 | /// let val = toml::Value::Integer(1); | |
58 | /// | |
59 | /// let res = toml_query::insert::TomlValueInsertExt::insert_with_seperator(&mut toml, query, sep, val); | |
60 | /// assert!(res.is_ok()); | |
61 | /// let res = res.unwrap(); | |
62 | /// assert!(res.is_none()); | |
63 | /// ``` | |
64 | /// | |
65 | /// The following example shows a failing `insert_with_seperator()` call on an empty toml | |
66 | /// document. The Query does contain an array token, which does not yet work. | |
67 | /// | |
68 | /// ```rust,should_panic | |
69 | /// extern crate toml; | |
70 | /// extern crate toml_query; | |
71 | /// | |
72 | /// let mut toml : toml::Value = toml::from_str("").unwrap(); | |
73 | /// let query = "foo.[0]"; | |
74 | /// let sep = '.'; | |
75 | /// let val = toml::Value::Integer(1); | |
76 | /// | |
77 | /// let res = toml_query::insert::TomlValueInsertExt::insert_with_seperator(&mut toml, query, sep, val); | |
78 | /// assert!(res.is_ok()); // panics | |
79 | /// ``` | |
80 | /// | |
81 | fn insert_with_seperator(&mut self, query: &str, sep: char, value: Value) -> Result<Option<Value>>; | |
82 | ||
83 | /// Extension function for inserting a value from the current toml::Value document | |
84 | /// | |
85 | /// See documentation of `TomlValueinsertExt::insert_with_seperator` | |
86 | fn insert(&mut self, query: &str, value: Value) -> Result<Option<Value>> { | |
87 | self.insert_with_seperator(query, '.', value) | |
88 | } | |
89 | ||
9fa01778 XL |
90 | /// A convenience method for inserting any arbitrary serializable value. |
91 | #[cfg(feature = "typed")] | |
92 | fn insert_serialized<S: Serialize>(&mut self, query: &str, value: S) -> Result<Option<Value>> { | |
dc9dc135 | 93 | let value = Value::try_from(value).map_err(Error::TomlSerialize)?; |
9fa01778 XL |
94 | self.insert(query, value) |
95 | } | |
96 | ||
2c00a5a8 XL |
97 | } |
98 | ||
99 | impl TomlValueInsertExt for Value { | |
100 | ||
101 | fn insert_with_seperator(&mut self, query: &str, sep: char, value: Value) -> Result<Option<Value>> { | |
dc9dc135 | 102 | use crate::resolver::mut_creating_resolver::resolve; |
2c00a5a8 | 103 | |
dc9dc135 | 104 | let mut tokens = r#try!(tokenize_with_seperator(query, sep)); |
2c00a5a8 XL |
105 | let (val, last) = match tokens.pop_last() { |
106 | None => (self, Box::new(tokens)), | |
dc9dc135 | 107 | Some(last) => (r#try!(resolve(self, &tokens)), last), |
2c00a5a8 XL |
108 | |
109 | }; | |
110 | ||
111 | match *last { | |
112 | Token::Identifier { ident, .. } => { | |
113 | match val { | |
114 | &mut Value::Table(ref mut t) => { | |
115 | Ok(t.insert(ident, value)) | |
116 | }, | |
dc9dc135 | 117 | _ => Err(Error::NoIdentifierInArray(ident.clone())) |
2c00a5a8 XL |
118 | } |
119 | }, | |
120 | ||
121 | Token::Index { idx , .. } => { | |
122 | match val { | |
123 | &mut Value::Array(ref mut a) => { | |
124 | if a.len() > idx { | |
125 | a.insert(idx, value); | |
126 | Ok(None) | |
127 | } else { | |
128 | a.push(value); | |
129 | Ok(None) | |
130 | } | |
131 | }, | |
dc9dc135 | 132 | _ => Err(Error::NoIndexInTable(idx)) |
2c00a5a8 XL |
133 | } |
134 | }, | |
135 | } | |
136 | } | |
137 | ||
138 | } | |
139 | ||
140 | #[cfg(test)] | |
141 | mod test { | |
142 | use super::*; | |
143 | use toml::Value; | |
144 | use toml::from_str as toml_from_str; | |
145 | ||
146 | #[test] | |
147 | fn test_insert_one_token() { | |
dc9dc135 XL |
148 | use toml::map::Map; |
149 | let mut toml = Value::Table(Map::new()); | |
2c00a5a8 XL |
150 | |
151 | let res = toml.insert(&String::from("value"), Value::Integer(1)); | |
152 | println!("TOML: {:?}", toml); | |
153 | assert!(res.is_ok()); | |
154 | ||
155 | let res = res.unwrap(); | |
156 | assert!(res.is_none()); | |
157 | ||
158 | assert!(is_match!(toml, Value::Table(_))); | |
159 | match toml { | |
160 | Value::Table(ref t) => { | |
161 | assert!(!t.is_empty()); | |
162 | ||
163 | let val = t.get("value"); | |
164 | assert!(val.is_some(), "'value' from table {:?} should be Some(_), is None", t); | |
165 | let val = val.unwrap(); | |
166 | ||
167 | assert!(is_match!(val, &Value::Integer(1)), "Is not one: {:?}", val); | |
168 | }, | |
169 | _ => panic!("What just happenend?"), | |
170 | } | |
171 | } | |
172 | ||
173 | #[test] | |
174 | fn test_insert_with_seperator_into_table() { | |
175 | let mut toml : Value = toml_from_str(r#" | |
176 | [table] | |
177 | "#).unwrap(); | |
178 | ||
179 | let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1)); | |
180 | ||
181 | assert!(res.is_ok()); | |
182 | ||
183 | let res = res.unwrap(); | |
184 | assert!(res.is_none()); | |
185 | ||
186 | assert!(is_match!(toml, Value::Table(_))); | |
187 | match toml { | |
188 | Value::Table(ref t) => { | |
189 | assert!(!t.is_empty()); | |
190 | ||
191 | let table = t.get("table"); | |
192 | assert!(table.is_some()); | |
193 | ||
194 | let table = table.unwrap(); | |
195 | assert!(is_match!(table, &Value::Table(_))); | |
196 | match table { | |
197 | &Value::Table(ref t) => { | |
198 | assert!(!t.is_empty()); | |
199 | ||
200 | let a = t.get("a"); | |
201 | assert!(a.is_some()); | |
202 | ||
203 | let a = a.unwrap(); | |
204 | assert!(is_match!(a, &Value::Integer(1))); | |
205 | }, | |
206 | _ => panic!("What just happenend?"), | |
207 | } | |
208 | }, | |
209 | _ => panic!("What just happenend?"), | |
210 | } | |
211 | } | |
212 | ||
213 | #[test] | |
214 | fn test_insert_with_seperator_into_array() { | |
215 | use std::ops::Index; | |
216 | ||
217 | let mut toml : Value = toml_from_str(r#" | |
218 | array = [] | |
219 | "#).unwrap(); | |
220 | ||
221 | let res = toml.insert_with_seperator(&String::from("array.[0]"), '.', Value::Integer(1)); | |
222 | ||
223 | assert!(res.is_ok()); | |
224 | ||
225 | let res = res.unwrap(); | |
226 | assert!(res.is_none()); | |
227 | ||
228 | assert!(is_match!(toml, Value::Table(_))); | |
229 | match toml { | |
230 | Value::Table(ref t) => { | |
231 | assert!(!t.is_empty()); | |
232 | ||
233 | let array = t.get("array"); | |
234 | assert!(array.is_some()); | |
235 | ||
236 | let array = array.unwrap(); | |
237 | assert!(is_match!(array, &Value::Array(_))); | |
238 | match array { | |
239 | &Value::Array(ref a) => { | |
240 | assert!(!a.is_empty()); | |
241 | assert!(is_match!(a.index(0), &Value::Integer(1))); | |
242 | }, | |
243 | _ => panic!("What just happenend?"), | |
244 | } | |
245 | }, | |
246 | _ => panic!("What just happenend?"), | |
247 | } | |
248 | } | |
249 | ||
250 | #[test] | |
251 | fn test_insert_with_seperator_into_nested_table() { | |
252 | let mut toml : Value = toml_from_str(r#" | |
253 | [a.b.c] | |
254 | "#).unwrap(); | |
255 | ||
256 | let res = toml.insert_with_seperator(&String::from("a.b.c.d"), '.', Value::Integer(1)); | |
257 | ||
258 | assert!(res.is_ok()); | |
259 | ||
260 | let res = res.unwrap(); | |
261 | assert!(res.is_none()); | |
262 | ||
263 | assert!(is_match!(toml, Value::Table(_))); | |
264 | match toml { | |
265 | Value::Table(ref outer) => { | |
266 | assert!(!outer.is_empty()); | |
267 | let a_tab = outer.get("a"); | |
268 | assert!(a_tab.is_some()); | |
269 | ||
270 | let a_tab = a_tab.unwrap(); | |
271 | assert!(is_match!(a_tab, &Value::Table(_))); | |
272 | match a_tab { | |
273 | &Value::Table(ref a) => { | |
274 | assert!(!a.is_empty()); | |
275 | ||
276 | let b_tab = a.get("b"); | |
277 | assert!(b_tab.is_some()); | |
278 | ||
279 | let b_tab = b_tab.unwrap(); | |
280 | assert!(is_match!(b_tab, &Value::Table(_))); | |
281 | match b_tab { | |
282 | &Value::Table(ref b) => { | |
283 | assert!(!b.is_empty()); | |
284 | ||
285 | let c_tab = b.get("c"); | |
286 | assert!(c_tab.is_some()); | |
287 | ||
288 | let c_tab = c_tab.unwrap(); | |
289 | assert!(is_match!(c_tab, &Value::Table(_))); | |
290 | match c_tab { | |
291 | &Value::Table(ref c) => { | |
292 | assert!(!c.is_empty()); | |
293 | ||
294 | let d = c.get("d"); | |
295 | assert!(d.is_some()); | |
296 | ||
297 | let d = d.unwrap(); | |
298 | assert!(is_match!(d, &Value::Integer(1))); | |
299 | }, | |
300 | _ => panic!("What just happenend?"), | |
301 | } | |
302 | }, | |
303 | _ => panic!("What just happenend?"), | |
304 | } | |
305 | }, | |
306 | _ => panic!("What just happenend?"), | |
307 | } | |
308 | }, | |
309 | _ => panic!("What just happened?"), | |
310 | } | |
311 | } | |
312 | ||
313 | #[test] | |
314 | fn test_insert_with_seperator_into_table_where_array_is() { | |
315 | let mut toml : Value = toml_from_str(r#" | |
316 | table = [] | |
317 | "#).unwrap(); | |
318 | ||
319 | let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1)); | |
320 | ||
321 | assert!(res.is_err()); | |
322 | ||
323 | let err = res.unwrap_err(); | |
dc9dc135 | 324 | assert!(is_match!(err, Error::NoIdentifierInArray(_))); |
2c00a5a8 XL |
325 | } |
326 | ||
327 | #[test] | |
328 | fn test_insert_with_seperator_into_array_where_table_is() { | |
329 | let mut toml : Value = toml_from_str(r#" | |
330 | [table] | |
331 | "#).unwrap(); | |
332 | ||
333 | let res = toml.insert_with_seperator(&String::from("table.[0]"), '.', Value::Integer(1)); | |
334 | ||
335 | assert!(res.is_err()); | |
336 | ||
337 | let err = res.unwrap_err(); | |
dc9dc135 | 338 | assert!(is_match!(err, Error::NoIndexInTable(_))); |
2c00a5a8 XL |
339 | } |
340 | ||
341 | #[test] | |
342 | fn test_insert_with_seperator_into_array_between_values() { | |
343 | use std::ops::Index; | |
344 | ||
345 | let mut toml : Value = toml_from_str(r#" | |
346 | array = [1, 2, 3, 4, 5] | |
347 | "#).unwrap(); | |
348 | ||
349 | let res = toml.insert_with_seperator(&String::from("array.[2]"), '.', Value::Integer(6)); | |
350 | ||
351 | assert!(res.is_ok()); | |
352 | ||
353 | let res = res.unwrap(); | |
354 | assert!(res.is_none()); | |
355 | ||
356 | assert!(is_match!(toml, Value::Table(_))); | |
357 | match toml { | |
358 | Value::Table(ref t) => { | |
359 | assert!(!t.is_empty()); | |
360 | ||
361 | let array = t.get("array"); | |
362 | assert!(array.is_some()); | |
363 | ||
364 | let array = array.unwrap(); | |
365 | assert!(is_match!(array, &Value::Array(_))); | |
366 | match array { | |
367 | &Value::Array(ref a) => { | |
368 | assert!(!a.is_empty()); | |
369 | assert!(is_match!(a.index(0), &Value::Integer(1))); | |
370 | assert!(is_match!(a.index(1), &Value::Integer(2))); | |
371 | assert!(is_match!(a.index(2), &Value::Integer(6))); | |
372 | assert!(is_match!(a.index(3), &Value::Integer(3))); | |
373 | assert!(is_match!(a.index(4), &Value::Integer(4))); | |
374 | assert!(is_match!(a.index(5), &Value::Integer(5))); | |
375 | }, | |
376 | _ => panic!("What just happenend?"), | |
377 | } | |
378 | }, | |
379 | _ => panic!("What just happenend?"), | |
380 | } | |
381 | } | |
382 | ||
383 | #[test] | |
384 | fn test_insert_with_seperator_into_table_with_nonexisting_keys() { | |
385 | let mut toml : Value = toml_from_str(r#" | |
386 | "#).unwrap(); | |
387 | ||
388 | let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1)); | |
389 | ||
390 | assert!(res.is_ok()); | |
391 | ||
392 | let res = res.unwrap(); | |
393 | assert!(res.is_none()); | |
394 | ||
395 | assert!(is_match!(toml, Value::Table(_))); | |
396 | match toml { | |
397 | Value::Table(ref t) => { | |
398 | assert!(!t.is_empty()); | |
399 | ||
400 | let table = t.get("table"); | |
401 | assert!(table.is_some()); | |
402 | ||
403 | let table = table.unwrap(); | |
404 | assert!(is_match!(table, &Value::Table(_))); | |
405 | match table { | |
406 | &Value::Table(ref t) => { | |
407 | assert!(!t.is_empty()); | |
408 | ||
409 | let a = t.get("a"); | |
410 | assert!(a.is_some()); | |
411 | ||
412 | let a = a.unwrap(); | |
413 | assert!(is_match!(a, &Value::Integer(1))); | |
414 | }, | |
415 | _ => panic!("What just happenend?"), | |
416 | } | |
417 | }, | |
418 | _ => panic!("What just happenend?"), | |
419 | } | |
420 | } | |
421 | ||
422 | } | |
423 |