#amqp #serde

serde_amqp

A serde implementation of AMQP1.0 protocol

27 releases (5 breaking)

Uses new Rust 2021

0.5.6 Nov 23, 2022
0.5.0 Oct 14, 2022
0.1.4 Jul 25, 2022
0.0.6 Mar 19, 2022

#88 in Encoding

Download history 114/week @ 2022-08-14 148/week @ 2022-08-21 171/week @ 2022-08-28 210/week @ 2022-09-04 89/week @ 2022-09-11 255/week @ 2022-09-18 294/week @ 2022-09-25 217/week @ 2022-10-02 437/week @ 2022-10-09 190/week @ 2022-10-16 213/week @ 2022-10-23 166/week @ 2022-10-30 857/week @ 2022-11-06 608/week @ 2022-11-13 955/week @ 2022-11-20 465/week @ 2022-11-27

2,894 downloads per month
Used in 5 crates (3 directly)

MIT/Apache

435KB
11K SLoC

serde_amqp

A serde implementation of AMQP1.0 protocol and the primitive types.

Serializing and deserializing data structures

Any type that implement serde::Serialize and serde::Deserialize trait can be serialized/deserialized with the following convenience functions

Serialization:

  • [to_vec]
  • [serialized_size]

Deserialization:

  • [from_slice]
  • [from_reader]

Primitive types

All primitive types defined in AMQP1.0 protocol can be found in mod [primitives].

Described types

AMQP1.0 specification allows annotating any AMQP type with a [descriptor::Descriptor], thus creating a described type. Though this can be constructed using [described::Described] and [Value], a much easier way to define a custom described type is to use the custom [SerializeComposite] and [DeserializeComposite] derive macros. Please be aware that the "derive" feature flag must be enabled.

You can read more about how to use the derive macros in the corresponding section.

Untyped AMQP1.0 values

Untyped AMQP1.0 values can be constructed as well as serialized/deserialized using [Value].

use serde_amqp::{
    SerializeComposite, DeserializeComposite, Value, to_vec, from_slice,
    described::Described, descriptor::Descriptor,
};

#[derive(Debug, SerializeComposite, DeserializeComposite)]
#[amqp_contract(code = "0x00:0x13", encoding = "list")]
struct Foo(Option<bool>, Option<i32>);

let foo = Foo(Some(true), Some(3));
let buf = to_vec(&foo).unwrap();
let value: Value = from_slice(&buf).unwrap();
let expected = Value::from(
    Described {
        descriptor: Descriptor::Code(0x13),
        value: Value::List(vec![
            Value::Bool(true),
            Value::Int(3)
        ])
    }
);
assert_eq!(value, expected);

Types that implements serde::Serialize and serde::Deserialize traits can also be converted into/from [Value] using the [to_value] and [from_value] functions.

WARNING enum

enum in serde data model can be categorized as below.

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
enum Enumeration {
    UnitVariant,
    NewTypeVariant(u32),
    TupleVariant(bool, u64, String),
    StructVariant { id: u32, is_true: bool },
}

AMQP1.0 protocol doesn't natively support NewTypeVariant, TupleVariant or StructVariant. For the sake of completeness, serialization and deserialization for these variants are implemented as follows:

  • NewTypeVariant is encoded/decoded as a map of one key-value pair with the variant index being the key and the single wrapped field being the value.
  • TupleVariant is encoded/decoded as a map of one key-value pair with the varant index being the key and a list of the fields being the value.
  • StructVariant is encoded/decoded as a map of one key-value pair with the variant index being the key and a list of the fields being the value.

Feature flag

default = []
Feature Description
"derive" enables SerializeComposite and DeserializeComposite
"extensions" enables extensions mod (see Extensions), added since "0.4.5"
"time" enables conversion of Timestamp from/to time::Duration and time::OffsetDateTime, added since "0.5.1"
"chrono" enables conversion of Timestamp from/to chrono::Duration and chrono::DateTime, added since "0.5.1"
"chrono-preview" a temporary feature that removes the use of deprecated APIs in chrono crate
"uuid" enables conversion of Uuid from/to uuid::Uuid, added since "0.5.1"

SerializeComposite and DeserializeComposite

The macro provides three types of encodings:

  1. "list": The struct will be serialized as a described list. A described list is an AMQP1.0 list with its descriptor prepended to the list itself. The deserialization will take either the "list" or the "map" encoded values.
  2. "map": The struct will be serialized as a described map. A described map is an AMQP1.0 map with its descriptor prepended to the map. The deserialization will take either the "list" or the "map" encoded values.
  3. "basic": The struct must be a thin wrapper (containing only one field) over another serializable/deserializable type. The inner struct will be serialized/deserialized with the descriptor prepended to the struct.

Details with the "list" encoding

Optinal fields

If a field is not marked with "mandatory" in the specification, the field can be an Option. During serialization, the optional fields may be skipped completely or encoded as an AMQP1.0 null primitive (0x40). During deserialization, an AMQP1.0 null primitive or an empty field will be decoded as a None.

Fields with default values:

For fields that have default values defined in the specification, the field type must implement both the Default and PartialEq trait. During serialization, if the field is equal to the default value of the field type, the field will be either ignored completely or encoded as an AMQP1.0 null primitive (0x40). During deserialization, an AMQP1.0 null primitive or an empty field will be decoded as the default value of the type.

Example of the derive macros

The "list" encoding will encode the Attach struct as a described list (a descriptor followed by a list of the fields).

/// 2.7.3 Attach
/// Attach a link to a session.
/// <type name="attach" class="composite" source="list" provides="frame">
///     <descriptor name="amqp:attach:list" code="0x00000000:0x00000012"/>
##[derive(Debug, DeserializeComposite, SerializeComposite)]
##[amqp_contract(
    name = "amqp:attach:list",
    code = "0x0000_0000:0x0000_0012",
    encoding = "list",
    rename_all = "kebab-case"
)]
pub struct Attach {
    /// <field name="name" type="string" mandatory="true"/>
    pub name: String,

    /// <field name="handle" type="handle" mandatory="true"/>
    pub handle: Handle,

    /// <field name="role" type="role" mandatory="true"/>
    pub role: Role,

    /// <field name="snd-settle-mode" type="sender-settle-mode" default="mixed"/>
    #[amqp_contract(default)]
    pub snd_settle_mode: SenderSettleMode,

    /// <field name="rcv-settle-mode" type="receiver-settle-mode" default="first"/>
    #[amqp_contract(default)]
    pub rcv_settle_mode: ReceiverSettleMode,

    /// <field name="source" type="*" requires="source"/>
    pub source: Option<Source>,

    /// <field name="target" type="*" requires="target"/>
    pub target: Option<Target>,

    /// <field name="unsettled" type="map"/>
    pub unsettled: Option<BTreeMap<DeliveryTag, DeliveryState>>,

    /// <field name="incomplete-unsettled" type="boolean" default="false"/>
    #[amqp_contract(default)]
    pub incomplete_unsettled: Boolean,

    /// <field name="initial-delivery-count" type="sequence-no"/>
    pub initial_delivery_count: Option<SequenceNo>,

    /// <field name="max-message-size" type="ulong"/>
    pub max_message_size: Option<ULong>,

    /// <field name="offered-capabilities" type="symbol" multiple="true"/>
    pub offered_capabilities: Option<Array<Symbol>>,

    /// <field name="desired-capabilities" type="symbol" multiple="true"/>
    pub desired_capabilities: Option<Array<Symbol>>,

    /// <field name="properties" type="fields"/>
    pub properties: Option<Fields>,
}
```rust

```rust,ignore
/// 3.2.5 Application Properties
/// <type name="application-properties" class="restricted" source="map" provides="section">
///     <descriptor name="amqp:application-properties:map" code="0x00000000:0x00000074"/>
/// </type>
#[derive(Debug, Clone, SerializeComposite, DeserializeComposite)]
#[amqp_contract(
    name = "amqp:application-properties:map",
    code = "0x0000_0000:0x0000_0074",
    encoding = "basic"
)]
pub struct ApplicationProperties(pub OrderedMap<String, SimpleValue>);

Extensions

A raw u64 can be used as the descriptor code in the macro attribute. This is useful for defining types that are not strictly compliant.

#[derive(Debug, Clone, SerializeComposite, DeserializeComposite)]
#[amqp_contract(
    name = "amqp:application-properties:map",
    code = "0x000_0000_0000_0007",
    encoding = "list",
    rename_all = "kebab-case"
)]
pub struct Foo {
   pub bar: String,
}

The following type(s) are provided in the extensions mod and require the extensions feature

  1. TransparentVec - a thin wrapper around Vec that is serialized/deserialized as a sequence of elements Vec is treated as an AMQP List in the core spec

License: MIT/Apache-2.0

Dependencies

~1.1–2.5MB
~49K SLoC