use crate::core::{AsxError, ErrorCode, ErrorContext, Result, SessionContext};
use crate::observability::{AsxEvent, AsxProtocol, EventBus, emit_protocol_event};
use crate::reliability::{RetryClass, RetryDecision};
use std::sync::Arc;
pub(crate) fn validate_message_id(
message_id: &str,
context: &'static str,
session: &SessionContext,
) -> Result<()> {
if message_id.trim().is_empty() {
return Err(AsxError::new(
ErrorCode::InvalidInput,
"message_id must not be empty",
ErrorContext::for_session(context, session),
));
}
Ok(())
}
pub(crate) fn validate_signing_credentials(
sign: bool,
has_key: bool,
has_cert: bool,
context: &'static str,
protocol: &'static str,
session: &SessionContext,
) -> Result<()> {
if sign && (!has_key || !has_cert) {
return Err(AsxError::new(
ErrorCode::PolicyViolation,
format!("{protocol} signing requires signing_key_pem and signing_cert_pem"),
ErrorContext::for_session(context, session),
));
}
Ok(())
}
pub(crate) fn validate_encryption_credentials(
encrypt: bool,
has_recipient_cert: bool,
context: &'static str,
protocol: &'static str,
session: &SessionContext,
) -> Result<()> {
if encrypt && !has_recipient_cert {
return Err(AsxError::new(
ErrorCode::PolicyViolation,
format!("{protocol} encryption requires recipient_cert_pem"),
ErrorContext::for_session(context, session),
));
}
Ok(())
}
pub(crate) fn emit_outbound_prepared(
event_bus: &EventBus,
session: &SessionContext,
message_id: &str,
protocol: AsxProtocol,
fail_closed_audit_events: bool,
) -> Result<()> {
emit_protocol_event(
event_bus,
session,
AsxEvent::OutboundPrepared {
message_id: Arc::from(message_id),
protocol,
},
fail_closed_audit_events,
"send_pipeline_outbound_prepared",
)
}
pub(crate) fn emit_message_signed(
event_bus: &EventBus,
session: &SessionContext,
message_id: &str,
fail_closed_audit_events: bool,
) -> Result<()> {
emit_protocol_event(
event_bus,
session,
AsxEvent::MessageSigned {
message_id: Arc::from(message_id),
},
fail_closed_audit_events,
"send_pipeline_message_signed",
)
}
pub(crate) fn emit_message_encrypted(
event_bus: &EventBus,
session: &SessionContext,
message_id: &str,
fail_closed_audit_events: bool,
) -> Result<()> {
emit_protocol_event(
event_bus,
session,
AsxEvent::MessageEncrypted {
message_id: Arc::from(message_id),
},
fail_closed_audit_events,
"send_pipeline_message_encrypted",
)
}
pub(crate) fn classify_send_retry(err: &AsxError) -> RetryDecision {
let class = match err.code {
ErrorCode::TransportFailure | ErrorCode::CapacityExhausted | ErrorCode::Timeout => {
RetryClass::Transient
}
ErrorCode::ReliabilityFailure => RetryClass::Indeterminate,
ErrorCode::InvalidInput
| ErrorCode::PolicyViolation
| ErrorCode::DecryptionFailed
| ErrorCode::InteropViolation
| ErrorCode::ParseFailed
| ErrorCode::SecurityVerificationFailed
| ErrorCode::NotFound
| ErrorCode::PayloadTooLarge
| ErrorCode::CertificateRevoked
| ErrorCode::CertificateExpired => RetryClass::Permanent,
ErrorCode::StorageBackendFailure => RetryClass::Transient,
};
RetryDecision {
should_retry: matches!(class, RetryClass::Transient | RetryClass::Indeterminate),
class,
}
}
#[cfg(test)]
mod tests {
use super::classify_send_retry;
use crate::core::{AsxError, ErrorCode, ErrorContext};
#[test]
fn classify_send_retry_maps_transient_and_permanent() {
let transient = AsxError::new(
ErrorCode::TransportFailure,
"transport",
ErrorContext::new("test"),
);
let permanent = AsxError::new(
ErrorCode::PolicyViolation,
"policy",
ErrorContext::new("test"),
);
assert!(classify_send_retry(&transient).should_retry);
assert!(!classify_send_retry(&permanent).should_retry);
}
}