use crate::value_objects::{DisplayHint, KeyMapping, Value, WidgetState}; use std::collections::BTreeMap; pub type WidgetId = u16; pub type DataSourceId = u16; const DEFAULT_MAX_DATA_SIZE: u16 = 2048; #[derive(Debug, Clone)] pub struct WidgetConfig { pub id: WidgetId, pub name: String, pub display_hint: DisplayHint, pub data_source_id: DataSourceId, pub mappings: Vec, pub max_data_size: u16, } impl WidgetConfig { pub fn new( id: WidgetId, name: String, display_hint: DisplayHint, data_source_id: DataSourceId, mappings: Vec, ) -> Self { Self { id, name, display_hint, data_source_id, mappings, max_data_size: DEFAULT_MAX_DATA_SIZE, } } pub fn extract(&self, raw: &Value) -> WidgetState { let has_mappings = self.mappings.iter().any(|m| !m.source_path.is_empty()); if !has_mappings { let data = match raw { Value::Object(map) => map.clone(), _ => BTreeMap::new(), }; return WidgetState { data, error: None }; } let budget = self.max_data_size as usize; let mut used = 0usize; let mut data = BTreeMap::new(); for mapping in &self.mappings { if let Some((key, value)) = mapping.extract(raw) { let key_cost = key.len(); let remaining = budget.saturating_sub(used + key_cost); let value = Self::truncate_value(value, remaining); used += key_cost + value.estimated_size(); data.insert(key, value); if used >= budget { break; } } } WidgetState { data, error: None } } fn truncate_value(value: Value, max_bytes: usize) -> Value { match value { Value::String(s) if s.len() > max_bytes => { let truncated: String = s .char_indices() .take_while(|(i, _)| *i < max_bytes) .map(|(_, c)| c) .collect(); Value::String(truncated) } other => other, } } }