rtmp_rs/amf/
value.rs

1//! AMF value types
2//!
3//! Both AMF0 and AMF3 share a common value representation. This enum
4//! provides a unified type that can be serialized to either format.
5
6use std::collections::HashMap;
7
8/// Unified AMF value representation
9///
10/// This enum represents all value types supported by AMF0 and AMF3.
11/// Some types (like ByteArray, Dictionary) are AMF3-only but included
12/// for completeness.
13#[derive(Debug, Clone, PartialEq)]
14pub enum AmfValue {
15    /// Null value (AMF0: 0x05, AMF3: 0x01)
16    Null,
17
18    /// Undefined value (AMF0: 0x06, AMF3: 0x00)
19    Undefined,
20
21    /// Boolean value (AMF0: 0x01, AMF3: 0x02/0x03)
22    Boolean(bool),
23
24    /// IEEE 754 double-precision floating point (AMF0: 0x00, AMF3: 0x05)
25    Number(f64),
26
27    /// UTF-8 string (AMF0: 0x02, AMF3: 0x06)
28    String(String),
29
30    /// Ordered array with optional associative portion
31    /// In AMF0 this is either StrictArray (0x0A) or ECMAArray (0x08)
32    /// In AMF3 this is Array (0x09)
33    Array(Vec<AmfValue>),
34
35    /// Key-value object (AMF0: 0x03, AMF3: 0x0A)
36    /// Keys are always strings in AMF
37    Object(HashMap<String, AmfValue>),
38
39    /// Typed object with class name
40    TypedObject {
41        class_name: String,
42        properties: HashMap<String, AmfValue>,
43    },
44
45    /// Date value as milliseconds since Unix epoch
46    /// (AMF0: 0x0B, AMF3: 0x08)
47    Date(f64),
48
49    /// XML document (AMF0: 0x0F, AMF3: 0x07/0x0B)
50    Xml(String),
51
52    /// Raw byte array (AMF3 only: 0x0C)
53    ByteArray(Vec<u8>),
54
55    /// Integer (AMF3 only: 0x04, 29-bit signed)
56    Integer(i32),
57
58    /// ECMA Array - associative array with dense and sparse parts
59    /// Stored as (dense_length, properties)
60    EcmaArray(HashMap<String, AmfValue>),
61}
62
63impl AmfValue {
64    /// Try to get this value as a string reference
65    pub fn as_str(&self) -> Option<&str> {
66        match self {
67            AmfValue::String(s) => Some(s),
68            _ => None,
69        }
70    }
71
72    /// Try to get this value as a number
73    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    /// Try to get this value as a boolean
82    pub fn as_bool(&self) -> Option<bool> {
83        match self {
84            AmfValue::Boolean(b) => Some(*b),
85            _ => None,
86        }
87    }
88
89    /// Try to get this value as an object reference
90    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    /// Try to get this value as a mutable object reference
100    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    /// Try to get this value as an array reference
110    pub fn as_array(&self) -> Option<&Vec<AmfValue>> {
111        match self {
112            AmfValue::Array(a) => Some(a),
113            _ => None,
114        }
115    }
116
117    /// Check if this value is null or undefined
118    pub fn is_null_or_undefined(&self) -> bool {
119        matches!(self, AmfValue::Null | AmfValue::Undefined)
120    }
121
122    /// Get a property from an object value
123    pub fn get(&self, key: &str) -> Option<&AmfValue> {
124        self.as_object()?.get(key)
125    }
126
127    /// Get a string property from an object value
128    pub fn get_string(&self, key: &str) -> Option<&str> {
129        self.get(key)?.as_str()
130    }
131
132    /// Get a number property from an object value
133    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}