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/// Maximum byte length of a [ShortString]
150pub const MAX_SHORT_STRING_LENGTH: usize = ShortShortUInt::MAX as usize;
151
152/// Error returned when constructing a [ShortString] from a string that exceeds [MAX_SHORT_STRING_LENGTH]
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct ShortStringError(usize);
155
156impl fmt::Display for ShortStringError {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        write!(
159            f,
160            "ShortString exceeds maximum length of {MAX_SHORT_STRING_LENGTH} bytes (got {})",
161            self.0
162        )
163    }
164}
165
166impl std::error::Error for ShortStringError {}
167
168/// A String (deprecated)
169#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
170pub struct ShortString(String);
171/// A String
172#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
173pub struct LongString(Vec<u8>);
174/// An array of AMQPValue
175#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
176pub struct FieldArray(Vec<AMQPValue>);
177/// A Map<String, AMQPValue>
178#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
179pub struct FieldTable(BTreeMap<ShortString, AMQPValue>);
180/// An array of bytes (RabbitMQ specific)
181#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
182pub struct ByteArray(Vec<u8>);
183
184/// A Decimal value composed of a scale and a value
185#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
186pub struct DecimalValue {
187    /// The scale of the value
188    pub scale: ShortShortUInt,
189    /// The actual value
190    pub value: LongUInt,
191}
192
193impl ShortString {
194    /// Get a reference to a ShortString as &str
195    pub fn as_str(&self) -> &str {
196        self.0.as_str()
197    }
198
199    /// Fallibly construct a [ShortString], returning an error if the string exceeds [MAX_SHORT_STRING_LENGTH] bytes
200    pub fn try_new(s: impl Into<String>) -> Result<Self, ShortStringError> {
201        let s = s.into();
202        if s.len() > MAX_SHORT_STRING_LENGTH {
203            return Err(ShortStringError(s.len()));
204        }
205        Ok(Self(s))
206    }
207}
208
209impl From<String> for ShortString {
210    fn from(s: String) -> Self {
211        Self::try_new(s).expect("ShortString exceeds maximum length")
212    }
213}
214
215impl From<&str> for ShortString {
216    fn from(s: &str) -> Self {
217        Self::try_new(s).expect("ShortString exceeds maximum length")
218    }
219}
220
221impl borrow::Borrow<str> for ShortString {
222    fn borrow(&self) -> &str {
223        self.0.borrow()
224    }
225}
226
227impl fmt::Display for ShortString {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        self.0.fmt(f)
230    }
231}
232
233impl From<ShortString> for String {
234    fn from(value: ShortString) -> Self {
235        value.0
236    }
237}
238
239impl LongString {
240    /// Get a reference to a LongString as &[u8]
241    pub fn as_bytes(&self) -> &[u8] {
242        &self.0[..]
243    }
244
245    /// Get the length of the inner bytes array
246    pub fn len(&self) -> usize {
247        self.0.len()
248    }
249
250    /// Check whether the inner bytes array is empty or not
251    pub fn is_empty(&self) -> bool {
252        self.0.is_empty()
253    }
254}
255
256impl<B> From<B> for LongString
257where
258    B: Into<Vec<u8>>,
259{
260    fn from(bytes: B) -> Self {
261        Self(bytes.into())
262    }
263}
264
265impl fmt::Display for LongString {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        String::from_utf8_lossy(&self.0).fmt(f)
268    }
269}
270
271impl FieldArray {
272    /// Get the inner values as a slice
273    pub fn as_slice(&self) -> &[AMQPValue] {
274        self.0.as_slice()
275    }
276
277    /// Add an item to the array
278    pub fn push(&mut self, v: AMQPValue) {
279        self.0.push(v);
280    }
281}
282
283impl From<Vec<AMQPValue>> for FieldArray {
284    fn from(v: Vec<AMQPValue>) -> Self {
285        Self(v)
286    }
287}
288
289impl FieldTable {
290    /// Insert a new entry in the table
291    pub fn insert(&mut self, k: ShortString, v: AMQPValue) -> &mut Self {
292        self.0.insert(k, v);
293        self
294    }
295
296    /// Check whether the table contains the given key
297    pub fn contains_key(&self, k: &str) -> bool {
298        self.0.contains_key(k)
299    }
300
301    /// Access the inner BTreeMap to perform lookups
302    pub fn inner(&self) -> &BTreeMap<ShortString, AMQPValue> {
303        &self.0
304    }
305}
306
307impl<'a> IntoIterator for &'a FieldTable {
308    type Item = (&'a ShortString, &'a AMQPValue);
309    type IntoIter = btree_map::Iter<'a, ShortString, AMQPValue>;
310
311    fn into_iter(self) -> Self::IntoIter {
312        self.0.iter()
313    }
314}
315
316impl From<BTreeMap<ShortString, AMQPValue>> for FieldTable {
317    fn from(m: BTreeMap<ShortString, AMQPValue>) -> Self {
318        Self(m)
319    }
320}
321
322impl ByteArray {
323    /// Get the inner bytes array as slice
324    pub fn as_slice(&self) -> &[u8] {
325        self.0.as_slice()
326    }
327
328    /// Get the length of the inner bytes array
329    pub fn len(&self) -> usize {
330        self.0.len()
331    }
332
333    /// Check whether the ByteArray is empty
334    pub fn is_empty(&self) -> bool {
335        self.0.is_empty()
336    }
337}
338
339impl From<Vec<u8>> for ByteArray {
340    fn from(v: Vec<u8>) -> Self {
341        Self(v)
342    }
343}
344
345impl From<&[u8]> for ByteArray {
346    fn from(v: &[u8]) -> Self {
347        Self(v.to_vec())
348    }
349}
350
351#[cfg(test)]
352mod test {
353    use super::*;
354
355    #[test]
356    fn test_type_from_id() {
357        assert_eq!(AMQPType::from_id('T'), Some(AMQPType::Timestamp));
358        assert_eq!(AMQPType::from_id('S'), Some(AMQPType::LongString));
359        assert_eq!(AMQPType::from_id('s'), Some(AMQPType::ShortInt));
360        assert_eq!(AMQPType::from_id('U'), Some(AMQPType::ShortInt));
361        assert_eq!(AMQPType::from_id('l'), Some(AMQPType::LongLongInt));
362        assert_eq!(AMQPType::from_id('z'), None);
363    }
364
365    #[test]
366    fn test_type_get_id() {
367        assert_eq!(AMQPType::LongLongInt.get_id(), 'l');
368        assert_eq!(AMQPType::LongLongUInt.get_id(), 'l');
369        assert_eq!(AMQPType::ShortString.get_id(), '_');
370    }
371
372    #[test]
373    fn test_type_to_string() {
374        assert_eq!(AMQPType::Boolean.to_string(), "Boolean");
375        assert_eq!(AMQPType::Void.to_string(), "Void");
376    }
377
378    #[test]
379    fn long_string_ergonomics() {
380        let str_ref = "string ref";
381        let str_owned = "string owned".to_owned();
382        let vec = b"bytes".to_vec();
383        let array = b"bytes".to_owned();
384        let slice = &b"bytes"[..];
385
386        let from_str_ref: LongString = str_ref.into();
387        let from_str_owned: LongString = str_owned.clone().into();
388        let from_vec: LongString = vec.clone().into();
389        let from_array: LongString = array.into();
390        let from_slice: LongString = slice.into();
391
392        for (left, right) in [
393            (str_ref.as_bytes(), from_str_ref.as_bytes()),
394            (str_owned.as_bytes(), from_str_owned.as_bytes()),
395            (vec.as_ref(), from_vec.as_bytes()),
396            (array.as_ref(), from_array.as_bytes()),
397            (slice, from_slice.as_bytes()),
398        ] {
399            assert_eq!(left, right);
400        }
401    }
402
403    #[test]
404    fn short_string_length_limit() {
405        assert!(ShortString::try_new("ok").is_ok());
406        assert!(ShortString::try_new("a".repeat(255)).is_ok());
407        assert_eq!(ShortString::try_new("a".repeat(256)), Err(ShortStringError(256)));
408    }
409}