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<'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#[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}