use bytes::{Bytes, BytesMut};
use crate::amqp_composite;
use crate::codec::described::descriptors;
use crate::codec::{Decode, DecodeError, Encode, OrderedMap, Symbol, Value};
pub type Fields = OrderedMap<Symbol, Value>;
pub type IetfLanguageTag = Symbol;
pub type Milliseconds = u32;
pub type Seconds = u32;
pub type Handle = u32;
pub type DeliveryNumber = u32;
pub type SequenceNo = u32;
pub type TransferNumber = SequenceNo;
pub type DeliveryTag = Bytes;
pub type MessageFormat = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Role {
#[default]
Sender,
Receiver,
}
impl Encode for Role {
fn encode(&self, buf: &mut BytesMut) {
matches!(self, Role::Receiver).encode(buf)
}
}
impl Decode for Role {
fn decode(buf: &mut Bytes) -> Result<Self, DecodeError> {
Ok(if bool::decode(buf)? {
Role::Receiver
} else {
Role::Sender
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum SenderSettleMode {
Unsettled = 0,
Settled = 1,
#[default]
Mixed = 2,
}
impl Encode for SenderSettleMode {
fn encode(&self, buf: &mut BytesMut) {
(*self as u8).encode(buf)
}
}
impl Decode for SenderSettleMode {
fn decode(buf: &mut Bytes) -> Result<Self, DecodeError> {
match u8::decode(buf)? {
0 => Ok(SenderSettleMode::Unsettled),
1 => Ok(SenderSettleMode::Settled),
2 => Ok(SenderSettleMode::Mixed),
n => Err(DecodeError::InvalidValue(format!(
"invalid sender-settle-mode {n}"
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum ReceiverSettleMode {
#[default]
First = 0,
Second = 1,
}
impl Encode for ReceiverSettleMode {
fn encode(&self, buf: &mut BytesMut) {
(*self as u8).encode(buf)
}
}
impl Decode for ReceiverSettleMode {
fn decode(buf: &mut Bytes) -> Result<Self, DecodeError> {
match u8::decode(buf)? {
0 => Ok(ReceiverSettleMode::First),
1 => Ok(ReceiverSettleMode::Second),
n => Err(DecodeError::InvalidValue(format!(
"invalid receiver-settle-mode {n}"
))),
}
}
}
macro_rules! condition_enum {
($(#[$m:meta])* $name:ident { $( $variant:ident => $sym:literal ),* $(,)? } default $def:ident) => {
$(#[$m])*
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum $name { $( $variant ),* }
impl $name {
pub fn as_str(&self) -> &'static str {
match self { $( $name::$variant => $sym ),* }
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s { $( $sym => Some($name::$variant), )* _ => None }
}
}
impl Default for $name {
fn default() -> Self { $name::$def }
}
};
}
condition_enum! {
AmqpError {
InternalError => "amqp:internal-error",
NotFound => "amqp:not-found",
UnauthorizedAccess => "amqp:unauthorized-access",
DecodeError => "amqp:decode-error",
ResourceLimitExceeded => "amqp:resource-limit-exceeded",
NotAllowed => "amqp:not-allowed",
InvalidField => "amqp:invalid-field",
NotImplemented => "amqp:not-implemented",
ResourceLocked => "amqp:resource-locked",
PreconditionFailed => "amqp:precondition-failed",
ResourceDeleted => "amqp:resource-deleted",
IllegalState => "amqp:illegal-state",
FrameSizeTooSmall => "amqp:frame-size-too-small",
} default InternalError
}
condition_enum! {
ConnectionError {
ConnectionForced => "amqp:connection:forced",
FramingError => "amqp:connection:framing-error",
Redirect => "amqp:connection:redirect",
} default ConnectionForced
}
condition_enum! {
SessionError {
WindowViolation => "amqp:session:window-violation",
ErrantLink => "amqp:session:errant-link",
HandleInUse => "amqp:session:handle-in-use",
UnattachedHandle => "amqp:session:unattached-handle",
} default WindowViolation
}
condition_enum! {
LinkError {
DetachForced => "amqp:link:detach-forced",
TransferLimitExceeded => "amqp:link:transfer-limit-exceeded",
MessageSizeExceeded => "amqp:link:message-size-exceeded",
Redirect => "amqp:link:redirect",
Stolen => "amqp:link:stolen",
} default DetachForced
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorCondition {
Amqp(AmqpError),
Connection(ConnectionError),
Session(SessionError),
Link(LinkError),
Custom(Symbol),
}
impl ErrorCondition {
pub fn as_str(&self) -> &str {
match self {
ErrorCondition::Amqp(e) => e.as_str(),
ErrorCondition::Connection(e) => e.as_str(),
ErrorCondition::Session(e) => e.as_str(),
ErrorCondition::Link(e) => e.as_str(),
ErrorCondition::Custom(s) => s.as_str(),
}
}
fn from_symbol(s: Symbol) -> Self {
let st = s.as_str();
if let Some(e) = AmqpError::from_str(st) {
ErrorCondition::Amqp(e)
} else if let Some(e) = ConnectionError::from_str(st) {
ErrorCondition::Connection(e)
} else if let Some(e) = SessionError::from_str(st) {
ErrorCondition::Session(e)
} else if let Some(e) = LinkError::from_str(st) {
ErrorCondition::Link(e)
} else {
ErrorCondition::Custom(s)
}
}
}
impl Default for ErrorCondition {
fn default() -> Self {
ErrorCondition::Amqp(AmqpError::InternalError)
}
}
impl From<AmqpError> for ErrorCondition {
fn from(e: AmqpError) -> Self {
ErrorCondition::Amqp(e)
}
}
impl From<ConnectionError> for ErrorCondition {
fn from(e: ConnectionError) -> Self {
ErrorCondition::Connection(e)
}
}
impl From<SessionError> for ErrorCondition {
fn from(e: SessionError) -> Self {
ErrorCondition::Session(e)
}
}
impl From<LinkError> for ErrorCondition {
fn from(e: LinkError) -> Self {
ErrorCondition::Link(e)
}
}
impl Encode for ErrorCondition {
fn encode(&self, buf: &mut BytesMut) {
Symbol::new(self.as_str()).encode(buf)
}
}
impl Decode for ErrorCondition {
fn decode(buf: &mut Bytes) -> Result<Self, DecodeError> {
Ok(ErrorCondition::from_symbol(Symbol::decode(buf)?))
}
}
impl std::fmt::Display for ErrorCondition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
amqp_composite! {
pub struct Error : descriptors::ERROR => {
condition: ErrorCondition = req("condition"),
description: Option<String> = opt(),
info: Option<Fields> = opt(),
}
}
impl Error {
pub fn new(condition: impl Into<ErrorCondition>, description: Option<String>) -> Self {
Error {
condition: condition.into(),
description,
info: None,
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.condition)?;
if let Some(d) = &self.description {
write!(f, ": {d}")?;
}
Ok(())
}
}
impl std::error::Error for Error {}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::{from_slice, to_vec};
#[test]
fn role_and_settle_modes_round_trip() {
for r in [Role::Sender, Role::Receiver] {
assert_eq!(r, from_slice(&to_vec(&r)).unwrap());
}
assert_eq!(to_vec(&Role::Sender), [0x42]);
assert_eq!(to_vec(&Role::Receiver), [0x41]);
for m in [
SenderSettleMode::Unsettled,
SenderSettleMode::Settled,
SenderSettleMode::Mixed,
] {
assert_eq!(m, from_slice(&to_vec(&m)).unwrap());
}
assert_eq!(to_vec(&SenderSettleMode::Mixed), [0x50, 0x02]);
}
#[test]
fn error_round_trips_and_classifies() {
let e = Error::new(
AmqpError::ResourceLimitExceeded,
Some("too many links".into()),
);
let back: Error = from_slice(&to_vec(&e)).unwrap();
assert_eq!(e, back);
assert_eq!(
back.condition,
ErrorCondition::Amqp(AmqpError::ResourceLimitExceeded)
);
let custom = Error::new(ErrorCondition::Custom(Symbol::new("vendor:weird")), None);
let back: Error = from_slice(&to_vec(&custom)).unwrap();
assert_eq!(
back.condition,
ErrorCondition::Custom(Symbol::new("vendor:weird"))
);
}
#[test]
fn error_condition_symbol_mapping() {
assert_eq!(
ErrorCondition::Link(LinkError::DetachForced).as_str(),
"amqp:link:detach-forced"
);
assert_eq!(
ErrorCondition::from_symbol(Symbol::new("amqp:session:handle-in-use")),
ErrorCondition::Session(SessionError::HandleInUse)
);
}
}