a2ui_base/model/
component_model.rs1use serde_json::Value;
4
5#[derive(Debug, Clone)]
7pub struct ComponentModel {
8 pub id: String,
10 pub component_type: String,
12 pub properties: serde_json::Map<String, Value>,
14}
15
16impl ComponentModel {
17 pub fn from_json(value: &Value) -> Result<Self, crate::error::A2uiError> {
20 let obj = value
21 .as_object()
22 .ok_or_else(|| crate::error::A2uiError::Validation("component must be an object".into()))?;
23
24 let id = obj
25 .get("id")
26 .and_then(|v| v.as_str())
27 .ok_or_else(|| crate::error::A2uiError::Validation("component missing 'id'".into()))?
28 .to_string();
29
30 let component_type = obj
31 .get("component")
32 .and_then(|v| v.as_str())
33 .ok_or_else(|| crate::error::A2uiError::Validation(format!("component '{}' missing 'component' type", id)))?
34 .to_string();
35
36 let properties: serde_json::Map<String, Value> = obj
38 .iter()
39 .filter(|(k, _)| *k != "id" && *k != "component")
40 .map(|(k, v)| (k.clone(), v.clone()))
41 .collect();
42
43 Ok(Self {
44 id,
45 component_type,
46 properties,
47 })
48 }
49
50 pub fn get_property<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
52 self.properties.get(key).and_then(|v| serde_json::from_value(v.clone()).ok())
53 }
54
55 pub fn get_raw(&self, key: &str) -> Option<&Value> {
57 self.properties.get(key)
58 }
59
60 pub fn children(&self) -> Option<crate::protocol::common_types::ChildList> {
62 self.properties
63 .get("children")
64 .and_then(|v| serde_json::from_value(v.clone()).ok())
65 }
66
67 pub fn child(&self) -> Option<String> {
69 self.properties.get("child").and_then(|v| v.as_str()).map(|s| s.to_string())
70 }
71
72 pub fn action(&self) -> Option<crate::protocol::common_types::Action> {
74 self.properties
75 .get("action")
76 .and_then(|v| serde_json::from_value(v.clone()).ok())
77 }
78
79 pub fn weight(&self) -> Option<f64> {
81 self.properties.get("weight").and_then(|v| v.as_f64())
82 }
83
84 pub fn min_height(&self) -> Option<u16> {
90 self.properties
91 .get("minHeight")
92 .and_then(|v| v.as_f64())
93 .map(|f| f.round().max(0.0) as u16)
94 }
95
96 pub fn checks(&self) -> Option<Vec<crate::protocol::common_types::CheckRule>> {
98 self.properties
99 .get("checks")
100 .and_then(|v| serde_json::from_value(v.clone()).ok())
101 }
102
103 pub fn accessibility(&self) -> Option<crate::protocol::common_types::AccessibilityAttributes> {
105 self.properties
106 .get("accessibility")
107 .and_then(|v| serde_json::from_value(v.clone()).ok())
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use serde_json::json;
115
116 #[test]
117 fn test_from_json() {
118 let raw = json!({
119 "id": "my_button",
120 "component": "Button",
121 "variant": "primary",
122 "child": "button_label"
123 });
124 let model = ComponentModel::from_json(&raw).unwrap();
125 assert_eq!(model.id, "my_button");
126 assert_eq!(model.component_type, "Button");
127 assert_eq!(model.child(), Some("button_label".to_string()));
128 }
129
130 #[test]
131 fn test_children_static() {
132 let raw = json!({
133 "id": "root",
134 "component": "Column",
135 "children": ["a", "b", "c"]
136 });
137 let model = ComponentModel::from_json(&raw).unwrap();
138 let children = model.children().unwrap();
139 match children {
140 crate::protocol::common_types::ChildList::Static(ids) => {
141 assert_eq!(ids, vec!["a", "b", "c"]);
142 }
143 _ => panic!("expected static child list"),
144 }
145 }
146}