use serde_json::Value;
#[derive(Debug, Clone)]
pub struct ComponentModel {
pub id: String,
pub component_type: String,
pub properties: serde_json::Map<String, Value>,
}
impl ComponentModel {
pub fn from_json(value: &Value) -> Result<Self, crate::error::A2uiError> {
let obj = value
.as_object()
.ok_or_else(|| crate::error::A2uiError::Validation("component must be an object".into()))?;
let id = obj
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::error::A2uiError::Validation("component missing 'id'".into()))?
.to_string();
let component_type = obj
.get("component")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::error::A2uiError::Validation(format!("component '{}' missing 'component' type", id)))?
.to_string();
let properties: serde_json::Map<String, Value> = obj
.iter()
.filter(|(k, _)| *k != "id" && *k != "component")
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
Ok(Self {
id,
component_type,
properties,
})
}
pub fn get_property<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
self.properties.get(key).and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn get_raw(&self, key: &str) -> Option<&Value> {
self.properties.get(key)
}
pub fn children(&self) -> Option<crate::protocol::common_types::ChildList> {
self.properties
.get("children")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn child(&self) -> Option<String> {
self.properties.get("child").and_then(|v| v.as_str()).map(|s| s.to_string())
}
pub fn action(&self) -> Option<crate::protocol::common_types::Action> {
self.properties
.get("action")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn weight(&self) -> Option<f64> {
self.properties.get("weight").and_then(|v| v.as_f64())
}
pub fn min_height(&self) -> Option<u16> {
self.properties
.get("minHeight")
.and_then(|v| v.as_f64())
.map(|f| f.round().max(0.0) as u16)
}
pub fn checks(&self) -> Option<Vec<crate::protocol::common_types::CheckRule>> {
self.properties
.get("checks")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn accessibility(&self) -> Option<crate::protocol::common_types::AccessibilityAttributes> {
self.properties
.get("accessibility")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_from_json() {
let raw = json!({
"id": "my_button",
"component": "Button",
"variant": "primary",
"child": "button_label"
});
let model = ComponentModel::from_json(&raw).unwrap();
assert_eq!(model.id, "my_button");
assert_eq!(model.component_type, "Button");
assert_eq!(model.child(), Some("button_label".to_string()));
}
#[test]
fn test_children_static() {
let raw = json!({
"id": "root",
"component": "Column",
"children": ["a", "b", "c"]
});
let model = ComponentModel::from_json(&raw).unwrap();
let children = model.children().unwrap();
match children {
crate::protocol::common_types::ChildList::Static(ids) => {
assert_eq!(ids, vec!["a", "b", "c"]);
}
_ => panic!("expected static child list"),
}
}
}