1use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq)]
14pub enum AmfValue {
15 Null,
17
18 Undefined,
20
21 Boolean(bool),
23
24 Number(f64),
26
27 String(String),
29
30 Array(Vec<AmfValue>),
34
35 Object(HashMap<String, AmfValue>),
38
39 TypedObject {
41 class_name: String,
42 properties: HashMap<String, AmfValue>,
43 },
44
45 Date(f64),
48
49 Xml(String),
51
52 ByteArray(Vec<u8>),
54
55 Integer(i32),
57
58 EcmaArray(HashMap<String, AmfValue>),
61}
62
63impl AmfValue {
64 pub fn as_str(&self) -> Option<&str> {
66 match self {
67 AmfValue::String(s) => Some(s),
68 _ => None,
69 }
70 }
71
72 pub fn as_number(&self) -> Option<f64> {
74 match self {
75 AmfValue::Number(n) => Some(*n),
76 AmfValue::Integer(i) => Some(*i as f64),
77 _ => None,
78 }
79 }
80
81 pub fn as_bool(&self) -> Option<bool> {
83 match self {
84 AmfValue::Boolean(b) => Some(*b),
85 _ => None,
86 }
87 }
88
89 pub fn as_object(&self) -> Option<&HashMap<String, AmfValue>> {
91 match self {
92 AmfValue::Object(m) => Some(m),
93 AmfValue::EcmaArray(m) => Some(m),
94 AmfValue::TypedObject { properties, .. } => Some(properties),
95 _ => None,
96 }
97 }
98
99 pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, AmfValue>> {
101 match self {
102 AmfValue::Object(m) => Some(m),
103 AmfValue::EcmaArray(m) => Some(m),
104 AmfValue::TypedObject { properties, .. } => Some(properties),
105 _ => None,
106 }
107 }
108
109 pub fn as_array(&self) -> Option<&Vec<AmfValue>> {
111 match self {
112 AmfValue::Array(a) => Some(a),
113 _ => None,
114 }
115 }
116
117 pub fn is_null_or_undefined(&self) -> bool {
119 matches!(self, AmfValue::Null | AmfValue::Undefined)
120 }
121
122 pub fn get(&self, key: &str) -> Option<&AmfValue> {
124 self.as_object()?.get(key)
125 }
126
127 pub fn get_string(&self, key: &str) -> Option<&str> {
129 self.get(key)?.as_str()
130 }
131
132 pub fn get_number(&self, key: &str) -> Option<f64> {
134 self.get(key)?.as_number()
135 }
136}
137
138impl Default for AmfValue {
139 fn default() -> Self {
140 AmfValue::Null
141 }
142}
143
144impl From<bool> for AmfValue {
145 fn from(v: bool) -> Self {
146 AmfValue::Boolean(v)
147 }
148}
149
150impl From<f64> for AmfValue {
151 fn from(v: f64) -> Self {
152 AmfValue::Number(v)
153 }
154}
155
156impl From<i32> for AmfValue {
157 fn from(v: i32) -> Self {
158 AmfValue::Number(v as f64)
159 }
160}
161
162impl From<u32> for AmfValue {
163 fn from(v: u32) -> Self {
164 AmfValue::Number(v as f64)
165 }
166}
167
168impl From<String> for AmfValue {
169 fn from(v: String) -> Self {
170 AmfValue::String(v)
171 }
172}
173
174impl From<&str> for AmfValue {
175 fn from(v: &str) -> Self {
176 AmfValue::String(v.to_string())
177 }
178}
179
180impl<V: Into<AmfValue>> From<Vec<V>> for AmfValue {
181 fn from(v: Vec<V>) -> Self {
182 AmfValue::Array(v.into_iter().map(|x| x.into()).collect())
183 }
184}
185
186impl<V: Into<AmfValue>> From<HashMap<String, V>> for AmfValue {
187 fn from(v: HashMap<String, V>) -> Self {
188 AmfValue::Object(v.into_iter().map(|(k, v)| (k, v.into())).collect())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_value_accessors() {
198 let s = AmfValue::String("test".into());
199 assert_eq!(s.as_str(), Some("test"));
200 assert_eq!(s.as_number(), None);
201
202 let n = AmfValue::Number(42.0);
203 assert_eq!(n.as_number(), Some(42.0));
204 assert_eq!(n.as_str(), None);
205
206 let mut obj = HashMap::new();
207 obj.insert("key".to_string(), AmfValue::String("value".into()));
208 let o = AmfValue::Object(obj);
209 assert_eq!(o.get_string("key"), Some("value"));
210 }
211
212 #[test]
213 fn test_from_conversions() {
214 let v: AmfValue = "test".into();
215 assert!(matches!(v, AmfValue::String(_)));
216
217 let v: AmfValue = 42.0.into();
218 assert!(matches!(v, AmfValue::Number(_)));
219
220 let v: AmfValue = true.into();
221 assert!(matches!(v, AmfValue::Boolean(true)));
222 }
223
224 #[test]
225 fn test_as_bool() {
226 assert_eq!(AmfValue::Boolean(true).as_bool(), Some(true));
227 assert_eq!(AmfValue::Boolean(false).as_bool(), Some(false));
228 assert_eq!(AmfValue::Number(1.0).as_bool(), None);
229 assert_eq!(AmfValue::Null.as_bool(), None);
230 }
231
232 #[test]
233 fn test_as_array() {
234 let arr = AmfValue::Array(vec![AmfValue::Number(1.0), AmfValue::Number(2.0)]);
235 assert!(arr.as_array().is_some());
236 assert_eq!(arr.as_array().unwrap().len(), 2);
237
238 assert!(AmfValue::Null.as_array().is_none());
239 assert!(AmfValue::Object(HashMap::new()).as_array().is_none());
240 }
241
242 #[test]
243 fn test_as_object_mut() {
244 let mut obj = AmfValue::Object(HashMap::new());
245 if let Some(map) = obj.as_object_mut() {
246 map.insert("key".to_string(), AmfValue::String("value".into()));
247 }
248
249 assert_eq!(obj.get_string("key"), Some("value"));
250 }
251
252 #[test]
253 fn test_is_null_or_undefined() {
254 assert!(AmfValue::Null.is_null_or_undefined());
255 assert!(AmfValue::Undefined.is_null_or_undefined());
256 assert!(!AmfValue::Boolean(false).is_null_or_undefined());
257 assert!(!AmfValue::Number(0.0).is_null_or_undefined());
258 assert!(!AmfValue::String(String::new()).is_null_or_undefined());
259 }
260
261 #[test]
262 fn test_get_number() {
263 let mut obj = HashMap::new();
264 obj.insert("count".to_string(), AmfValue::Number(42.0));
265 obj.insert("name".to_string(), AmfValue::String("test".into()));
266 let amf = AmfValue::Object(obj);
267
268 assert_eq!(amf.get_number("count"), Some(42.0));
269 assert_eq!(amf.get_number("name"), None);
270 assert_eq!(amf.get_number("missing"), None);
271 }
272
273 #[test]
274 fn test_integer_as_number() {
275 let integer = AmfValue::Integer(100);
276 assert_eq!(integer.as_number(), Some(100.0));
277 }
278
279 #[test]
280 fn test_default_value() {
281 let default = AmfValue::default();
282 assert_eq!(default, AmfValue::Null);
283 }
284
285 #[test]
286 fn test_from_i32() {
287 let v: AmfValue = 42i32.into();
288 assert_eq!(v, AmfValue::Number(42.0));
289 }
290
291 #[test]
292 fn test_from_u32() {
293 let v: AmfValue = 1000u32.into();
294 assert_eq!(v, AmfValue::Number(1000.0));
295 }
296
297 #[test]
298 fn test_from_string_owned() {
299 let s = String::from("owned");
300 let v: AmfValue = s.into();
301 assert_eq!(v.as_str(), Some("owned"));
302 }
303
304 #[test]
305 fn test_from_vec() {
306 let vec: Vec<f64> = vec![1.0, 2.0, 3.0];
307 let v: AmfValue = vec.into();
308 if let AmfValue::Array(arr) = v {
309 assert_eq!(arr.len(), 3);
310 } else {
311 panic!("Expected Array");
312 }
313 }
314
315 #[test]
316 fn test_from_hashmap() {
317 let mut map = HashMap::new();
318 map.insert("a".to_string(), 1.0f64);
319 map.insert("b".to_string(), 2.0f64);
320
321 let v: AmfValue = map.into();
322 if let AmfValue::Object(obj) = v {
323 assert_eq!(obj.len(), 2);
324 } else {
325 panic!("Expected Object");
326 }
327 }
328
329 #[test]
330 fn test_as_object_with_typed_object() {
331 let mut props = HashMap::new();
332 props.insert("x".to_string(), AmfValue::Number(10.0));
333
334 let typed = AmfValue::TypedObject {
335 class_name: "Point".to_string(),
336 properties: props,
337 };
338
339 assert!(typed.as_object().is_some());
341 assert_eq!(typed.get_number("x"), Some(10.0));
342 }
343
344 #[test]
345 fn test_as_object_with_ecma_array() {
346 let mut props = HashMap::new();
347 props.insert("key".to_string(), AmfValue::String("value".into()));
348
349 let ecma = AmfValue::EcmaArray(props);
350
351 assert!(ecma.as_object().is_some());
353 assert_eq!(ecma.get_string("key"), Some("value"));
354 }
355
356 #[test]
357 fn test_get_on_non_object() {
358 assert!(AmfValue::Null.get("key").is_none());
359 assert!(AmfValue::Number(42.0).get("key").is_none());
360 assert!(AmfValue::Array(vec![]).get("0").is_none());
361 }
362
363 #[test]
364 fn test_amf_value_clone() {
365 let original = AmfValue::Object({
366 let mut m = HashMap::new();
367 m.insert(
368 "nested".to_string(),
369 AmfValue::Array(vec![AmfValue::Number(1.0), AmfValue::String("test".into())]),
370 );
371 m
372 });
373
374 let cloned = original.clone();
375 assert_eq!(original, cloned);
376 }
377
378 #[test]
379 fn test_amf_value_partial_eq() {
380 assert_eq!(AmfValue::Null, AmfValue::Null);
381 assert_ne!(AmfValue::Null, AmfValue::Undefined);
382 assert_eq!(AmfValue::Number(1.0), AmfValue::Number(1.0));
383 assert_ne!(AmfValue::Number(1.0), AmfValue::Number(2.0));
384 assert_eq!(
385 AmfValue::String("test".into()),
386 AmfValue::String("test".into())
387 );
388 }
389}