Skip to main content

ntex_amqp_codec/types/
variant.rs

1use std::hash::{Hash, Hasher};
2
3use chrono::{DateTime, Utc};
4use ntex_bytes::{BytePages, ByteString, Bytes};
5use ordered_float::OrderedFloat;
6use uuid::Uuid;
7
8use crate::types::{Array, Descriptor, List, Str, Symbol};
9use crate::{AmqpParseError, Decode, Encode, HashMap, protocol::Annotations};
10
11/// Represents an AMQP type for use in polymorphic collections
12#[derive(Debug, Eq, PartialEq, Hash, Clone, From)]
13pub enum Variant {
14    /// Indicates an empty value.
15    Null,
16
17    /// Represents a true or false value.
18    Boolean(bool),
19
20    /// Integer in the range 0 to 2^8 - 1 inclusive.
21    Ubyte(u8),
22
23    /// Integer in the range 0 to 2^16 - 1 inclusive.
24    Ushort(u16),
25
26    /// Integer in the range 0 to 2^32 - 1 inclusive.
27    Uint(u32),
28
29    /// Integer in the range 0 to 2^64 - 1 inclusive.
30    Ulong(u64),
31
32    /// Integer in the range 0 to 2^7 - 1 inclusive.
33    Byte(i8),
34
35    /// Integer in the range 0 to 2^15 - 1 inclusive.
36    Short(i16),
37
38    /// Integer in the range 0 to 2^32 - 1 inclusive.
39    Int(i32),
40
41    /// Integer in the range 0 to 2^64 - 1 inclusive.
42    Long(i64),
43
44    /// 32-bit floating point number (IEEE 754-2008 binary32).
45    Float(OrderedFloat<f32>),
46
47    /// 64-bit floating point number (IEEE 754-2008 binary64).
48    Double(OrderedFloat<f64>),
49
50    /// 32-bit decimal number, represented per IEEE 754-2008 decimal32 specification.
51    Decimal32([u8; 4]),
52
53    /// 64-bit decimal number, represented per IEEE 754-2008 decimal64 specification.
54    Decimal64([u8; 8]),
55
56    /// 128-bit decimal number, represented per IEEE 754-2008 decimal128 specification.
57    Decimal128([u8; 16]),
58
59    /// A single Unicode character.
60    Char(char),
61
62    /// An absolute point in time.
63    /// Represents an approximate point in time using the Unix time encoding of
64    /// UTC with a precision of milliseconds. For example, 1311704463521
65    /// represents the moment 2011-07-26T18:21:03.521Z.
66    Timestamp(DateTime<Utc>),
67
68    /// A universally unique identifier as defined by RFC-4122 section 4.1.2
69    Uuid(Uuid),
70
71    /// A sequence of octets.
72    Binary(Bytes),
73
74    /// A sequence of Unicode characters
75    String(Str),
76
77    /// Symbolic values from a constrained domain.
78    Symbol(Symbol),
79
80    /// List
81    List(List),
82
83    /// Map
84    Map(VariantMap),
85
86    /// Array
87    Array(Array),
88
89    /// Described value of primitive type. See `Variant::DescribedCompound` for
90    Described((Descriptor, Box<Variant>)),
91
92    /// Described value of compound or array type. See `Variant::DescribedCompound` for details.
93    DescribedCompound(DescribedCompound),
94}
95
96/// Represents a compound value with a descriptor. The value contains data starting with format code for the underlying AMQP type
97/// (right after the descriptor).
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub struct DescribedCompound {
100    descriptor: Descriptor,
101    pub(crate) data: Bytes,
102}
103
104impl DescribedCompound {
105    /// Creates a representation of described value of compound value type based on `T` type's AMQP encoding.
106    /// `T`'s implementation of `Encode` is expected to produce the binary representation of the T in an underlying AMQP type value, starting from the format code.
107    /// For instance, if the described value is to be represented as an AMQP list with 1 ubyte field with a value of 3:
108    /// ```text
109    /// 0x00 0xa3 0x07 "foo:bar" 0xc0 0x02 0x01 0x50 0x03
110    /// ```
111    /// The `T::encode` method is expected to produce the following output:
112    /// ```text
113    /// 0xc0 0x02 0x01 0x50 0x03
114    /// ```
115    pub fn create<T: Encode>(descriptor: Descriptor, value: T) -> Self {
116        let mut data = BytePages::default();
117        value.encode(&mut data);
118        DescribedCompound {
119            descriptor,
120            data: data.freeze(),
121        }
122    }
123
124    pub(crate) fn new(descriptor: Descriptor, data: Bytes) -> Self {
125        DescribedCompound { descriptor, data }
126    }
127
128    pub fn descriptor(&self) -> &Descriptor {
129        &self.descriptor
130    }
131
132    /// Attempts to decode the described value as `T`.
133    /// `T`'s implementation of `Decode` is expected to parse the underlying AMQP type starting from the format code.
134    /// For instance, if the value is of described type represented by AMQP list with 1 ubyte field with a value of 3:
135    /// ```text
136    /// 0x00 0xa3 0x07 "foo:bar" 0xc0 0x02 0x01 0x50 0x03
137    /// ```
138    /// The `T::decode` method will be called with the following input:
139    /// ```text
140    /// 0xc0 0x02 0x01 0x50 0x03
141    /// ```
142    pub fn decode<T: Decode>(&self) -> Result<T, AmqpParseError> {
143        let mut buf = self.data.clone();
144        let result = T::decode(&mut buf)?;
145        if buf.is_empty() {
146            Ok(result)
147        } else {
148            Err(AmqpParseError::InvalidSize)
149        }
150    }
151}
152
153impl Encode for DescribedCompound {
154    fn encoded_size(&self) -> usize {
155        self.descriptor.encoded_size() + self.data.len()
156    }
157
158    fn encode(&self, buf: &mut BytePages) {
159        self.descriptor.encode(buf);
160        buf.append(self.data.clone());
161    }
162}
163
164impl From<HashMap<Variant, Variant>> for Variant {
165    fn from(data: HashMap<Variant, Variant>) -> Self {
166        Variant::Map(VariantMap { map: data })
167    }
168}
169
170impl From<ByteString> for Variant {
171    fn from(s: ByteString) -> Self {
172        Str::from(s).into()
173    }
174}
175
176impl From<String> for Variant {
177    fn from(s: String) -> Self {
178        Str::from(ByteString::from(s)).into()
179    }
180}
181
182impl From<&'static str> for Variant {
183    fn from(s: &'static str) -> Self {
184        Str::from(s).into()
185    }
186}
187
188impl PartialEq<str> for Variant {
189    fn eq(&self, other: &str) -> bool {
190        match self {
191            Variant::String(s) => s == other,
192            Variant::Symbol(s) => s == other,
193            _ => false,
194        }
195    }
196}
197
198impl Variant {
199    pub fn as_str(&self) -> Option<&str> {
200        match self {
201            Variant::String(s) => Some(s.as_str()),
202            Variant::Symbol(s) => Some(s.as_str()),
203            _ => None,
204        }
205    }
206
207    /// Expresses integer-typed variant values as i64 value when possible. Notably, does not include ulong.
208    /// Returns `None` for variants other than supported integers.
209    pub fn as_long(&self) -> Option<i64> {
210        match self {
211            Variant::Ubyte(v) => Some(*v as i64),
212            Variant::Ushort(v) => Some(*v as i64),
213            Variant::Uint(v) => Some(*v as i64),
214            Variant::Byte(v) => Some(*v as i64),
215            Variant::Short(v) => Some(*v as i64),
216            Variant::Int(v) => Some(*v as i64),
217            Variant::Long(v) => Some(*v),
218            _ => None,
219        }
220    }
221
222    /// Expresses unsigned integer-typed variant values as u64 value. Returns `None` for variants other than unsigned integers.
223    pub fn as_ulong(&self) -> Option<u64> {
224        match self {
225            Variant::Ubyte(v) => Some(*v as u64),
226            Variant::Ushort(v) => Some(*v as u64),
227            Variant::Uint(v) => Some(*v as u64),
228            Variant::Ulong(v) => Some(*v),
229            _ => None,
230        }
231    }
232
233    pub fn to_bytes_str(&self) -> Option<ByteString> {
234        match self {
235            Variant::String(s) => Some(s.to_bytes_str()),
236            Variant::Symbol(s) => Some(s.to_bytes_str()),
237            _ => None,
238        }
239    }
240}
241
242#[derive(PartialEq, Eq, Clone, Debug)]
243pub struct VariantMap {
244    pub map: HashMap<Variant, Variant>,
245}
246
247impl VariantMap {
248    pub fn new(map: HashMap<Variant, Variant>) -> VariantMap {
249        VariantMap { map }
250    }
251}
252
253#[allow(clippy::derived_hash_with_manual_eq)]
254impl Hash for VariantMap {
255    fn hash<H: Hasher>(&self, _state: &mut H) {
256        unimplemented!()
257    }
258}
259
260#[derive(PartialEq, Eq, Clone, Debug)]
261pub struct VecSymbolMap(pub Vec<(Symbol, Variant)>);
262
263impl Default for VecSymbolMap {
264    fn default() -> Self {
265        VecSymbolMap(Vec::with_capacity(8))
266    }
267}
268
269impl From<Annotations> for VecSymbolMap {
270    fn from(anns: Annotations) -> VecSymbolMap {
271        VecSymbolMap(anns.into_iter().collect())
272    }
273}
274
275impl From<Vec<(Symbol, Variant)>> for VecSymbolMap {
276    fn from(data: Vec<(Symbol, Variant)>) -> VecSymbolMap {
277        VecSymbolMap(data)
278    }
279}
280
281impl std::ops::Deref for VecSymbolMap {
282    type Target = Vec<(Symbol, Variant)>;
283
284    fn deref(&self) -> &Self::Target {
285        &self.0
286    }
287}
288
289impl std::ops::DerefMut for VecSymbolMap {
290    fn deref_mut(&mut self) -> &mut Self::Target {
291        &mut self.0
292    }
293}
294
295#[derive(PartialEq, Eq, Clone, Debug)]
296pub struct VecStringMap(pub Vec<(Str, Variant)>);
297
298impl Default for VecStringMap {
299    fn default() -> Self {
300        VecStringMap(Vec::with_capacity(8))
301    }
302}
303
304impl From<Vec<(Str, Variant)>> for VecStringMap {
305    fn from(data: Vec<(Str, Variant)>) -> VecStringMap {
306        VecStringMap(data)
307    }
308}
309
310impl From<HashMap<Str, Variant>> for VecStringMap {
311    fn from(map: HashMap<Str, Variant>) -> VecStringMap {
312        VecStringMap(map.into_iter().collect())
313    }
314}
315
316impl std::ops::Deref for VecStringMap {
317    type Target = Vec<(Str, Variant)>;
318
319    fn deref(&self) -> &Self::Target {
320        &self.0
321    }
322}
323
324impl std::ops::DerefMut for VecStringMap {
325    fn deref_mut(&mut self) -> &mut Self::Target {
326        &mut self.0
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use ntex_bytes::{Buf, BufMut};
333
334    use crate::{codec::ListHeader, format_codes};
335
336    use super::*;
337
338    #[test]
339    fn bytes_eq() {
340        let bytes1 = Variant::Binary(Bytes::from(&b"hello"[..]));
341        let bytes2 = Variant::Binary(Bytes::from(&b"hello"[..]));
342        let bytes3 = Variant::Binary(Bytes::from(&b"world"[..]));
343
344        assert_eq!(bytes1, bytes2);
345        assert!(bytes1 != bytes3);
346    }
347
348    #[test]
349    fn string_eq() {
350        let a = Variant::String(ByteString::from("hello").into());
351        let b = Variant::String(ByteString::from("world!").into());
352
353        assert_eq!(Variant::String(ByteString::from("hello").into()), a);
354        assert!(a != b);
355    }
356
357    #[test]
358    fn symbol_eq() {
359        let a = Variant::Symbol(Symbol::from("hello"));
360        let b = Variant::Symbol(Symbol::from("world!"));
361
362        assert_eq!(Variant::Symbol(Symbol::from("hello")), a);
363        assert!(a != b);
364    }
365
366    // <type name="mqtt-metadata" class="composite" source="list">
367    //   <descriptor name="contoso:test"/>
368    //   <field name="field1" type="string" mandatory="true"/>
369    //   <field name="field2" type="ubyte" mandatory="true"/>
370    //   <field name="field3" type="string"/>
371    // </type>
372    #[derive(Debug, PartialEq, Eq, Clone)]
373    struct CustomList {
374        field1: ByteString,
375        field2: u8,
376        field3: Option<ByteString>,
377    }
378
379    impl CustomList {
380        fn encoded_data_size(&self) -> usize {
381            let mut size = self.field1.encoded_size() + self.field2.encoded_size();
382            if let Some(ref field3) = self.field3 {
383                size += field3.encoded_size();
384            }
385            size
386        }
387    }
388
389    impl crate::DecodeFormatted for CustomList {
390        fn decode_with_format(input: &mut Bytes, fmt: u8) -> Result<Self, AmqpParseError> {
391            let header = ListHeader::decode_with_format(input, fmt)?;
392            if header.count < 2 {
393                return Err(AmqpParseError::RequiredFieldOmitted("field2"));
394            }
395            let field1 = ByteString::decode(input)?;
396            let field2 = u8::decode(input)?;
397            let field3 = if header.count == 3 {
398                Some(ByteString::decode(input)?)
399            } else {
400                None
401            };
402            if input.has_remaining() {
403                return Err(AmqpParseError::InvalidSize);
404            }
405            Ok(CustomList {
406                field1,
407                field2,
408                field3,
409            })
410        }
411    }
412
413    impl crate::Encode for CustomList {
414        fn encoded_size(&self) -> usize {
415            let size = self.encoded_data_size();
416            if size + 1 > u8::MAX as usize {
417                size + 9 // 1 for format code, 4 for size, 4 for count
418            } else {
419                size + 3 // 1 for format code, 1 for size, 1 for count
420            }
421        }
422
423        fn encode(&self, buf: &mut BytePages) {
424            let count = if self.field3.is_some() { 3u8 } else { 2u8 };
425            let data_size = self.encoded_data_size();
426            if data_size + 1 > u8::MAX as usize {
427                buf.put_u8(format_codes::FORMATCODE_LIST32);
428                buf.put_u32((4 + data_size) as u32); // size. 4 for count
429                buf.put_u32(count as u32); // count
430            } else {
431                buf.put_u8(format_codes::FORMATCODE_LIST8);
432                buf.put_u8((1 + data_size) as u8); // size. 1 for count
433                buf.put_u8(count); // count
434            }
435            self.field1.encode(buf);
436            self.field2.encode(buf);
437            if let Some(ref field3) = self.field3 {
438                field3.encode(buf);
439            }
440        }
441    }
442
443    #[test]
444    fn described_custom_list_recoding() {
445        let custom_list = CustomList {
446            field1: ByteString::from("value1"),
447            field2: 115,
448            field3: Some(ByteString::from("value3")),
449        };
450        let value = Variant::DescribedCompound(DescribedCompound::create(
451            Descriptor::Symbol("contoso:test".into()),
452            custom_list.clone(),
453        ));
454        let mut buf = BytePages::default();
455        value.encode(&mut buf);
456        let data = buf.freeze();
457        assert_eq!(
458            data.as_ref(),
459            &b"\x00\xa3\x0ccontoso:test\xc0\x13\x03\xa1\x06value1\x50\x73\xa1\x06value3"[..]
460        );
461        let mut input = data.clone();
462        let decoded = Variant::decode(&mut input).unwrap();
463        assert_eq!(decoded, value);
464        let decoded_list = match decoded {
465            Variant::DescribedCompound(desc) => desc.decode::<CustomList>().unwrap(),
466            _ => panic!("Expected a described compound"),
467        };
468        assert_eq!(decoded_list, custom_list);
469    }
470}