facet_yaml/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use facet_core::{Def, Facet};
5use facet_reflect::Wip;
6use yaml_rust2::{Yaml, YamlLoader};
7
8/// Deserializes a YAML string into a value of type `T` that implements `Facet`.
9pub fn from_str<'input: 'facet, 'facet, T: Facet<'facet>>(yaml: &'input str) -> Result<T, AnyErr> {
10    let wip = Wip::alloc::<T>()?;
11    let wip = from_str_value(wip, yaml)?;
12    let heap_value = wip.build().map_err(|e| AnyErr(e.to_string()))?;
13    heap_value
14        .materialize::<T>()
15        .map_err(|e| AnyErr(e.to_string()))
16}
17
18/// Any error
19#[derive(Debug, Clone)]
20pub struct AnyErr(String);
21
22impl core::fmt::Display for AnyErr {
23    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28impl std::error::Error for AnyErr {}
29
30impl From<String> for AnyErr {
31    fn from(s: String) -> Self {
32        Self(s)
33    }
34}
35
36impl From<&str> for AnyErr {
37    fn from(s: &str) -> Self {
38        Self(s.to_string())
39    }
40}
41
42impl From<facet_reflect::ReflectError> for AnyErr {
43    fn from(value: facet_reflect::ReflectError) -> Self {
44        Self(format!("Reflection error: {value}"))
45    }
46}
47
48fn yaml_type(ty: &Yaml) -> &'static str {
49    match ty {
50        Yaml::Real(_) => "real number",
51        Yaml::Integer(_) => "integer",
52        Yaml::String(_) => "string",
53        Yaml::Boolean(_) => "boolean",
54        Yaml::Array(_) => "array",
55        Yaml::Hash(_) => "hash/map",
56        Yaml::Alias(_) => "alias",
57        Yaml::Null => "null",
58        Yaml::BadValue => "bad value",
59    }
60}
61
62fn yaml_to_u64(ty: &Yaml) -> Result<u64, AnyErr> {
63    match ty {
64        Yaml::Real(r) => r
65            .parse::<u64>()
66            .map_err(|_| AnyErr("Failed to parse real as u64".into())),
67        Yaml::Integer(i) => Ok(*i as u64),
68        Yaml::String(s) => s
69            .parse::<u64>()
70            .map_err(|_| AnyErr("Failed to parse string as u64".into())),
71        Yaml::Boolean(b) => Ok(if *b { 1 } else { 0 }),
72        _ => Err(AnyErr(format!("Cannot convert {} to u64", yaml_type(ty)))),
73    }
74}
75
76fn from_str_value<'a>(wip: Wip<'a>, yaml: &str) -> Result<Wip<'a>, AnyErr> {
77    let docs = YamlLoader::load_from_str(yaml).map_err(|e| e.to_string())?;
78    if docs.len() != 1 {
79        return Err("Expected exactly one YAML document".into());
80    }
81    deserialize_value(wip, &docs[0])
82}
83
84fn deserialize_value<'a>(mut wip: Wip<'a>, value: &Yaml) -> Result<Wip<'a>, AnyErr> {
85    let shape = wip.shape();
86    match shape.def {
87        Def::Scalar(_) => {
88            if shape.is_type::<u64>() {
89                let u = yaml_to_u64(value)?;
90                wip = wip.put(u).map_err(|e| AnyErr(e.to_string()))?;
91            } else if shape.is_type::<String>() {
92                let s = value
93                    .as_str()
94                    .ok_or_else(|| AnyErr(format!("Expected string, got: {}", yaml_type(value))))?
95                    .to_string();
96                wip = wip.put(s).map_err(|e| AnyErr(e.to_string()))?;
97            } else {
98                return Err(AnyErr(format!("Unsupported scalar type: {}", shape)));
99            }
100        }
101        Def::List(_) => todo!(),
102        Def::Map(_) => todo!(),
103        Def::Struct(_) => {
104            if let Yaml::Hash(hash) = value {
105                for (k, v) in hash {
106                    let k = k.as_str().ok_or_else(|| {
107                        AnyErr(format!("Expected string key, got: {}", yaml_type(k)))
108                    })?;
109                    let field_index = wip
110                        .field_index(k)
111                        .ok_or_else(|| AnyErr(format!("Field '{}' not found", k)))?;
112                    wip = wip
113                        .field(field_index)
114                        .map_err(|e| AnyErr(format!("Field '{}' error: {}", k, e)))?;
115                    wip = deserialize_value(wip, v)?;
116                    wip = wip.pop().map_err(|e| AnyErr(e.to_string()))?;
117                }
118            } else {
119                return Err(AnyErr(format!("Expected a YAML hash, got: {:?}", value)));
120            }
121        }
122        Def::Enum(_) => todo!(),
123        _ => return Err(AnyErr(format!("Unsupported type: {:?}", shape))),
124    }
125    Ok(wip)
126}