amq_protocol_types/
types.rs

1use crate::value::AMQPValue;
2
3use std::{
4    borrow,
5    collections::{BTreeMap, btree_map},
6    fmt, str,
7};
8
9use serde::{Deserialize, Serialize};
10
11/// Enumeration referencing all the available AMQP types
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
13pub enum AMQPType {
14    /// A bool
15    Boolean,
16    /// An i8
17    ShortShortInt,
18    /// A u8
19    ShortShortUInt,
20    /// An i16
21    ShortInt,
22    /// A u16
23    ShortUInt,
24    /// An i32
25    LongInt,
26    /// A u32
27    LongUInt,
28    /// An i64
29    LongLongInt,
30    /// A u64
31    LongLongUInt,
32    /// An f32
33    Float,
34    /// An f64
35    Double,
36    /// A decimal value represented by a scale and a value
37    DecimalValue,
38    /// Deprecated, a String
39    ShortString,
40    /// A String
41    LongString,
42    /// An array of AMQPValue
43    FieldArray,
44    /// A timestamp (u64)
45    Timestamp,
46    /// A Map<String, AMQPValue>
47    FieldTable,
48    /// An array of bytes, RabbitMQ specific
49    ByteArray, /* ByteArray is specific to RabbitMQ */
50    /// No value
51    Void,
52}
53
54impl AMQPType {
55    /// Get the AMQPType corresponding to the given id.
56    /// We don't strictly follow the spec here but rather the RabbitMQ implementation
57    /// 's' means ShortInt (like 'U') instead of ShortString
58    /// 'l' and 'L' both mean LongLongInt (no LongLongUInt)
59    pub fn from_id(id: char) -> Option<AMQPType> {
60        match id {
61            't' => Some(AMQPType::Boolean),
62            'b' => Some(AMQPType::ShortShortInt),
63            'B' => Some(AMQPType::ShortShortUInt),
64            /* Specs says 'U', RabbitMQ says 's' (which means ShortString in specs) */
65            's' | 'U' => Some(AMQPType::ShortInt),
66            'u' => Some(AMQPType::ShortUInt),
67            'I' => Some(AMQPType::LongInt),
68            'i' => Some(AMQPType::LongUInt),
69            /* RabbitMQ treats both 'l' and 'L' as LongLongInt and ignores LongLongUInt */
70            'L' | 'l' => Some(AMQPType::LongLongInt),
71            'f' => Some(AMQPType::Float),
72            'd' => Some(AMQPType::Double),
73            'D' => Some(AMQPType::DecimalValue),
74            'S' => Some(AMQPType::LongString),
75            'A' => Some(AMQPType::FieldArray),
76            'T' => Some(AMQPType::Timestamp),
77            'F' => Some(AMQPType::FieldTable),
78            'x' => Some(AMQPType::ByteArray),
79            'V' => Some(AMQPType::Void),
80            _ => None,
81        }
82    }
83
84    /// Get the id from an AMQPType
85    /// We don't strictly follow the spec here but rather the RabbitMQ implementation
86    /// ShortString doesn't have an id, we return '_' instead
87    /// ShortInt is supposed to be 'U' but we use 's'
88    /// LongLongUInt is supposed to be 'L' but we return 'l' as LongLongInt
89    pub fn get_id(self) -> char {
90        match self {
91            AMQPType::Boolean => 't',
92            AMQPType::ShortShortInt => 'b',
93            AMQPType::ShortShortUInt => 'B',
94            /* Specs says 'U', RabbitMQ says 's' (which means ShortString in specs) */
95            AMQPType::ShortInt => 's',
96            AMQPType::ShortUInt => 'u',
97            AMQPType::LongInt => 'I',
98            AMQPType::LongUInt => 'i',
99            /* RabbitMQ treats both 'l' and 'L' as LongLongInt and ignores LongLongUInt */
100            AMQPType::LongLongInt | AMQPType::LongLongUInt => 'l',
101            AMQPType::Float => 'f',
102            AMQPType::Double => 'd',
103            AMQPType::DecimalValue => 'D',
104            /* ShortString only exists for internal usage, we shouldn't ever have to use this */
105            AMQPType::ShortString => '_',
106            AMQPType::LongString => 'S',
107            AMQPType::FieldArray => 'A',
108            AMQPType::Timestamp => 'T',
109            AMQPType::FieldTable => 'F',
110            AMQPType::ByteArray => 'x',
111            AMQPType::Void => 'V',
112        }
113    }
114}
115
116impl fmt::Display for AMQPType {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.write_fmt(format_args!("{self:?}"))
119    }
120}
121
122/// A bool
123pub type Boolean = bool;
124/// An i8
125pub type ShortShortInt = i8;
126/// A u8
127pub type ShortShortUInt = u8;
128/// An i16
129pub type ShortInt = i16;
130/// A u16
131pub type ShortUInt = u16;
132/// An i32
133pub type LongInt = i32;
134/// A u32
135pub type LongUInt = u32;
136/// An i64
137pub type LongLongInt = i64;
138/// A u64
139pub type LongLongUInt = u64;
140/// A f32
141pub type Float = f32;
142/// A f64
143pub type Double = f64;
144/// A timestamp (u64)
145pub type Timestamp = LongLongUInt;
146/// No value
147pub type Void = ();
148
149/// A String (deprecated)
150#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
151pub struct ShortString(String);
152/// A String
153#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
154pub struct LongString(Vec<u8>);
155/// An array of AMQPValue
156#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
157pub struct FieldArray(Vec<AMQPValue>);
158/// A Map<String, AMQPValue>
159#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
160pub struct FieldTable(BTreeMap<ShortString, AMQPValue>);
161/// An array of bytes (RabbitMQ specific)
162#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
163pub struct ByteArray(Vec<u8>);
164
165/// A Decimal value composed of a scale and a value
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
167pub struct DecimalValue {
168    /// The scale of the value
169    pub scale: ShortShortUInt,
170    /// The actual value
171    pub value: LongUInt,
172}
173
174impl<'a> ShortString {
175    /// Get a reference to a ShortString as &str
176    pub fn as_str(&'a self) -> &'a str {
177        self.0.as_str()
178    }
179}
180
181impl From<String> for ShortString {
182    fn from(s: String) -> Self {
183        Self(s)
184    }
185}
186
187impl From<&str> for ShortString {
188    fn from(s: &str) -> Self {
189        s.to_owned().into()
190    }
191}
192
193impl borrow::Borrow<str> for ShortString {
194    fn borrow(&self) -> &str {
195        self.0.borrow()
196    }
197}
198
199impl fmt::Display for ShortString {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        self.0.fmt(f)
202    }
203}
204
205impl From<ShortString> for String {
206    fn from(value: ShortString) -> Self {
207        value.0
208    }
209}
210
211impl<'a> LongString {
212    /// Get a reference to a LongString as &[u8]
213    pub fn as_bytes(&'a self) -> &'a [u8] {
214        &self.0[..]
215    }
216}
217
218impl<B> From<B> for LongString
219where
220    B: Into<Vec<u8>>,
221{
222    fn from(bytes: B) -> Self {
223        Self(bytes.into())
224    }
225}
226
227impl fmt::Display for LongString {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        String::from_utf8_lossy(&self.0).fmt(f)
230    }
231}
232
233impl FieldArray {
234    /// Get the inner values as a slice
235    pub fn as_slice(&self) -> &[AMQPValue] {
236        self.0.as_slice()
237    }
238
239    /// Add an item to the array
240    pub fn push(&mut self, v: AMQPValue) {
241        self.0.push(v);
242    }
243}
244
245impl From<Vec<AMQPValue>> for FieldArray {
246    fn from(v: Vec<AMQPValue>) -> Self {
247        Self(v)
248    }
249}
250
251impl FieldTable {
252    /// Insert a new entry in the table
253    pub fn insert(&mut self, k: ShortString, v: AMQPValue) -> &mut Self {
254        self.0.insert(k, v);
255        self
256    }
257
258    /// Check whether the table contains the given key
259    pub fn contains_key(&self, k: &str) -> bool {
260        self.0.contains_key(k)
261    }
262
263    /// Access the inner BTreeMap to perform lookups
264    pub fn inner(&self) -> &BTreeMap<ShortString, AMQPValue> {
265        &self.0
266    }
267}
268
269impl<'a> IntoIterator for &'a FieldTable {
270    type Item = (&'a ShortString, &'a AMQPValue);
271    type IntoIter = btree_map::Iter<'a, ShortString, AMQPValue>;
272
273    fn into_iter(self) -> Self::IntoIter {
274        self.0.iter()
275    }
276}
277
278impl From<BTreeMap<ShortString, AMQPValue>> for FieldTable {
279    fn from(m: BTreeMap<ShortString, AMQPValue>) -> Self {
280        Self(m)
281    }
282}
283
284impl ByteArray {
285    /// Get the inner bytes array as slice
286    pub fn as_slice(&self) -> &[u8] {
287        self.0.as_slice()
288    }
289
290    /// Get the length of the inner bytes array
291    pub fn len(&self) -> usize {
292        self.0.len()
293    }
294
295    /// Check whether the ByteArray is empty
296    pub fn is_empty(&self) -> bool {
297        self.0.is_empty()
298    }
299}
300
301impl From<Vec<u8>> for ByteArray {
302    fn from(v: Vec<u8>) -> Self {
303        Self(v)
304    }
305}
306
307impl From<&[u8]> for ByteArray {
308    fn from(v: &[u8]) -> Self {
309        Self(v.to_vec())
310    }
311}
312
313#[cfg(test)]
314mod test {
315    use super::*;
316
317    #[test]
318    fn test_type_from_id() {
319        assert_eq!(AMQPType::from_id('T'), Some(AMQPType::Timestamp));
320        assert_eq!(AMQPType::from_id('S'), Some(AMQPType::LongString));
321        assert_eq!(AMQPType::from_id('s'), Some(AMQPType::ShortInt));
322        assert_eq!(AMQPType::from_id('U'), Some(AMQPType::ShortInt));
323        assert_eq!(AMQPType::from_id('l'), Some(AMQPType::LongLongInt));
324        assert_eq!(AMQPType::from_id('z'), None);
325    }
326
327    #[test]
328    fn test_type_get_id() {
329        assert_eq!(AMQPType::LongLongInt.get_id(), 'l');
330        assert_eq!(AMQPType::LongLongUInt.get_id(), 'l');
331        assert_eq!(AMQPType::ShortString.get_id(), '_');
332    }
333
334    #[test]
335    fn test_type_to_string() {
336        assert_eq!(AMQPType::Boolean.to_string(), "Boolean");
337        assert_eq!(AMQPType::Void.to_string(), "Void");
338    }
339
340    #[test]
341    fn long_string_ergonomics() {
342        let str_ref = "string ref";
343        let str_owned = "string owned".to_owned();
344        let vec = b"bytes".to_vec();
345        let array = b"bytes".to_owned();
346        let slice = &b"bytes"[..];
347
348        let from_str_ref: LongString = str_ref.into();
349        let from_str_owned: LongString = str_owned.clone().into();
350        let from_vec: LongString = vec.clone().into();
351        let from_array: LongString = array.into();
352        let from_slice: LongString = slice.into();
353
354        for (left, right) in [
355            (str_ref.as_bytes(), from_str_ref.as_bytes()),
356            (str_owned.as_bytes(), from_str_owned.as_bytes()),
357            (vec.as_ref(), from_vec.as_bytes()),
358            (array.as_ref(), from_array.as_bytes()),
359            (slice, from_slice.as_bytes()),
360        ] {
361            assert_eq!(left, right);
362        }
363    }
364}