Skip to main content

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 LongString {
212    /// Get a reference to a LongString as &[u8]
213    pub fn as_bytes(&self) -> &[u8] {
214        &self.0[..]
215    }
216
217    /// Get the length of the inner bytes array
218    pub fn len(&self) -> usize {
219        self.0.len()
220    }
221
222    /// Check whether the inner bytes array is empty or not
223    pub fn is_empty(&self) -> bool {
224        self.0.is_empty()
225    }
226}
227
228impl<B> From<B> for LongString
229where
230    B: Into<Vec<u8>>,
231{
232    fn from(bytes: B) -> Self {
233        Self(bytes.into())
234    }
235}
236
237impl fmt::Display for LongString {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        String::from_utf8_lossy(&self.0).fmt(f)
240    }
241}
242
243impl FieldArray {
244    /// Get the inner values as a slice
245    pub fn as_slice(&self) -> &[AMQPValue] {
246        self.0.as_slice()
247    }
248
249    /// Add an item to the array
250    pub fn push(&mut self, v: AMQPValue) {
251        self.0.push(v);
252    }
253}
254
255impl From<Vec<AMQPValue>> for FieldArray {
256    fn from(v: Vec<AMQPValue>) -> Self {
257        Self(v)
258    }
259}
260
261impl FieldTable {
262    /// Insert a new entry in the table
263    pub fn insert(&mut self, k: ShortString, v: AMQPValue) -> &mut Self {
264        self.0.insert(k, v);
265        self
266    }
267
268    /// Check whether the table contains the given key
269    pub fn contains_key(&self, k: &str) -> bool {
270        self.0.contains_key(k)
271    }
272
273    /// Access the inner BTreeMap to perform lookups
274    pub fn inner(&self) -> &BTreeMap<ShortString, AMQPValue> {
275        &self.0
276    }
277}
278
279impl<'a> IntoIterator for &'a FieldTable {
280    type Item = (&'a ShortString, &'a AMQPValue);
281    type IntoIter = btree_map::Iter<'a, ShortString, AMQPValue>;
282
283    fn into_iter(self) -> Self::IntoIter {
284        self.0.iter()
285    }
286}
287
288impl From<BTreeMap<ShortString, AMQPValue>> for FieldTable {
289    fn from(m: BTreeMap<ShortString, AMQPValue>) -> Self {
290        Self(m)
291    }
292}
293
294impl ByteArray {
295    /// Get the inner bytes array as slice
296    pub fn as_slice(&self) -> &[u8] {
297        self.0.as_slice()
298    }
299
300    /// Get the length of the inner bytes array
301    pub fn len(&self) -> usize {
302        self.0.len()
303    }
304
305    /// Check whether the ByteArray is empty
306    pub fn is_empty(&self) -> bool {
307        self.0.is_empty()
308    }
309}
310
311impl From<Vec<u8>> for ByteArray {
312    fn from(v: Vec<u8>) -> Self {
313        Self(v)
314    }
315}
316
317impl From<&[u8]> for ByteArray {
318    fn from(v: &[u8]) -> Self {
319        Self(v.to_vec())
320    }
321}
322
323#[cfg(test)]
324mod test {
325    use super::*;
326
327    #[test]
328    fn test_type_from_id() {
329        assert_eq!(AMQPType::from_id('T'), Some(AMQPType::Timestamp));
330        assert_eq!(AMQPType::from_id('S'), Some(AMQPType::LongString));
331        assert_eq!(AMQPType::from_id('s'), Some(AMQPType::ShortInt));
332        assert_eq!(AMQPType::from_id('U'), Some(AMQPType::ShortInt));
333        assert_eq!(AMQPType::from_id('l'), Some(AMQPType::LongLongInt));
334        assert_eq!(AMQPType::from_id('z'), None);
335    }
336
337    #[test]
338    fn test_type_get_id() {
339        assert_eq!(AMQPType::LongLongInt.get_id(), 'l');
340        assert_eq!(AMQPType::LongLongUInt.get_id(), 'l');
341        assert_eq!(AMQPType::ShortString.get_id(), '_');
342    }
343
344    #[test]
345    fn test_type_to_string() {
346        assert_eq!(AMQPType::Boolean.to_string(), "Boolean");
347        assert_eq!(AMQPType::Void.to_string(), "Void");
348    }
349
350    #[test]
351    fn long_string_ergonomics() {
352        let str_ref = "string ref";
353        let str_owned = "string owned".to_owned();
354        let vec = b"bytes".to_vec();
355        let array = b"bytes".to_owned();
356        let slice = &b"bytes"[..];
357
358        let from_str_ref: LongString = str_ref.into();
359        let from_str_owned: LongString = str_owned.clone().into();
360        let from_vec: LongString = vec.clone().into();
361        let from_array: LongString = array.into();
362        let from_slice: LongString = slice.into();
363
364        for (left, right) in [
365            (str_ref.as_bytes(), from_str_ref.as_bytes()),
366            (str_owned.as_bytes(), from_str_owned.as_bytes()),
367            (vec.as_ref(), from_vec.as_bytes()),
368            (array.as_ref(), from_array.as_bytes()),
369            (slice, from_slice.as_bytes()),
370        ] {
371            assert_eq!(left, right);
372        }
373    }
374}