facet_toml/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4pub mod error;
5mod to_scalar;
6
7use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8
9use error::AnyErr;
10use facet_core::{Def, Facet, StructKind};
11use facet_reflect::{ScalarType, Wip};
12use log::trace;
13use toml_edit::{DocumentMut, Item, TomlError};
14use yansi::Paint as _;
15
16/// Deserializes a TOML string into a value of type `T` that implements `Facet`.
17pub fn from_str<T: Facet>(toml: &str) -> Result<T, AnyErr> {
18    trace!("Starting deserialization");
19
20    let wip = Wip::alloc::<T>();
21
22    let docs: DocumentMut = toml.parse().map_err(|e| TomlError::to_string(&e))?;
23    let wip = deserialize_item(wip, docs.as_item())?;
24
25    let heap_value = wip.build().map_err(|e| AnyErr(e.to_string()))?;
26    let result = heap_value
27        .materialize::<T>()
28        .map_err(|e| AnyErr(e.to_string()))?;
29
30    trace!("Finished deserialization");
31
32    Ok(result)
33}
34
35fn deserialize_item<'a>(wip: Wip<'a>, item: &Item) -> Result<Wip<'a>, AnyErr> {
36    trace!("Deserializing {}", item.type_name().blue());
37
38    match wip.shape().def {
39        Def::Scalar(_) => deserialize_as_scalar(wip, item),
40        Def::List(_) => deserialize_as_list(wip, item),
41        Def::Map(_) => deserialize_as_map(wip, item),
42        Def::Struct(_) => deserialize_as_struct(wip, item),
43        Def::Enum(_) => deserialize_as_enum(wip, item),
44        Def::Option(_) => deserialize_as_option(wip, item),
45        Def::SmartPointer(_) => deserialize_as_smartpointer(wip, item),
46        _ => Err(AnyErr(format!("Unsupported type: {:?}", wip.shape()))),
47    }
48}
49
50fn deserialize_as_struct<'a>(mut wip: Wip<'a>, item: &Item) -> Result<Wip<'a>, AnyErr> {
51    trace!("Deserializing {}", "struct".blue());
52
53    // Parse as a the inner struct type if item is a single value and the struct is a unit struct
54    if item.is_value() && !item.is_inline_table() {
55        // Only allow unit structs
56        let shape = wip.shape();
57        if let Def::Struct(def) = shape.def {
58            if def.fields.len() > 1 {
59                return Err(AnyErr(
60                    "Failed trying to parse a single value as a struct with multiple fields".into(),
61                ));
62            }
63        }
64
65        let field_index = 0;
66        wip = wip
67            .field(field_index)
68            .map_err(|e| AnyErr(format!("Unit struct is missing value: {}", e)))?;
69        wip = deserialize_item(wip, item)?;
70        wip = wip.pop().map_err(|e| AnyErr(e.to_string()))?;
71        return Ok(wip);
72    }
73
74    // Otherwise we expect a table
75    let table = item.as_table_like().ok_or_else(|| {
76        AnyErr(format!(
77            "Expected table like structure, got {}",
78            item.type_name()
79        ))
80    })?;
81
82    for (k, v) in table.iter() {
83        let field_index = wip
84            .field_index(k)
85            .ok_or_else(|| AnyErr(format!("Field '{}' not found", k)))?;
86        wip = wip
87            .field(field_index)
88            .map_err(|e| AnyErr(format!("Field '{}' error: {}", k, e)))?;
89        wip = deserialize_item(wip, v)
90            .map_err(|e| AnyErr(format!("Error deserializing field '{}': {}", k, e)))?;
91        wip = wip.pop().map_err(|e| AnyErr(e.to_string()))?;
92    }
93
94    trace!("Finished deserializing {}", "struct".blue());
95
96    Ok(wip)
97}
98
99fn deserialize_as_enum<'a>(wip: Wip<'a>, item: &Item) -> Result<Wip<'a>, AnyErr> {
100    trace!("Deserializing {}", "enum".blue());
101
102    let wip = match item {
103        Item::None => todo!(),
104
105        Item::Value(value) => {
106            trace!("Entering {}", "value".cyan());
107
108            // A value can be an inline table, so parse it as such
109            if let Some(inline_table) = value.as_inline_table() {
110                if let Some((key, field)) = inline_table.iter().next() {
111                    trace!(
112                        "Entering {} with key {}",
113                        "inline table".cyan(),
114                        key.cyan().bold()
115                    );
116
117                    if inline_table.len() > 1 {
118                        return Err(AnyErr(
119                            "Cannot parse enum from inline table because it got multiple fields"
120                                .to_string(),
121                        ));
122                    } else {
123                        return build_enum_from_variant_name(
124                            wip,
125                            key,
126                            // TODO: remove clone
127                            &Item::Value(field.clone()),
128                        );
129                    }
130                } else {
131                    return Err(AnyErr(
132                        "Inline table doesn't have any fields to parse into enum variant"
133                            .to_string(),
134                    ));
135                }
136            }
137
138            let variant_name = value
139                .as_str()
140                .ok_or_else(|| format!("Expected string, got: {}", value.type_name()))?;
141
142            build_enum_from_variant_name(wip, variant_name, item)?
143        }
144
145        Item::Table(table) => {
146            if let Some((key, field)) = table.iter().next() {
147                trace!("Entering {} with key {}", "table".cyan(), key.cyan().bold());
148
149                if table.len() > 1 {
150                    return Err(AnyErr(
151                        "Cannot parse enum from inline table because it got multiple fields"
152                            .to_string(),
153                    ));
154                } else {
155                    build_enum_from_variant_name(wip, key, field)?
156                }
157            } else {
158                return Err(AnyErr(
159                    "Inline table doesn't have any fields to parse into enum variant".to_string(),
160                ));
161            }
162        }
163
164        Item::ArrayOfTables(_array_of_tables) => todo!(),
165    };
166
167    trace!("Finished deserializing {}", "enum".blue());
168
169    Ok(wip)
170}
171
172fn build_enum_from_variant_name<'a>(
173    mut wip: Wip<'a>,
174    variant_name: &str,
175    item: &Item,
176) -> Result<Wip<'a>, AnyErr> {
177    // Select the variant
178    wip = wip
179        .variant_named(variant_name)
180        .map_err(|e| AnyErr(e.to_string()))?;
181    // Safe to unwrap because the variant got just selected
182    let variant = wip.selected_variant().unwrap();
183
184    if variant.data.kind == StructKind::Unit {
185        // No need to do anything, we can just set the variant since it's a unit enum
186        return Ok(wip);
187    }
188
189    // Push all fields
190    for (index, field) in variant.data.fields.iter().enumerate() {
191        wip = wip
192            .field_named(field.name)
193            .map_err(|e| format!("Field by name on enum does not exist: {e}"))?;
194
195        // Try to get the TOML value as a table to extract the field
196        if let Some(item) = item.as_table_like() {
197            // Base the field name on what type of struct we are
198            let field_name = if let StructKind::TupleStruct | StructKind::Tuple = variant.data.kind
199            {
200                &index.to_string()
201            } else {
202                // It must be a struct field
203                field.name
204            };
205
206            // Try to get the TOML field matching the Rust name
207            let Some(field) = item.get(field_name) else {
208                return Err(format!("TOML field '{}' not found", field_name).into());
209            };
210
211            wip = deserialize_item(wip, field)?;
212
213            wip = wip.pop().map_err(|e| AnyErr(e.to_string()))?;
214        } else if item.is_value() {
215            wip = deserialize_item(wip, item)?;
216        } else {
217            return Err(format!("TOML {} is not a recognized type", item.type_name()).into());
218        }
219    }
220
221    Ok(wip)
222}
223
224fn deserialize_as_list<'a>(mut wip: Wip<'a>, item: &Item) -> Result<Wip<'a>, AnyErr> {
225    trace!("Deserializing {}", "list".blue());
226
227    // Get the TOML item as an array
228    let Some(item) = item.as_array() else {
229        return Err(AnyErr(format!(
230            "Item is not an array but a {}",
231            item.type_name()
232        )));
233    };
234
235    if item.is_empty() {
236        // Only put an empty list
237        return wip
238            .put_empty_list()
239            .map_err(|e| AnyErr(format!("Can not put empty list: {e}")));
240    }
241
242    // Start the list
243    wip = wip
244        .begin_pushback()
245        .map_err(|e| format!("Can not start filling list: {e}"))?;
246
247    // Loop over all items in the TOML list
248    for value in item.iter() {
249        // Start the field
250        wip = wip
251            .push()
252            .map_err(|e| format!("Can not start field: {e}"))?;
253
254        wip = deserialize_item(
255            wip,
256            // TODO: remove clone
257            &Item::Value(value.clone()),
258        )?;
259
260        // Finish the field
261        wip = wip
262            .pop()
263            .map_err(|e| format!("Can not finish field: {e}"))?;
264    }
265
266    trace!("Finished deserializing {}", "list".blue());
267
268    Ok(wip)
269}
270
271fn deserialize_as_map<'a>(mut wip: Wip<'a>, item: &Item) -> Result<Wip<'a>, AnyErr> {
272    trace!("Deserializing {}", "map".blue());
273
274    // We expect a table to fill a map
275    let table = item.as_table_like().ok_or_else(|| {
276        AnyErr(format!(
277            "Expected table like structure, got {}",
278            item.type_name()
279        ))
280    })?;
281
282    if table.is_empty() {
283        // Only put an empty map
284        return wip
285            .put_empty_map()
286            .map_err(|e| AnyErr(format!("Can not put empty map: {e}")));
287    }
288
289    // Start the map
290    wip = wip
291        .begin_map_insert()
292        .map_err(|e| format!("Can not start filling map: {e}"))?;
293
294    // Loop over all items in the TOML list
295    for (k, v) in table.iter() {
296        // Start the key
297        wip = wip
298            .push_map_key()
299            .map_err(|e| format!("Can not start field: {e}"))?;
300
301        trace!("Push {} {}", "key".cyan(), k.cyan().bold());
302
303        // Deserialize the key
304        match ScalarType::try_from_shape(wip.shape())
305            .ok_or_else(|| format!("Unsupported scalar type: {}", wip.shape()))?
306        {
307            #[cfg(feature = "std")]
308            ScalarType::String => {
309                wip = wip.put(k.to_string()).map_err(|e| AnyErr(e.to_string()))?
310            }
311            #[cfg(feature = "std")]
312            ScalarType::CowStr => {
313                wip = wip
314                    .put(std::borrow::Cow::Owned(k.to_string()))
315                    .map_err(|e| AnyErr(e.to_string()))?
316            }
317            _ => {
318                return Err(AnyErr(format!(
319                    "Can not convert {} to map key",
320                    wip.shape()
321                )));
322            }
323        };
324
325        trace!("Push {}", "value".cyan());
326
327        // Start the value
328        wip = wip
329            .push_map_value()
330            .map_err(|e| format!("Can not start value: {e}"))?;
331
332        // Deserialize the value
333        wip = deserialize_item(wip, v)?;
334
335        // Finish the key
336        wip = wip
337            .pop()
338            .map_err(|e| format!("Can not finish value: {e}"))?;
339    }
340
341    trace!("Finished deserializing {}", "map".blue());
342
343    Ok(wip)
344}
345
346fn deserialize_as_option<'a>(mut _wip: Wip<'a>, _item: &Item) -> Result<Wip<'a>, AnyErr> {
347    trace!("Deserializing {}", "option".blue());
348
349    trace!("Finished deserializing {}", "option".blue());
350
351    todo!();
352}
353
354fn deserialize_as_smartpointer<'a>(mut _wip: Wip<'a>, _item: &Item) -> Result<Wip<'a>, AnyErr> {
355    trace!("Deserializing {}", "smart pointer".blue());
356
357    trace!("Finished deserializing {}", "smart pointer".blue());
358
359    todo!();
360}
361
362fn deserialize_as_scalar<'a>(mut wip: Wip<'a>, item: &Item) -> Result<Wip<'a>, AnyErr> {
363    trace!("Deserializing {}", "scalar".blue());
364
365    match ScalarType::try_from_shape(wip.shape())
366        .ok_or_else(|| format!("Unsupported scalar type: {}", wip.shape()))?
367    {
368        ScalarType::Bool => {
369            wip = wip
370                .put(to_scalar::boolean(item)?)
371                .map_err(|e| AnyErr(e.to_string()))?
372        }
373        #[cfg(feature = "std")]
374        ScalarType::String => {
375            wip = wip
376                .put(to_scalar::string(item)?)
377                .map_err(|e| AnyErr(e.to_string()))?
378        }
379        #[cfg(feature = "std")]
380        ScalarType::CowStr => {
381            wip = wip
382                .put(std::borrow::Cow::Owned(to_scalar::string(item)?))
383                .map_err(|e| AnyErr(e.to_string()))?
384        }
385        ScalarType::F32 => {
386            wip = wip
387                .put(to_scalar::number::<f32>(item)?)
388                .map_err(|e| AnyErr(e.to_string()))?
389        }
390        ScalarType::F64 => {
391            wip = wip
392                .put(to_scalar::number::<f64>(item)?)
393                .map_err(|e| AnyErr(e.to_string()))?
394        }
395        ScalarType::U8 => {
396            wip = wip
397                .put(to_scalar::number::<u8>(item)?)
398                .map_err(|e| AnyErr(e.to_string()))?
399        }
400        ScalarType::U16 => {
401            wip = wip
402                .put(to_scalar::number::<u16>(item)?)
403                .map_err(|e| AnyErr(e.to_string()))?
404        }
405        ScalarType::U32 => {
406            wip = wip
407                .put(to_scalar::number::<u32>(item)?)
408                .map_err(|e| AnyErr(e.to_string()))?
409        }
410        ScalarType::U64 => {
411            wip = wip
412                .put(to_scalar::number::<u64>(item)?)
413                .map_err(|e| AnyErr(e.to_string()))?
414        }
415        ScalarType::USize => {
416            wip = wip
417                .put(to_scalar::number::<usize>(item)?)
418                .map_err(|e| AnyErr(e.to_string()))?
419        }
420        ScalarType::I8 => {
421            wip = wip
422                .put(to_scalar::number::<i8>(item)?)
423                .map_err(|e| AnyErr(e.to_string()))?
424        }
425        ScalarType::I16 => {
426            wip = wip
427                .put(to_scalar::number::<i16>(item)?)
428                .map_err(|e| AnyErr(e.to_string()))?
429        }
430        ScalarType::I32 => {
431            wip = wip
432                .put(to_scalar::number::<i32>(item)?)
433                .map_err(|e| AnyErr(e.to_string()))?
434        }
435        ScalarType::I64 => {
436            wip = wip
437                .put(to_scalar::number::<i64>(item)?)
438                .map_err(|e| AnyErr(e.to_string()))?
439        }
440        ScalarType::ISize => {
441            wip = wip
442                .put(to_scalar::number::<isize>(item)?)
443                .map_err(|e| AnyErr(e.to_string()))?
444        }
445        #[cfg(feature = "std")]
446        ScalarType::SocketAddr => {
447            wip = wip
448                .put(to_scalar::from_str::<std::net::SocketAddr>(
449                    item,
450                    "socket address",
451                )?)
452                .map_err(|e| AnyErr(e.to_string()))?
453        }
454        ScalarType::IpAddr => {
455            wip = wip
456                .put(to_scalar::from_str::<IpAddr>(item, "ip address")?)
457                .map_err(|e| AnyErr(e.to_string()))?
458        }
459        ScalarType::Ipv4Addr => {
460            wip = wip
461                .put(to_scalar::from_str::<Ipv4Addr>(item, "ipv4 address")?)
462                .map_err(|e| AnyErr(e.to_string()))?
463        }
464        ScalarType::Ipv6Addr => {
465            wip = wip
466                .put(to_scalar::from_str::<Ipv6Addr>(item, "ipv6 address")?)
467                .map_err(|e| AnyErr(e.to_string()))?
468        }
469        _ => return Err(AnyErr(format!("Unsupported scalar type: {}", wip.shape()))),
470    }
471
472    trace!("Finished deserializing {}", "scalar".blue());
473
474    Ok(wip)
475}