Flint Engine / Guide / API Reference

flint_core/
toml_util.rs

1//! TOML value coercion utilities.
2//!
3//! TOML integers and floats are distinct types — `1` parses as integer, `1.0` as
4//! float. These helpers coerce either form into Rust numeric types so callers
5//! don't have to repeat the `as_float().or_else(|| as_integer().map(...))` dance.
6
7use crate::Vec3;
8
9/// Coerce a TOML integer or float to `f64`.
10pub fn toml_f64(v: &toml::Value) -> Option<f64> {
11    v.as_float().or_else(|| v.as_integer().map(|i| i as f64))
12}
13
14/// Coerce a TOML integer or float to `f32`.
15pub fn toml_f32(v: &toml::Value) -> Option<f32> {
16    toml_f64(v).map(|f| f as f32)
17}
18
19/// Extract `[f32; 3]` from a TOML array of 3+ numbers.
20pub fn toml_vec3(v: &toml::Value) -> Option<[f32; 3]> {
21    let arr = v.as_array()?;
22    if arr.len() >= 3 {
23        Some([toml_f32(&arr[0])?, toml_f32(&arr[1])?, toml_f32(&arr[2])?])
24    } else {
25        None
26    }
27}
28
29/// Extract `[f32; 4]` from a TOML array of 4+ numbers.
30pub fn toml_vec4(v: &toml::Value) -> Option<[f32; 4]> {
31    let arr = v.as_array()?;
32    if arr.len() >= 4 {
33        Some([
34            toml_f32(&arr[0])?,
35            toml_f32(&arr[1])?,
36            toml_f32(&arr[2])?,
37            toml_f32(&arr[3])?,
38        ])
39    } else {
40        None
41    }
42}
43
44/// Extract an RGBA color from a TOML array of 3 (alpha defaults to 1.0) or 4 numbers.
45pub fn toml_color(v: &toml::Value) -> Option<[f32; 4]> {
46    let arr = v.as_array()?;
47    if arr.len() < 3 {
48        return None;
49    }
50    let r = toml_f32(&arr[0])?;
51    let g = toml_f32(&arr[1])?;
52    let b = toml_f32(&arr[2])?;
53    let a = if arr.len() >= 4 {
54        toml_f32(&arr[3]).unwrap_or(1.0)
55    } else {
56        1.0
57    };
58    Some([r, g, b, a])
59}
60
61/// Coerce every element of a `&[Value]` slice to `f32`.
62pub fn toml_f32_slice(arr: &[toml::Value]) -> Option<Vec<f32>> {
63    arr.iter().map(toml_f32).collect()
64}
65
66/// Extract a [`Vec3`] from a TOML value that is either an array `[x, y, z]` or
67/// a table `{ x = .., y = .., z = .. }`.
68pub fn toml_to_vec3(v: &toml::Value) -> Option<Vec3> {
69    if let Some(arr) = v.as_array() {
70        if arr.len() >= 3 {
71            return Some(Vec3::new(
72                toml_f32(&arr[0])?,
73                toml_f32(&arr[1])?,
74                toml_f32(&arr[2])?,
75            ));
76        }
77    }
78    if let Some(table) = v.as_table() {
79        let x = table.get("x").and_then(toml_f32)?;
80        let y = table.get("y").and_then(toml_f32)?;
81        let z = table.get("z").and_then(toml_f32)?;
82        return Some(Vec3::new(x, y, z));
83    }
84    None
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use toml::Value;
91
92    #[test]
93    fn f64_from_float() {
94        let v: Value = 3.14.into();
95        assert_eq!(toml_f64(&v), Some(3.14));
96    }
97
98    #[test]
99    fn f64_from_integer() {
100        let v: Value = 42.into();
101        assert_eq!(toml_f64(&v), Some(42.0));
102    }
103
104    #[test]
105    fn f64_from_string_returns_none() {
106        let v: Value = "hello".into();
107        assert_eq!(toml_f64(&v), None);
108    }
109
110    #[test]
111    fn f32_coercion() {
112        let v: Value = 1.5.into();
113        assert_eq!(toml_f32(&v), Some(1.5f32));
114        let v: Value = 7.into();
115        assert_eq!(toml_f32(&v), Some(7.0f32));
116    }
117
118    #[test]
119    fn vec3_from_array() {
120        let v: Value = toml::toml! { x = [1.0, 2, 3.5] }.get("x").unwrap().clone();
121        assert_eq!(toml_vec3(&v), Some([1.0, 2.0, 3.5]));
122    }
123
124    #[test]
125    fn vec3_too_short() {
126        let v: Value = toml::toml! { x = [1.0, 2.0] }.get("x").unwrap().clone();
127        assert_eq!(toml_vec3(&v), None);
128    }
129
130    #[test]
131    fn vec4_from_array() {
132        let v: Value = toml::toml! { x = [1, 2, 3, 4] }.get("x").unwrap().clone();
133        assert_eq!(toml_vec4(&v), Some([1.0, 2.0, 3.0, 4.0]));
134    }
135
136    #[test]
137    fn color_rgb_defaults_alpha() {
138        let v: Value = toml::toml! { c = [0.5, 0.6, 0.7] }
139            .get("c")
140            .unwrap()
141            .clone();
142        assert_eq!(toml_color(&v), Some([0.5, 0.6, 0.7, 1.0]));
143    }
144
145    #[test]
146    fn color_rgba() {
147        let v: Value = toml::toml! { c = [0.1, 0.2, 0.3, 0.4] }
148            .get("c")
149            .unwrap()
150            .clone();
151        assert_eq!(toml_color(&v), Some([0.1, 0.2, 0.3, 0.4]));
152    }
153
154    #[test]
155    fn f32_slice() {
156        let arr = vec![Value::from(1), Value::from(2.5), Value::from(3)];
157        assert_eq!(toml_f32_slice(&arr), Some(vec![1.0, 2.5, 3.0]));
158    }
159
160    #[test]
161    fn f32_slice_with_bad_element() {
162        let arr = vec![Value::from(1), Value::from("x")];
163        assert_eq!(toml_f32_slice(&arr), None);
164    }
165
166    #[test]
167    fn to_vec3_from_array() {
168        let v: Value = toml::toml! { p = [1, 2.0, 3] }.get("p").unwrap().clone();
169        assert_eq!(toml_to_vec3(&v), Some(Vec3::new(1.0, 2.0, 3.0)));
170    }
171
172    #[test]
173    fn to_vec3_from_table() {
174        let v: Value = toml::toml! {
175            [p]
176            x = 4
177            y = 5.0
178            z = 6
179        }
180        .get("p")
181        .unwrap()
182        .clone();
183        assert_eq!(toml_to_vec3(&v), Some(Vec3::new(4.0, 5.0, 6.0)));
184    }
185
186    #[test]
187    fn to_vec3_invalid() {
188        let v: Value = "not a vec3".into();
189        assert_eq!(toml_to_vec3(&v), None);
190    }
191}