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
8pub fn from_str<T: Facet>(yaml: &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#[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
42fn yaml_type(ty: &Yaml) -> &'static str {
43 match ty {
44 Yaml::Real(_) => "real number",
45 Yaml::Integer(_) => "integer",
46 Yaml::String(_) => "string",
47 Yaml::Boolean(_) => "boolean",
48 Yaml::Array(_) => "array",
49 Yaml::Hash(_) => "hash/map",
50 Yaml::Alias(_) => "alias",
51 Yaml::Null => "null",
52 Yaml::BadValue => "bad value",
53 }
54}
55
56fn yaml_to_u64(ty: &Yaml) -> Result<u64, AnyErr> {
57 match ty {
58 Yaml::Real(r) => r
59 .parse::<u64>()
60 .map_err(|_| AnyErr("Failed to parse real as u64".into())),
61 Yaml::Integer(i) => Ok(*i as u64),
62 Yaml::String(s) => s
63 .parse::<u64>()
64 .map_err(|_| AnyErr("Failed to parse string as u64".into())),
65 Yaml::Boolean(b) => Ok(if *b { 1 } else { 0 }),
66 _ => Err(AnyErr(format!("Cannot convert {} to u64", yaml_type(ty)))),
67 }
68}
69
70fn from_str_value<'a>(wip: Wip<'a>, yaml: &str) -> Result<Wip<'a>, AnyErr> {
71 let docs = YamlLoader::load_from_str(yaml).map_err(|e| e.to_string())?;
72 if docs.len() != 1 {
73 return Err("Expected exactly one YAML document".into());
74 }
75 deserialize_value(wip, &docs[0])
76}
77
78fn deserialize_value<'a>(mut wip: Wip<'a>, value: &Yaml) -> Result<Wip<'a>, AnyErr> {
79 let shape = wip.shape();
80 match shape.def {
81 Def::Scalar(_) => {
82 if shape.is_type::<u64>() {
83 let u = yaml_to_u64(value)?;
84 wip = wip.put(u).map_err(|e| AnyErr(e.to_string()))?;
85 } else if shape.is_type::<String>() {
86 let s = value
87 .as_str()
88 .ok_or_else(|| AnyErr(format!("Expected string, got: {}", yaml_type(value))))?
89 .to_string();
90 wip = wip.put(s).map_err(|e| AnyErr(e.to_string()))?;
91 } else {
92 return Err(AnyErr(format!("Unsupported scalar type: {}", shape)));
93 }
94 }
95 Def::List(_) => todo!(),
96 Def::Map(_) => todo!(),
97 Def::Struct(_) => {
98 if let Yaml::Hash(hash) = value {
99 for (k, v) in hash {
100 let k = k.as_str().ok_or_else(|| {
101 AnyErr(format!("Expected string key, got: {}", yaml_type(k)))
102 })?;
103 let field_index = wip
104 .field_index(k)
105 .ok_or_else(|| AnyErr(format!("Field '{}' not found", k)))?;
106 wip = wip
107 .field(field_index)
108 .map_err(|e| AnyErr(format!("Field '{}' error: {}", k, e)))?;
109 wip = deserialize_value(wip, v)?;
110 wip = wip.pop().map_err(|e| AnyErr(e.to_string()))?;
111 }
112 } else {
113 return Err(AnyErr(format!("Expected a YAML hash, got: {:?}", value)));
114 }
115 }
116 Def::Enum(_) => todo!(),
117 _ => return Err(AnyErr(format!("Unsupported type: {:?}", shape))),
118 }
119 Ok(wip)
120}