concinnity_cook/authoring/spec/
mod.rs1pub mod asset;
11
12mod json;
13
14pub use json::{arg_value_to_json, spec_args, spec_to_value};
15
16#[derive(Clone, Debug, PartialEq)]
21pub enum ArgValue {
22 Null,
24 Bool(bool),
26 Int(i64),
28 Float(f64),
30 Str(String),
32 Array(Vec<ArgValue>),
34 Object(Vec<(String, ArgValue)>),
36}
37
38impl ArgValue {
39 pub(crate) fn floats(vals: &[f32]) -> ArgValue {
41 ArgValue::Array(vals.iter().map(|&v| ArgValue::Float(v as f64)).collect())
42 }
43}
44
45impl From<bool> for ArgValue {
46 fn from(v: bool) -> Self {
47 ArgValue::Bool(v)
48 }
49}
50impl From<i64> for ArgValue {
51 fn from(v: i64) -> Self {
52 ArgValue::Int(v)
53 }
54}
55impl From<u32> for ArgValue {
56 fn from(v: u32) -> Self {
57 ArgValue::Int(v as i64)
58 }
59}
60impl From<usize> for ArgValue {
61 fn from(v: usize) -> Self {
62 ArgValue::Int(v as i64)
63 }
64}
65impl From<f32> for ArgValue {
66 fn from(v: f32) -> Self {
67 ArgValue::Float(v as f64)
68 }
69}
70impl From<f64> for ArgValue {
71 fn from(v: f64) -> Self {
72 ArgValue::Float(v)
73 }
74}
75impl From<&str> for ArgValue {
76 fn from(v: &str) -> Self {
77 ArgValue::Str(String::from(v))
78 }
79}
80impl From<String> for ArgValue {
81 fn from(v: String) -> Self {
82 ArgValue::Str(v)
83 }
84}
85impl<const N: usize> From<[f32; N]> for ArgValue {
86 fn from(v: [f32; N]) -> Self {
87 ArgValue::floats(&v)
88 }
89}
90
91#[derive(Clone, Debug, PartialEq)]
93pub struct AssetSpec {
94 pub name: String,
97 pub asset_type: &'static str,
99 pub fields: Vec<(String, ArgValue)>,
101}
102
103impl AssetSpec {
104 pub(crate) fn new(name: impl Into<String>, asset_type: &'static str) -> Self {
107 AssetSpec {
108 name: name.into(),
109 asset_type,
110 fields: Vec::new(),
111 }
112 }
113
114 pub fn set(mut self, key: impl Into<String>, value: impl Into<ArgValue>) -> Self {
117 self.fields.push((key.into(), value.into()));
118 self
119 }
120
121 #[cfg(test)]
123 pub(crate) fn args(&self) -> ArgValue {
124 ArgValue::Object(self.fields.clone())
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn set_records_fields_in_order() {
134 let spec = AssetSpec::new("lamp", "PointLight")
135 .set("intensity", 8.0f32)
136 .set("range", 20.0f32);
137 assert_eq!(spec.name, "lamp");
138 assert_eq!(spec.asset_type, "PointLight");
139 assert_eq!(
140 spec.fields
141 .iter()
142 .map(|(k, _)| k.as_str())
143 .collect::<Vec<_>>(),
144 vec!["intensity", "range"]
145 );
146 }
147
148 #[test]
149 fn conversions_pick_the_expected_variant() {
150 assert_eq!(ArgValue::from(true), ArgValue::Bool(true));
151 assert_eq!(ArgValue::from(48u32), ArgValue::Int(48));
152 assert_eq!(ArgValue::from(1.5f32), ArgValue::Float(1.5));
153 assert_eq!(ArgValue::from("sky"), ArgValue::Str("sky".to_string()));
154 assert_eq!(
155 ArgValue::from([1.0f32, 0.0, 0.0]),
156 ArgValue::Array(vec![
157 ArgValue::Float(1.0),
158 ArgValue::Float(0.0),
159 ArgValue::Float(0.0),
160 ])
161 );
162 }
163
164 #[test]
165 fn args_wraps_fields_as_an_object() {
166 let spec = AssetSpec::new("s", "Sprite").set("visible", false);
167 assert_eq!(
168 spec.args(),
169 ArgValue::Object(vec![("visible".to_string(), ArgValue::Bool(false))])
170 );
171 }
172}