1#[cfg(feature = "typed")]
4use std::fmt::Debug;
5
6#[cfg(feature = "typed")]
7use serde::{Deserialize, Serialize};
8use toml::Value;
9
10use crate::error::{Error, Result};
11use crate::tokenizer::tokenize_with_seperator;
12
13pub trait TomlValueReadExt<'doc> {
14 fn read_with_seperator(&'doc self, query: &str, sep: char) -> Result<Option<&'doc Value>>;
17
18 fn read_mut_with_seperator(
21 &'doc mut self,
22 query: &str,
23 sep: char,
24 ) -> Result<Option<&'doc mut Value>>;
25
26 fn read(&'doc self, query: &str) -> Result<Option<&'doc Value>> {
28 self.read_with_seperator(query, '.')
29 }
30
31 fn read_mut(&'doc mut self, query: &str) -> Result<Option<&'doc mut Value>> {
33 self.read_mut_with_seperator(query, '.')
34 }
35
36 #[cfg(feature = "typed")]
37 fn read_deserialized<'de, D: Deserialize<'de>>(&'doc self, query: &str) -> Result<Option<D>> {
38 let raw = self.read(query)?;
39
40 match raw {
41 Some(value) => {
42 let deserialized = value.clone().try_into().map_err(Error::TomlDeserialize)?;
43 Ok(Some(deserialized))
44 }
45 None => Ok(None),
46 }
47 }
48
49 #[cfg(feature = "typed")]
50 fn read_partial<'a, P: Partial<'a>>(&'doc self) -> Result<Option<P::Output>> {
51 self.read_deserialized::<P::Output>(P::LOCATION)
52 }
53}
54
55#[cfg(feature = "typed")]
57pub trait Partial<'a> {
58 const LOCATION: &'static str;
60
61 type Output: Serialize + Deserialize<'a> + Debug;
63}
64
65impl<'doc> TomlValueReadExt<'doc> for Value {
66 fn read_with_seperator(&'doc self, query: &str, sep: char) -> Result<Option<&'doc Value>> {
67 use crate::resolver::non_mut_resolver::resolve;
68
69 tokenize_with_seperator(query, sep).and_then(move |tokens| resolve(self, &tokens, false))
70 }
71
72 fn read_mut_with_seperator(
73 &'doc mut self,
74 query: &str,
75 sep: char,
76 ) -> Result<Option<&'doc mut Value>> {
77 use crate::resolver::mut_resolver::resolve;
78
79 tokenize_with_seperator(query, sep).and_then(move |tokens| resolve(self, &tokens, false))
80 }
81}
82
83pub trait TomlValueReadTypeExt<'doc>: TomlValueReadExt<'doc> {
84 fn read_string(&'doc self, query: &str) -> Result<Option<String>>;
85 fn read_int(&'doc self, query: &str) -> Result<Option<i64>>;
86 fn read_float(&'doc self, query: &str) -> Result<Option<f64>>;
87 fn read_bool(&'doc self, query: &str) -> Result<Option<bool>>;
88}
89
90macro_rules! make_type_getter {
91 ($fnname:ident, $rettype:ty, $typename:expr, $matcher:pat => $implementation:expr) => {
92 fn $fnname(&'doc self, query: &str) -> Result<Option<$rettype>> {
93 self.read_with_seperator(query, '.').and_then(|o| match o {
94 $matcher => Ok(Some($implementation)),
95 Some(o) => Err(Error::TypeError($typename, crate::util::name_of_val(&o)).into()),
96 None => Ok(None),
97 })
98 }
99 };
100}
101
102impl<'doc, T> TomlValueReadTypeExt<'doc> for T
103where
104 T: TomlValueReadExt<'doc>,
105{
106 make_type_getter!(read_string, String, "String", Some(&Value::String(ref obj)) => obj.clone());
107 make_type_getter!(read_int, i64, "Integer", Some(&Value::Integer(obj)) => obj);
108 make_type_getter!(read_float, f64, "Float", Some(&Value::Float(obj)) => obj);
109 make_type_getter!(read_bool, bool, "Boolean", Some(&Value::Boolean(obj)) => obj);
110}
111
112#[cfg(test)]
113mod test {
114 use super::*;
115 use toml::from_str as toml_from_str;
116
117 #[test]
118 fn test_read_empty() {
119 let toml: Value = toml_from_str("").unwrap();
120
121 let val = toml.read_with_seperator(&String::from("a"), '.');
122
123 assert!(val.is_ok());
124 let val = val.unwrap();
125
126 assert!(val.is_none());
127 }
128
129 #[test]
130 fn test_read_table() {
131 let toml: Value = toml_from_str(
132 r#"
133 [table]
134 "#,
135 )
136 .unwrap();
137
138 let val = toml.read_with_seperator(&String::from("table"), '.');
139
140 assert!(val.is_ok());
141 let val = val.unwrap();
142
143 assert!(val.is_some());
144 let val = val.unwrap();
145
146 assert!(is_match!(val, &Value::Table(_)));
147 match val {
148 Value::Table(ref t) => assert!(t.is_empty()),
149 _ => panic!("What just happened?"),
150 }
151 }
152
153 #[test]
154 fn test_read_table_value() {
155 let toml: Value = toml_from_str(
156 r#"
157 [table]
158 a = 1
159 "#,
160 )
161 .unwrap();
162
163 let val = toml.read_with_seperator(&String::from("table.a"), '.');
164
165 assert!(val.is_ok());
166 let val = val.unwrap();
167
168 assert!(val.is_some());
169 let val = val.unwrap();
170
171 assert!(is_match!(val, &Value::Integer(1)));
172 }
173
174 #[test]
175 fn test_read_empty_table_value() {
176 let toml: Value = toml_from_str(
177 r#"
178 [table]
179 "#,
180 )
181 .unwrap();
182
183 let val = toml.read_with_seperator(&String::from("table.a"), '.');
184 assert!(val.is_ok());
185 let val = val.unwrap();
186
187 assert!(val.is_none());
188 }
189
190 #[test]
191 fn test_read_table_index() {
192 let toml: Value = toml_from_str(
193 r#"
194 [table]
195 "#,
196 )
197 .unwrap();
198
199 let val = toml.read_with_seperator(&String::from("table.[0]"), '.');
200 assert!(val.is_err());
201 let err = val.unwrap_err();
202
203 assert!(is_match!(err, Error::NoIndexInTable(_)));
204 }
205
206 #[test]
213 fn test_read_empty_without_seperator() {
214 let toml: Value = toml_from_str("").unwrap();
215
216 let val = toml.read(&String::from("a"));
217 assert!(val.is_ok());
218 let val = val.unwrap();
219
220 assert!(val.is_none());
221 }
222
223 #[test]
224 fn test_read_table_without_seperator() {
225 let toml: Value = toml_from_str(
226 r#"
227 [table]
228 "#,
229 )
230 .unwrap();
231
232 let val = toml.read(&String::from("table"));
233
234 assert!(val.is_ok());
235 let val = val.unwrap();
236
237 assert!(val.is_some());
238 let val = val.unwrap();
239
240 assert!(is_match!(val, &Value::Table(_)));
241 match val {
242 Value::Table(ref t) => assert!(t.is_empty()),
243 _ => panic!("What just happened?"),
244 }
245 }
246
247 #[test]
248 fn test_read_table_value_without_seperator() {
249 let toml: Value = toml_from_str(
250 r#"
251 [table]
252 a = 1
253 "#,
254 )
255 .unwrap();
256
257 let val = toml.read(&String::from("table.a"));
258
259 assert!(val.is_ok());
260 let val = val.unwrap();
261
262 assert!(val.is_some());
263 let val = val.unwrap();
264
265 assert!(is_match!(val, &Value::Integer(1)));
266 }
267
268 #[test]
269 fn test_read_empty_table_value_without_seperator() {
270 let toml: Value = toml_from_str(
271 r#"
272 [table]
273 "#,
274 )
275 .unwrap();
276
277 let val = toml.read(&String::from("table.a"));
278 assert!(val.is_ok());
279 let val = val.unwrap();
280
281 assert!(val.is_none());
282 }
283
284 #[test]
285 fn test_read_table_index_without_seperator() {
286 let toml: Value = toml_from_str(
287 r#"
288 [table]
289 "#,
290 )
291 .unwrap();
292
293 let val = toml.read(&String::from("table.[0]"));
294 assert!(val.is_err());
295 let err = val.unwrap_err();
296
297 assert!(is_match!(err, Error::NoIndexInTable(_)));
298 }
299}
300
301#[cfg(test)]
302mod high_level_fn_test {
303 use super::*;
304 use toml::from_str as toml_from_str;
305
306 #[test]
307 fn test_read_table_value() {
308 let toml: Value = toml_from_str(
309 r#"
310 [table]
311 a = 1
312 "#,
313 )
314 .unwrap();
315
316 let val = toml.read_int("table.a").unwrap();
317
318 assert_eq!(val.unwrap(), 1);
319 }
320
321 #[cfg(feature = "typed")]
322 #[test]
323 fn test_name() {
324 let toml: Value = toml_from_str(
325 r#"
326 [table]
327 a = 1
328 "#,
329 )
330 .unwrap();
331
332 let val: u32 = toml.read_deserialized("table.a").unwrap().unwrap();
333
334 assert_eq!(val, 1);
335 }
336
337 #[cfg(feature = "typed")]
338 #[test]
339 fn test_deser() {
340 use crate::insert::TomlValueInsertExt;
341 use crate::read::TomlValueReadExt;
342 use toml::map::Map;
343
344 #[derive(Serialize, Deserialize, Debug)]
345 struct Test {
346 a: u64,
347 s: String,
348 }
349
350 let mut toml = Value::Table(Map::new());
351 let test = Test {
352 a: 15,
353 s: String::from("Helloworld"),
354 };
355
356 assert!(toml
357 .insert_serialized("table.value", test)
358 .unwrap()
359 .is_none());
360 let _: Test = toml.read_deserialized("table.value").unwrap().unwrap();
361
362 assert!(true);
363 }
364}
365
366#[cfg(all(test, feature = "typed"))]
367mod partial_tests {
368 use super::*;
369
370 use toml::map::Map;
371 use toml::Value;
372
373 #[derive(Debug, Deserialize, Serialize)]
374 struct TestObj {
375 pub value: String,
376 }
377
378 impl<'a> Partial<'a> for TestObj {
379 const LOCATION: &'static str = "foo";
380 type Output = Self;
381 }
382
383 #[test]
384 fn test_compiles() {
385 let tbl = {
386 let mut tbl = Map::new();
387 tbl.insert(String::from("foo"), {
388 let mut tbl = Map::new();
389 tbl.insert(String::from("value"), Value::String(String::from("foobar")));
390 Value::Table(tbl)
391 });
392 Value::Table(tbl)
393 };
394
395 let obj: TestObj = tbl.read_partial::<TestObj>().unwrap().unwrap();
396 assert_eq!(obj.value, "foobar");
397 }
398}