unity_asset_core/
dynamic_access.rs

1//! Dynamic property access for Unity classes
2//!
3//! This module provides Python-like dynamic property access for Unity objects,
4//! similar to the reference library's behavior.
5
6use crate::{Result, UnityAssetError, UnityValue};
7use std::collections::HashMap;
8
9/// Trait for dynamic property access
10pub trait DynamicAccess {
11    /// Get a property value with automatic type conversion
12    fn get_dynamic(&self, key: &str) -> Option<DynamicValue>;
13
14    /// Set a property value with automatic type conversion
15    fn set_dynamic(&mut self, key: &str, value: DynamicValue) -> Result<()>;
16
17    /// Check if a property exists
18    fn has_dynamic(&self, key: &str) -> bool;
19
20    /// Get all property names
21    fn keys_dynamic(&self) -> Vec<String>;
22}
23
24/// Dynamic value wrapper that supports Python-like operations
25#[derive(Debug, Clone, PartialEq)]
26pub enum DynamicValue {
27    /// String value
28    String(String),
29    /// Integer value
30    Integer(i64),
31    /// Float value
32    Float(f64),
33    /// Boolean value
34    Bool(bool),
35    /// Array value
36    Array(Vec<DynamicValue>),
37    /// Object value
38    Object(HashMap<String, DynamicValue>),
39    /// Null value
40    Null,
41}
42
43impl DynamicValue {
44    /// Convert from UnityValue
45    pub fn from_unity_value(value: &UnityValue) -> Self {
46        match value {
47            UnityValue::String(s) => DynamicValue::String(s.clone()),
48            UnityValue::Integer(i) => DynamicValue::Integer(*i),
49            UnityValue::Float(f) => DynamicValue::Float(*f),
50            UnityValue::Bool(b) => DynamicValue::Bool(*b),
51            UnityValue::Array(arr) => {
52                let converted: Vec<DynamicValue> =
53                    arr.iter().map(DynamicValue::from_unity_value).collect();
54                DynamicValue::Array(converted)
55            }
56            UnityValue::Object(obj) => {
57                let converted: HashMap<String, DynamicValue> = obj
58                    .iter()
59                    .map(|(k, v)| (k.clone(), DynamicValue::from_unity_value(v)))
60                    .collect();
61                DynamicValue::Object(converted)
62            }
63            UnityValue::Null => DynamicValue::Null,
64        }
65    }
66
67    /// Convert to UnityValue
68    pub fn to_unity_value(&self) -> UnityValue {
69        match self {
70            DynamicValue::String(s) => UnityValue::String(s.clone()),
71            DynamicValue::Integer(i) => UnityValue::Integer(*i),
72            DynamicValue::Float(f) => UnityValue::Float(*f),
73            DynamicValue::Bool(b) => UnityValue::Bool(*b),
74            DynamicValue::Array(arr) => {
75                let converted: Vec<UnityValue> =
76                    arr.iter().map(DynamicValue::to_unity_value).collect();
77                UnityValue::Array(converted)
78            }
79            DynamicValue::Object(obj) => {
80                let converted: indexmap::IndexMap<String, UnityValue> = obj
81                    .iter()
82                    .map(|(k, v)| (k.clone(), v.to_unity_value()))
83                    .collect();
84                UnityValue::Object(converted)
85            }
86            DynamicValue::Null => UnityValue::Null,
87        }
88    }
89
90    /// Get as string
91    pub fn as_string(&self) -> Option<&str> {
92        match self {
93            DynamicValue::String(s) => Some(s),
94            _ => None,
95        }
96    }
97
98    /// Get as integer
99    pub fn as_integer(&self) -> Option<i64> {
100        match self {
101            DynamicValue::Integer(i) => Some(*i),
102            DynamicValue::Float(f) => Some(*f as i64),
103            DynamicValue::Bool(b) => Some(if *b { 1 } else { 0 }),
104            _ => None,
105        }
106    }
107
108    /// Get as float
109    pub fn as_float(&self) -> Option<f64> {
110        match self {
111            DynamicValue::Float(f) => Some(*f),
112            DynamicValue::Integer(i) => Some(*i as f64),
113            _ => None,
114        }
115    }
116
117    /// Get as boolean
118    pub fn as_bool(&self) -> Option<bool> {
119        match self {
120            DynamicValue::Bool(b) => Some(*b),
121            DynamicValue::Integer(i) => Some(*i != 0),
122            _ => None,
123        }
124    }
125
126    /// Get as array
127    pub fn as_array(&self) -> Option<&Vec<DynamicValue>> {
128        match self {
129            DynamicValue::Array(arr) => Some(arr),
130            _ => None,
131        }
132    }
133
134    /// Get as mutable array
135    pub fn as_array_mut(&mut self) -> Option<&mut Vec<DynamicValue>> {
136        match self {
137            DynamicValue::Array(arr) => Some(arr),
138            _ => None,
139        }
140    }
141
142    /// Get as object
143    pub fn as_object(&self) -> Option<&HashMap<String, DynamicValue>> {
144        match self {
145            DynamicValue::Object(obj) => Some(obj),
146            _ => None,
147        }
148    }
149
150    /// Get as mutable object
151    pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, DynamicValue>> {
152        match self {
153            DynamicValue::Object(obj) => Some(obj),
154            _ => None,
155        }
156    }
157
158    /// Check if value is null
159    pub fn is_null(&self) -> bool {
160        matches!(self, DynamicValue::Null)
161    }
162
163    /// Array indexing (similar to Python list access)
164    pub fn get_index(&self, index: usize) -> Option<&DynamicValue> {
165        match self {
166            DynamicValue::Array(arr) => arr.get(index),
167            _ => None,
168        }
169    }
170
171    /// Mutable array indexing
172    pub fn get_index_mut(&mut self, index: usize) -> Option<&mut DynamicValue> {
173        match self {
174            DynamicValue::Array(arr) => arr.get_mut(index),
175            _ => None,
176        }
177    }
178
179    /// Object property access (similar to Python dict access)
180    pub fn get_property(&self, key: &str) -> Option<&DynamicValue> {
181        match self {
182            DynamicValue::Object(obj) => obj.get(key),
183            _ => None,
184        }
185    }
186
187    /// Mutable object property access
188    pub fn get_property_mut(&mut self, key: &str) -> Option<&mut DynamicValue> {
189        match self {
190            DynamicValue::Object(obj) => obj.get_mut(key),
191            _ => None,
192        }
193    }
194
195    /// Set object property
196    pub fn set_property(&mut self, key: String, value: DynamicValue) -> Result<()> {
197        match self {
198            DynamicValue::Object(obj) => {
199                obj.insert(key, value);
200                Ok(())
201            }
202            _ => Err(UnityAssetError::format(
203                "Cannot set property on non-object value",
204            )),
205        }
206    }
207
208    /// Add to array
209    pub fn push(&mut self, value: DynamicValue) -> Result<()> {
210        match self {
211            DynamicValue::Array(arr) => {
212                arr.push(value);
213                Ok(())
214            }
215            _ => Err(UnityAssetError::format("Cannot push to non-array value")),
216        }
217    }
218
219    /// String concatenation (Python-like += for strings)
220    pub fn concat_string(&mut self, other: &str) -> Result<()> {
221        match self {
222            DynamicValue::String(s) => {
223                s.push_str(other);
224                Ok(())
225            }
226            _ => Err(UnityAssetError::format(
227                "Cannot concatenate to non-string value",
228            )),
229        }
230    }
231
232    /// Numeric addition (Python-like += for numbers)
233    pub fn add_numeric(&mut self, other: f64) -> Result<()> {
234        match self {
235            DynamicValue::Integer(i) => {
236                *i += other as i64;
237                Ok(())
238            }
239            DynamicValue::Float(f) => {
240                *f += other;
241                Ok(())
242            }
243            _ => Err(UnityAssetError::format("Cannot add to non-numeric value")),
244        }
245    }
246}
247
248impl std::fmt::Display for DynamicValue {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        match self {
251            DynamicValue::String(s) => write!(f, "\"{}\"", s),
252            DynamicValue::Integer(i) => write!(f, "{}", i),
253            DynamicValue::Float(fl) => write!(f, "{}", fl),
254            DynamicValue::Bool(b) => write!(f, "{}", b),
255            DynamicValue::Array(arr) => {
256                write!(f, "[")?;
257                for (i, item) in arr.iter().enumerate() {
258                    if i > 0 {
259                        write!(f, ", ")?;
260                    }
261                    write!(f, "{}", item)?;
262                }
263                write!(f, "]")
264            }
265            DynamicValue::Object(obj) => {
266                write!(f, "{{")?;
267                for (i, (key, value)) in obj.iter().enumerate() {
268                    if i > 0 {
269                        write!(f, ", ")?;
270                    }
271                    write!(f, "\"{}\": {}", key, value)?;
272                }
273                write!(f, "}}")
274            }
275            DynamicValue::Null => write!(f, "null"),
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_dynamic_value_conversion() {
286        let unity_val = UnityValue::String("test".to_string());
287        let dynamic_val = DynamicValue::from_unity_value(&unity_val);
288
289        assert_eq!(dynamic_val, DynamicValue::String("test".to_string()));
290        assert_eq!(dynamic_val.to_unity_value(), unity_val);
291    }
292
293    #[test]
294    fn test_dynamic_value_access() {
295        let mut val = DynamicValue::String("hello".to_string());
296        val.concat_string(" world").unwrap();
297
298        assert_eq!(val.as_string(), Some("hello world"));
299    }
300
301    #[test]
302    fn test_numeric_operations() {
303        let mut val = DynamicValue::Integer(10);
304        val.add_numeric(5.0).unwrap();
305
306        assert_eq!(val.as_integer(), Some(15));
307    }
308}