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    #[must_use]
60    pub fn from_id(id: char) -> Option<AMQPType> {
61        match id {
62            't' => Some(AMQPType::Boolean),
63            'b' => Some(AMQPType::ShortShortInt),
64            'B' => Some(AMQPType::ShortShortUInt),
65            /* Specs says 'U', RabbitMQ says 's' (which means ShortString in specs) */
66            's' | 'U' => Some(AMQPType::ShortInt),
67            'u' => Some(AMQPType::ShortUInt),
68            'I' => Some(AMQPType::LongInt),
69            'i' => Some(AMQPType::LongUInt),
70            /* RabbitMQ treats both 'l' and 'L' as LongLongInt and ignores LongLongUInt */
71            'L' | 'l' => Some(AMQPType::LongLongInt),
72            'f' => Some(AMQPType::Float),
73            'd' => Some(AMQPType::Double),
74            'D' => Some(AMQPType::DecimalValue),
75            'S' => Some(AMQPType::LongString),
76            'A' => Some(AMQPType::FieldArray),
77            'T' => Some(AMQPType::Timestamp),
78            'F' => Some(AMQPType::FieldTable),
79            'x' => Some(AMQPType::ByteArray),
80            'V' => Some(AMQPType::Void),
81            _ => None,
82        }
83    }
84
85    /// Get the id from an AMQPType
86    /// We don't strictly follow the spec here but rather the RabbitMQ implementation
87    /// ShortString doesn't have an id, we return '_' instead
88    /// ShortInt is supposed to be 'U' but we use 's'
89    /// LongLongUInt is supposed to be 'L' but we return 'l' as LongLongInt
90    #[must_use]
91    pub fn get_id(self) -> char {
92        match self {
93            AMQPType::Boolean => 't',
94            AMQPType::ShortShortInt => 'b',
95            AMQPType::ShortShortUInt => 'B',
96            /* Specs says 'U', RabbitMQ says 's' (which means ShortString in specs) */
97            AMQPType::ShortInt => 's',
98            AMQPType::ShortUInt => 'u',
99            AMQPType::LongInt => 'I',
100            AMQPType::LongUInt => 'i',
101            /* RabbitMQ treats both 'l' and 'L' as LongLongInt and ignores LongLongUInt */
102            AMQPType::LongLongInt | AMQPType::LongLongUInt => 'l',
103            AMQPType::Float => 'f',
104            AMQPType::Double => 'd',
105            AMQPType::DecimalValue => 'D',
106            /* ShortString only exists for internal usage, we shouldn't ever have to use this */
107            AMQPType::ShortString => '_',
108            AMQPType::LongString => 'S',
109            AMQPType::FieldArray => 'A',
110            AMQPType::Timestamp => 'T',
111            AMQPType::FieldTable => 'F',
112            AMQPType::ByteArray => 'x',
113            AMQPType::Void => 'V',
114        }
115    }
116}
117
118impl fmt::Display for AMQPType {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_fmt(format_args!("{self:?}"))
121    }
122}
123
124/// AMQP boolean (`t`): a single true/false value.
125pub type Boolean = bool;
126/// AMQP short-short-int (`b`): a signed 8-bit integer.
127pub type ShortShortInt = i8;
128/// AMQP short-short-uint (`B`): an unsigned 8-bit integer.
129pub type ShortShortUInt = u8;
130/// AMQP short-int (`U`): a signed 16-bit integer.
131pub type ShortInt = i16;
132/// AMQP short-uint (`u`): an unsigned 16-bit integer.
133pub type ShortUInt = u16;
134/// AMQP long-int (`I`): a signed 32-bit integer.
135pub type LongInt = i32;
136/// AMQP long-uint (`i`): an unsigned 32-bit integer.
137pub type LongUInt = u32;
138/// AMQP long-long-int (`L`): a signed 64-bit integer.
139pub type LongLongInt = i64;
140/// AMQP long-long-uint (`l`): an unsigned 64-bit integer.
141pub type LongLongUInt = u64;
142/// AMQP float (`f`): a 32-bit IEEE 754 floating-point value.
143pub type Float = f32;
144/// AMQP double (`d`): a 64-bit IEEE 754 floating-point value.
145pub type Double = f64;
146/// AMQP timestamp (`T`): a 64-bit POSIX timestamp (seconds since the Unix epoch).
147pub type Timestamp = LongLongUInt;
148/// AMQP void: the absence of a value, equivalent to `()`.
149pub type Void = ();
150
151/// Maximum byte length of a [ShortString]
152pub const MAX_SHORT_STRING_LENGTH: usize = ShortShortUInt::MAX as usize;
153
154/// Error returned when constructing a [ShortString] from a string that exceeds [MAX_SHORT_STRING_LENGTH]
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct ShortStringError(usize);
157
158impl fmt::Display for ShortStringError {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        write!(
161            f,
162            "ShortString exceeds maximum length of {MAX_SHORT_STRING_LENGTH} bytes (got {})",
163            self.0
164        )
165    }
166}
167
168impl std::error::Error for ShortStringError {}
169
170/// A String (deprecated)
171#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
172pub struct ShortString(String);
173/// A String
174#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
175pub struct LongString(Vec<u8>);
176/// An array of AMQPValue
177#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
178pub struct FieldArray(Vec<AMQPValue>);
179/// A Map<String, AMQPValue>
180#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
181pub struct FieldTable(BTreeMap<ShortString, AMQPValue>);
182/// An array of bytes (RabbitMQ specific)
183#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
184pub struct ByteArray(Vec<u8>);
185
186/// A Decimal value composed of a scale and a value
187#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
188pub struct DecimalValue {
189    /// The scale of the value
190    pub scale: ShortShortUInt,
191    /// The actual value
192    pub value: LongUInt,
193}
194
195impl ShortString {
196    /// Get a reference to a ShortString as &str
197    #[must_use]
198    pub fn as_str(&self) -> &str {
199        self.0.as_str()
200    }
201
202    /// Fallibly construct a [ShortString], returning an error if the string exceeds [MAX_SHORT_STRING_LENGTH] bytes
203    pub fn try_new(s: impl Into<String>) -> Result<Self, ShortStringError> {
204        let s = s.into();
205        if s.len() > MAX_SHORT_STRING_LENGTH {
206            return Err(ShortStringError(s.len()));
207        }
208        Ok(Self(s))
209    }
210}
211
212impl From<String> for ShortString {
213    fn from(s: String) -> Self {
214        Self::try_new(s).expect("ShortString exceeds maximum length")
215    }
216}
217
218impl From<&str> for ShortString {
219    fn from(s: &str) -> Self {
220        Self::try_new(s).expect("ShortString exceeds maximum length")
221    }
222}
223
224impl borrow::Borrow<str> for ShortString {
225    fn borrow(&self) -> &str {
226        self.0.borrow()
227    }
228}
229
230impl fmt::Display for ShortString {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        self.0.fmt(f)
233    }
234}
235
236impl From<ShortString> for String {
237    fn from(value: ShortString) -> Self {
238        value.0
239    }
240}
241
242impl LongString {
243    /// Get a reference to a LongString as &[u8]
244    #[must_use]
245    pub fn as_bytes(&self) -> &[u8] {
246        &self.0[..]
247    }
248
249    /// Get the length of the inner bytes array
250    #[must_use]
251    pub fn len(&self) -> usize {
252        self.0.len()
253    }
254
255    /// Check whether the inner bytes array is empty or not
256    #[must_use]
257    pub fn is_empty(&self) -> bool {
258        self.0.is_empty()
259    }
260}
261
262impl<B> From<B> for LongString
263where
264    B: Into<Vec<u8>>,
265{
266    fn from(bytes: B) -> Self {
267        Self(bytes.into())
268    }
269}
270
271impl fmt::Display for LongString {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        String::from_utf8_lossy(&self.0).fmt(f)
274    }
275}
276
277impl FieldArray {
278    /// Get the inner values as a slice
279    #[must_use]
280    pub fn as_slice(&self) -> &[AMQPValue] {
281        self.0.as_slice()
282    }
283
284    /// Add an item to the array
285    pub fn push(&mut self, v: AMQPValue) {
286        self.0.push(v);
287    }
288}
289
290impl From<Vec<AMQPValue>> for FieldArray {
291    fn from(v: Vec<AMQPValue>) -> Self {
292        Self(v)
293    }
294}
295
296impl FieldTable {
297    /// Insert a new entry in the table
298    pub fn insert(&mut self, k: ShortString, v: AMQPValue) -> &mut Self {
299        self.0.insert(k, v);
300        self
301    }
302
303    /// Check whether the table contains the given key
304    #[must_use]
305    pub fn contains_key(&self, k: &str) -> bool {
306        self.0.contains_key(k)
307    }
308
309    /// Access the inner BTreeMap to perform lookups
310    #[must_use]
311    pub fn inner(&self) -> &BTreeMap<ShortString, AMQPValue> {
312        &self.0
313    }
314}
315
316impl<'a> IntoIterator for &'a FieldTable {
317    type Item = (&'a ShortString, &'a AMQPValue);
318    type IntoIter = btree_map::Iter<'a, ShortString, AMQPValue>;
319
320    fn into_iter(self) -> Self::IntoIter {
321        self.0.iter()
322    }
323}
324
325impl From<BTreeMap<ShortString, AMQPValue>> for FieldTable {
326    fn from(m: BTreeMap<ShortString, AMQPValue>) -> Self {
327        Self(m)
328    }
329}
330
331impl ByteArray {
332    /// Get the inner bytes array as slice
333    #[must_use]
334    pub fn as_slice(&self) -> &[u8] {
335        self.0.as_slice()
336    }
337
338    /// Get the length of the inner bytes array
339    #[must_use]
340    pub fn len(&self) -> usize {
341        self.0.len()
342    }
343
344    /// Check whether the ByteArray is empty
345    #[must_use]
346    pub fn is_empty(&self) -> bool {
347        self.0.is_empty()
348    }
349}
350
351impl From<Vec<u8>> for ByteArray {
352    fn from(v: Vec<u8>) -> Self {
353        Self(v)
354    }
355}
356
357impl From<&[u8]> for ByteArray {
358    fn from(v: &[u8]) -> Self {
359        Self(v.to_vec())
360    }
361}
362
363#[cfg(test)]
364mod test {
365    use super::*;
366
367    #[test]
368    fn test_type_from_id() {
369        assert_eq!(AMQPType::from_id('T'), Some(AMQPType::Timestamp));
370        assert_eq!(AMQPType::from_id('S'), Some(AMQPType::LongString));
371        assert_eq!(AMQPType::from_id('s'), Some(AMQPType::ShortInt));
372        assert_eq!(AMQPType::from_id('U'), Some(AMQPType::ShortInt));
373        assert_eq!(AMQPType::from_id('l'), Some(AMQPType::LongLongInt));
374        assert_eq!(AMQPType::from_id('z'), None);
375    }
376
377    #[test]
378    fn test_type_get_id() {
379        assert_eq!(AMQPType::LongLongInt.get_id(), 'l');
380        assert_eq!(AMQPType::LongLongUInt.get_id(), 'l');
381        assert_eq!(AMQPType::ShortString.get_id(), '_');
382    }
383
384    #[test]
385    fn test_type_to_string() {
386        assert_eq!(AMQPType::Boolean.to_string(), "Boolean");
387        assert_eq!(AMQPType::Void.to_string(), "Void");
388    }
389
390    #[test]
391    fn long_string_ergonomics() {
392        let str_ref = "string ref";
393        let str_owned = "string owned".to_owned();
394        let vec = b"bytes".to_vec();
395        let array = b"bytes".to_owned();
396        let slice = &b"bytes"[..];
397
398        let from_str_ref: LongString = str_ref.into();
399        let from_str_owned: LongString = str_owned.clone().into();
400        let from_vec: LongString = vec.clone().into();
401        let from_array: LongString = array.into();
402        let from_slice: LongString = slice.into();
403
404        for (left, right) in [
405            (str_ref.as_bytes(), from_str_ref.as_bytes()),
406            (str_owned.as_bytes(), from_str_owned.as_bytes()),
407            (vec.as_ref(), from_vec.as_bytes()),
408            (array.as_ref(), from_array.as_bytes()),
409            (slice, from_slice.as_bytes()),
410        ] {
411            assert_eq!(left, right);
412        }
413    }
414
415    #[test]
416    fn short_string_length_limit() {
417        assert!(ShortString::try_new("ok").is_ok());
418        assert!(ShortString::try_new("a".repeat(255)).is_ok());
419        assert_eq!(
420            ShortString::try_new("a".repeat(256)),
421            Err(ShortStringError(256))
422        );
423    }
424}