pub mod messages {
pub fn missing_mandatory_ie_short(ie_name: &str) -> String {
format!("Missing mandatory {} IE", ie_name)
}
pub fn missing_ie(ie_name: &str) -> String {
format!("Missing {} IE", ie_name)
}
pub fn ie_not_found(ie_name: &str) -> String {
format!("{} IE not found", ie_name)
}
pub fn ie_required(ie_name: &str) -> String {
format!("{} is required", ie_name)
}
pub fn ie_is_mandatory(ie_name: &str) -> String {
format!("{} IE is mandatory", ie_name)
}
pub fn requires_at_least_bytes(ie_name: &str, min_bytes: usize) -> String {
let byte_word = if min_bytes == 1 { "byte" } else { "bytes" };
format!("{} requires at least {} {}", ie_name, min_bytes, byte_word)
}
pub fn payload_too_short(ie_name: &str) -> String {
format!("{} payload too short", ie_name)
}
pub fn payload_too_short_expected(ie_name: &str, min_bytes: usize) -> String {
let byte_word = if min_bytes == 1 { "byte" } else { "bytes" };
format!(
"{} payload too short: expected at least {} {}",
ie_name, min_bytes, byte_word
)
}
pub fn too_short(context: &str) -> String {
format!("{} too short", context)
}
pub fn invalid_length(ie_name: &str, expected: usize, actual: usize) -> String {
format!(
"Invalid {} length: expected at least {} bytes, got {}",
ie_name, expected, actual
)
}
pub fn invalid_value(field_name: &str) -> String {
format!("Invalid {} value", field_name)
}
pub fn invalid_value_reason(field_name: &str, reason: &str) -> String {
format!("Invalid {} value: {}", field_name, reason)
}
pub fn builder_field_required(field_name: &str) -> String {
format!("{} is required", field_name)
}
pub fn builder_missing_field(builder_type: &str, field_name: &str) -> String {
format!(
"Builder {} is missing required field '{}'",
builder_type, field_name
)
}
pub fn zero_length_ie_not_allowed(ie_name: &str, ie_type: u16) -> String {
format!(
"Zero-length IE not allowed for {} (IE type: {}) per 3GPP TS 29.244 R18",
ie_name, ie_type
)
}
pub fn invalid_utf8(ie_name: &str) -> String {
format!("Invalid UTF-8 in {}", ie_name)
}
}
use std::fmt;
use std::io;
#[derive(Debug, Clone, PartialEq)]
pub enum PfcpError {
MissingMandatoryIe {
ie_type: crate::ie::IeType,
message_type: Option<crate::message::MsgType>,
parent_ie: Option<crate::ie::IeType>,
},
IeParseError {
ie_type: crate::ie::IeType,
reason: String,
offset: Option<usize>,
},
InvalidLength {
ie_name: String,
ie_type: crate::ie::IeType,
expected: usize,
actual: usize,
},
InvalidValue {
field: String,
value: String,
reason: String,
},
ValidationError {
builder: String,
field: String,
reason: String,
},
EncodingError {
ie_name: String,
ie_type: crate::ie::IeType,
source: std::str::Utf8Error,
},
ZeroLengthNotAllowed { ie_name: String, ie_type: u16 },
MessageParseError {
message_type: Option<crate::message::MsgType>,
reason: String,
},
IoError {
kind: io::ErrorKind,
message: String,
},
}
impl fmt::Display for PfcpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PfcpError::MissingMandatoryIe {
ie_type,
message_type,
parent_ie,
} => {
let ie_name = format!("{:?}", ie_type);
if let Some(msg_type) = message_type {
write!(
f,
"{} in {:?}",
messages::missing_mandatory_ie_short(&ie_name),
msg_type
)
} else if let Some(parent) = parent_ie {
write!(
f,
"{} in grouped IE {:?}",
messages::missing_mandatory_ie_short(&ie_name),
parent
)
} else {
write!(f, "{}", messages::missing_mandatory_ie_short(&ie_name))
}
}
PfcpError::IeParseError {
ie_type,
reason,
offset,
} => {
if let Some(off) = offset {
write!(
f,
"Failed to parse {:?} at offset {}: {}",
ie_type, off, reason
)
} else {
write!(f, "Failed to parse {:?}: {}", ie_type, reason)
}
}
PfcpError::InvalidLength {
ie_name,
expected,
actual,
..
} => {
write!(
f,
"{}",
messages::invalid_length(ie_name, *expected, *actual)
)
}
PfcpError::InvalidValue {
field,
value,
reason,
} => {
write!(f, "Invalid {} value '{}': {}", field, value, reason)
}
PfcpError::ValidationError {
builder,
field,
reason,
} => {
write!(
f,
"Validation failed for {}: field '{}' - {}",
builder, field, reason
)
}
PfcpError::EncodingError { ie_name, .. } => {
write!(f, "{}", messages::invalid_utf8(ie_name))
}
PfcpError::ZeroLengthNotAllowed { ie_name, ie_type } => {
write!(
f,
"{}",
messages::zero_length_ie_not_allowed(ie_name, *ie_type)
)
}
PfcpError::MessageParseError {
message_type,
reason,
} => {
if let Some(msg_type) = message_type {
write!(f, "Failed to parse {:?}: {}", msg_type, reason)
} else {
write!(f, "Failed to parse PFCP message: {}", reason)
}
}
PfcpError::IoError { kind, message } => {
write!(f, "I/O error ({:?}): {}", kind, message)
}
}
}
}
impl std::error::Error for PfcpError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PfcpError::EncodingError { source, .. } => Some(source),
_ => None,
}
}
}
impl From<io::Error> for PfcpError {
fn from(err: io::Error) -> Self {
PfcpError::IoError {
kind: err.kind(),
message: err.to_string(),
}
}
}
impl From<std::str::Utf8Error> for PfcpError {
fn from(source: std::str::Utf8Error) -> Self {
PfcpError::EncodingError {
ie_name: "Unknown IE".to_string(),
ie_type: crate::ie::IeType::CreatePdr, source,
}
}
}
impl PfcpError {
pub fn missing_ie(ie_type: crate::ie::IeType) -> Self {
PfcpError::MissingMandatoryIe {
ie_type,
message_type: None,
parent_ie: None,
}
}
pub fn missing_ie_in_message(
ie_type: crate::ie::IeType,
message_type: crate::message::MsgType,
) -> Self {
PfcpError::MissingMandatoryIe {
ie_type,
message_type: Some(message_type),
parent_ie: None,
}
}
pub fn missing_ie_in_grouped(ie_type: crate::ie::IeType, parent_ie: crate::ie::IeType) -> Self {
PfcpError::MissingMandatoryIe {
ie_type,
message_type: None,
parent_ie: Some(parent_ie),
}
}
pub fn parse_error(ie_type: crate::ie::IeType, reason: impl Into<String>) -> Self {
PfcpError::IeParseError {
ie_type,
reason: reason.into(),
offset: None,
}
}
pub fn parse_error_at(
ie_type: crate::ie::IeType,
reason: impl Into<String>,
offset: usize,
) -> Self {
PfcpError::IeParseError {
ie_type,
reason: reason.into(),
offset: Some(offset),
}
}
pub fn invalid_length(
ie_name: impl Into<String>,
ie_type: crate::ie::IeType,
expected: usize,
actual: usize,
) -> Self {
PfcpError::InvalidLength {
ie_name: ie_name.into(),
ie_type,
expected,
actual,
}
}
pub fn invalid_value(
field: impl Into<String>,
value: impl Into<String>,
reason: impl Into<String>,
) -> Self {
PfcpError::InvalidValue {
field: field.into(),
value: value.into(),
reason: reason.into(),
}
}
pub fn validation_error(
builder: impl Into<String>,
field: impl Into<String>,
reason: impl Into<String>,
) -> Self {
PfcpError::ValidationError {
builder: builder.into(),
field: field.into(),
reason: reason.into(),
}
}
pub fn zero_length_not_allowed(ie_name: impl Into<String>, ie_type: u16) -> Self {
PfcpError::ZeroLengthNotAllowed {
ie_name: ie_name.into(),
ie_type,
}
}
pub fn message_parse_error(reason: impl Into<String>) -> Self {
PfcpError::MessageParseError {
message_type: None,
reason: reason.into(),
}
}
pub fn encoding_error(
ie_name: impl Into<String>,
ie_type: crate::ie::IeType,
source: std::str::Utf8Error,
) -> Self {
PfcpError::EncodingError {
ie_name: ie_name.into(),
ie_type,
source,
}
}
pub fn to_cause_code(&self) -> crate::ie::cause::CauseValue {
use crate::ie::cause::CauseValue;
match self {
PfcpError::MissingMandatoryIe { .. } => CauseValue::MandatoryIeMissing,
PfcpError::IeParseError { .. } => CauseValue::MandatoryIeIncorrect,
PfcpError::InvalidLength { .. } => CauseValue::InvalidLength,
PfcpError::InvalidValue { .. } => CauseValue::MandatoryIeIncorrect,
PfcpError::ValidationError { .. } => CauseValue::RuleCreationModificationFailure,
PfcpError::EncodingError { .. } => CauseValue::MandatoryIeIncorrect,
PfcpError::ZeroLengthNotAllowed { .. } => CauseValue::InvalidLength,
PfcpError::MessageParseError { .. } => CauseValue::RequestRejected,
PfcpError::IoError { .. } => CauseValue::SystemFailure,
}
}
}
#[cfg(test)]
mod tests {
use super::messages;
#[test]
fn test_missing_ie_messages() {
assert_eq!(
messages::missing_mandatory_ie_short("PDR ID"),
"Missing mandatory PDR ID IE"
);
assert_eq!(messages::missing_ie("Node ID"), "Missing Node ID IE");
assert_eq!(messages::ie_not_found("F-SEID"), "F-SEID IE not found");
assert_eq!(messages::ie_required("Cause"), "Cause is required");
assert_eq!(
messages::ie_is_mandatory("Node ID"),
"Node ID IE is mandatory"
);
}
#[test]
fn test_length_error_messages() {
assert_eq!(
messages::requires_at_least_bytes("PDR ID", 2),
"PDR ID requires at least 2 bytes"
);
assert_eq!(
messages::requires_at_least_bytes("Cause", 1),
"Cause requires at least 1 byte"
);
assert_eq!(
messages::payload_too_short("Reporting Triggers"),
"Reporting Triggers payload too short"
);
assert_eq!(
messages::payload_too_short_expected("Report Type", 1),
"Report Type payload too short: expected at least 1 byte"
);
assert_eq!(messages::too_short("Header"), "Header too short");
assert_eq!(
messages::invalid_length("F-TEID", 9, 5),
"Invalid F-TEID length: expected at least 9 bytes, got 5"
);
}
#[test]
fn test_invalid_value_messages() {
assert_eq!(messages::invalid_value("DSCP"), "Invalid DSCP value");
assert_eq!(
messages::invalid_value_reason("gate status", "must be 0-3"),
"Invalid gate status value: must be 0-3"
);
}
#[test]
fn test_builder_error_messages() {
assert_eq!(
messages::builder_field_required("pdr_id"),
"pdr_id is required"
);
assert_eq!(
messages::builder_missing_field("CreatePdrBuilder", "pdr_id"),
"Builder CreatePdrBuilder is missing required field 'pdr_id'"
);
}
#[test]
fn test_security_error_messages() {
assert_eq!(
messages::zero_length_ie_not_allowed("F-TEID", 21),
"Zero-length IE not allowed for F-TEID (IE type: 21) per 3GPP TS 29.244 R18"
);
}
#[test]
fn test_utf8_error_messages() {
assert_eq!(
messages::invalid_utf8("Application ID"),
"Invalid UTF-8 in Application ID"
);
}
#[test]
fn test_byte_pluralization() {
assert_eq!(
messages::requires_at_least_bytes("Test", 1),
"Test requires at least 1 byte"
);
assert_eq!(
messages::payload_too_short_expected("Test", 1),
"Test payload too short: expected at least 1 byte"
);
assert_eq!(
messages::requires_at_least_bytes("Test", 2),
"Test requires at least 2 bytes"
);
assert_eq!(
messages::payload_too_short_expected("Test", 10),
"Test payload too short: expected at least 10 bytes"
);
}
use super::PfcpError;
use std::error::Error;
use std::io;
#[test]
fn test_pfcp_error_missing_mandatory_ie() {
let err = PfcpError::missing_ie(crate::ie::IeType::PdrId);
assert!(matches!(
err,
PfcpError::MissingMandatoryIe {
ie_type: crate::ie::IeType::PdrId,
message_type: None,
parent_ie: None,
}
));
let display = format!("{}", err);
assert!(display.contains("Missing mandatory"));
assert!(display.contains("PdrId"));
}
#[test]
fn test_pfcp_error_missing_ie_in_message() {
let err = PfcpError::missing_ie_in_message(
crate::ie::IeType::NodeId,
crate::message::MsgType::HeartbeatRequest,
);
assert!(matches!(
err,
PfcpError::MissingMandatoryIe {
ie_type: crate::ie::IeType::NodeId,
message_type: Some(crate::message::MsgType::HeartbeatRequest),
parent_ie: None,
}
));
let display = format!("{}", err);
assert!(display.contains("Missing mandatory"));
assert!(display.contains("NodeId"));
assert!(display.contains("HeartbeatRequest"));
}
#[test]
fn test_pfcp_error_missing_ie_in_grouped() {
let err = PfcpError::missing_ie_in_grouped(
crate::ie::IeType::PdrId,
crate::ie::IeType::CreatePdr,
);
assert!(matches!(
err,
PfcpError::MissingMandatoryIe {
ie_type: crate::ie::IeType::PdrId,
message_type: None,
parent_ie: Some(crate::ie::IeType::CreatePdr),
}
));
let display = format!("{}", err);
assert!(display.contains("Missing mandatory"));
assert!(display.contains("PdrId"));
assert!(display.contains("CreatePdr"));
}
#[test]
fn test_pfcp_error_parse_error() {
let err = PfcpError::parse_error(crate::ie::IeType::Fteid, "Invalid flags");
assert!(matches!(
err,
PfcpError::IeParseError {
ie_type: crate::ie::IeType::Fteid,
offset: None,
..
}
));
let display = format!("{}", err);
assert!(display.contains("Failed to parse"));
assert!(display.contains("Fteid"));
assert!(display.contains("Invalid flags"));
}
#[test]
fn test_pfcp_error_parse_error_at() {
let err = PfcpError::parse_error_at(crate::ie::IeType::CreatePdr, "Bad PDI", 42);
assert!(matches!(
err,
PfcpError::IeParseError {
ie_type: crate::ie::IeType::CreatePdr,
offset: Some(42),
..
}
));
let display = format!("{}", err);
assert!(display.contains("Failed to parse"));
assert!(display.contains("CreatePdr"));
assert!(display.contains("Bad PDI"));
assert!(display.contains("offset 42"));
}
#[test]
fn test_pfcp_error_invalid_length() {
let err = PfcpError::invalid_length("F-TEID", crate::ie::IeType::Fteid, 9, 5);
assert!(matches!(
err,
PfcpError::InvalidLength {
ie_type: crate::ie::IeType::Fteid,
expected: 9,
actual: 5,
..
}
));
let display = format!("{}", err);
assert!(display.contains("Invalid F-TEID length"));
assert!(display.contains("expected at least 9"));
assert!(display.contains("got 5"));
}
#[test]
fn test_pfcp_error_invalid_value() {
let err = PfcpError::invalid_value("gate_status", "5", "must be 0-3");
assert!(matches!(err, PfcpError::InvalidValue { .. }));
let display = format!("{}", err);
assert!(display.contains("Invalid gate_status value"));
assert!(display.contains("must be 0-3"));
}
#[test]
fn test_pfcp_error_validation_error() {
let err = PfcpError::validation_error("CreatePdrBuilder", "pdr_id", "PDR ID is required");
assert!(matches!(err, PfcpError::ValidationError { .. }));
let display = format!("{}", err);
assert!(display.contains("Validation failed"));
assert!(display.contains("CreatePdrBuilder"));
assert!(display.contains("pdr_id"));
assert!(display.contains("PDR ID is required"));
}
#[test]
fn test_pfcp_error_encoding_error() {
let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
let utf8_err = std::str::from_utf8(&invalid_utf8).unwrap_err();
let err = PfcpError::encoding_error(
"Network Instance",
crate::ie::IeType::NetworkInstance,
utf8_err,
);
assert!(matches!(err, PfcpError::EncodingError { .. }));
let display = format!("{}", err);
assert!(display.contains("Invalid UTF-8"));
assert!(display.contains("Network Instance"));
}
#[test]
fn test_pfcp_error_zero_length_not_allowed() {
let err = PfcpError::zero_length_not_allowed("F-TEID", 21);
assert!(matches!(
err,
PfcpError::ZeroLengthNotAllowed { ie_type: 21, .. }
));
let display = format!("{}", err);
assert!(display.contains("Zero-length IE not allowed"));
assert!(display.contains("F-TEID"));
assert!(display.contains("21"));
}
#[test]
fn test_pfcp_error_message_parse_error() {
let err = PfcpError::message_parse_error("Unexpected message type");
assert!(matches!(
err,
PfcpError::MessageParseError {
message_type: None,
..
}
));
let display = format!("{}", err);
assert!(display.contains("Failed to parse PFCP message"));
assert!(display.contains("Unexpected message type"));
}
#[test]
fn test_pfcp_error_from_io_error() {
let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "short read");
let pfcp_err: PfcpError = io_err.into();
assert!(matches!(
pfcp_err,
PfcpError::IoError {
kind: io::ErrorKind::UnexpectedEof,
..
}
));
let display = format!("{}", pfcp_err);
assert!(display.contains("I/O error"));
assert!(display.contains("short read"));
}
#[test]
fn test_pfcp_error_from_utf8_error() {
let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
let utf8_err = std::str::from_utf8(&invalid_utf8).unwrap_err();
let pfcp_err: PfcpError = utf8_err.into();
assert!(matches!(pfcp_err, PfcpError::EncodingError { .. }));
}
#[test]
fn test_pfcp_error_source() {
let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
let utf8_err = std::str::from_utf8(&invalid_utf8).unwrap_err();
let pfcp_err =
PfcpError::encoding_error("Test IE", crate::ie::IeType::NetworkInstance, utf8_err);
assert!(pfcp_err.source().is_some());
let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "test");
let pfcp_err: PfcpError = io_err.into();
assert!(pfcp_err.source().is_none());
let pfcp_err = PfcpError::missing_ie(crate::ie::IeType::PdrId);
assert!(pfcp_err.source().is_none());
let pfcp_err = PfcpError::invalid_value("field", "value", "reason");
assert!(pfcp_err.source().is_none());
}
#[test]
fn test_pfcp_error_clone() {
let err1 = PfcpError::missing_ie(crate::ie::IeType::PdrId);
let err2 = err1.clone();
assert_eq!(err1, err2);
}
#[test]
fn test_pfcp_error_debug() {
let err = PfcpError::invalid_value("test_field", "bad_value", "test reason");
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("InvalidValue"));
assert!(debug_str.contains("test_field"));
assert!(debug_str.contains("bad_value"));
}
#[test]
fn test_pfcp_error_display_all_variants() {
let errors = vec![
PfcpError::missing_ie(crate::ie::IeType::PdrId),
PfcpError::missing_ie_in_message(
crate::ie::IeType::NodeId,
crate::message::MsgType::HeartbeatRequest,
),
PfcpError::missing_ie_in_grouped(
crate::ie::IeType::PdrId,
crate::ie::IeType::CreatePdr,
),
PfcpError::parse_error(crate::ie::IeType::Fteid, "test error"),
PfcpError::parse_error_at(crate::ie::IeType::CreatePdr, "test error", 10),
PfcpError::invalid_length("Test IE", crate::ie::IeType::PdrId, 10, 5),
PfcpError::invalid_value("field", "value", "reason"),
PfcpError::validation_error("Builder", "field", "reason"),
PfcpError::zero_length_not_allowed("IE", 42),
PfcpError::message_parse_error("test error"),
PfcpError::IoError {
kind: io::ErrorKind::InvalidData,
message: "test error".to_string(),
},
];
for err in errors {
let display = format!("{}", err);
assert!(!display.is_empty(), "Error display should not be empty");
assert!(display.len() > 10, "Error display should be descriptive");
}
}
use crate::ie::cause::CauseValue;
#[test]
fn test_to_cause_code_missing_mandatory_ie() {
let err = PfcpError::missing_ie(crate::ie::IeType::PdrId);
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeMissing);
let err = PfcpError::missing_ie_in_message(
crate::ie::IeType::NodeId,
crate::message::MsgType::HeartbeatRequest,
);
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeMissing);
let err = PfcpError::missing_ie_in_grouped(
crate::ie::IeType::PdrId,
crate::ie::IeType::CreatePdr,
);
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeMissing);
}
#[test]
fn test_to_cause_code_ie_parse_error() {
let err = PfcpError::parse_error(crate::ie::IeType::Fteid, "Invalid flags");
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeIncorrect);
let err = PfcpError::parse_error_at(crate::ie::IeType::CreatePdr, "Bad PDI", 42);
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeIncorrect);
}
#[test]
fn test_to_cause_code_invalid_length() {
let err = PfcpError::invalid_length("F-TEID", crate::ie::IeType::Fteid, 9, 5);
assert_eq!(err.to_cause_code(), CauseValue::InvalidLength);
let err = PfcpError::zero_length_not_allowed("F-TEID", 21);
assert_eq!(err.to_cause_code(), CauseValue::InvalidLength);
}
#[test]
fn test_to_cause_code_invalid_value() {
let err = PfcpError::invalid_value("gate_status", "5", "must be 0-3");
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeIncorrect);
}
#[test]
fn test_to_cause_code_validation_error() {
let err = PfcpError::validation_error("CreatePdrBuilder", "pdr_id", "PDR ID is required");
assert_eq!(
err.to_cause_code(),
CauseValue::RuleCreationModificationFailure
);
}
#[test]
fn test_to_cause_code_encoding_error() {
let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
let utf8_err = std::str::from_utf8(&invalid_utf8).unwrap_err();
let err = PfcpError::encoding_error(
"Network Instance",
crate::ie::IeType::NetworkInstance,
utf8_err,
);
assert_eq!(err.to_cause_code(), CauseValue::MandatoryIeIncorrect);
}
#[test]
fn test_to_cause_code_message_parse_error() {
let err = PfcpError::message_parse_error("Unexpected message type");
assert_eq!(err.to_cause_code(), CauseValue::RequestRejected);
}
#[test]
fn test_to_cause_code_io_error() {
let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "short read");
let err: PfcpError = io_err.into();
assert_eq!(err.to_cause_code(), CauseValue::SystemFailure);
}
#[test]
fn test_to_cause_code_all_variants() {
let errors_and_causes = vec![
(
PfcpError::missing_ie(crate::ie::IeType::PdrId),
CauseValue::MandatoryIeMissing,
),
(
PfcpError::parse_error(crate::ie::IeType::Fteid, "test"),
CauseValue::MandatoryIeIncorrect,
),
(
PfcpError::invalid_length("Test", crate::ie::IeType::PdrId, 10, 5),
CauseValue::InvalidLength,
),
(
PfcpError::invalid_value("field", "value", "reason"),
CauseValue::MandatoryIeIncorrect,
),
(
PfcpError::validation_error("Builder", "field", "reason"),
CauseValue::RuleCreationModificationFailure,
),
(
PfcpError::zero_length_not_allowed("IE", 42),
CauseValue::InvalidLength,
),
(
PfcpError::message_parse_error("test"),
CauseValue::RequestRejected,
),
(
PfcpError::IoError {
kind: io::ErrorKind::InvalidData,
message: "test".to_string(),
},
CauseValue::SystemFailure,
),
];
for (error, expected_cause) in errors_and_causes {
assert_eq!(
error.to_cause_code(),
expected_cause,
"Error {:?} should map to cause {:?}",
error,
expected_cause
);
}
}
}