use std::error::Error;
use std::fmt;
use rocketmq_runtime::RuntimeError;
use super::DeferredCommitError;
use super::ProtocolNoResponseReason;
use super::RemotingResponse;
use super::RequestId;
use super::ResponseOperationalFailure;
use super::WriteProgress;
use crate::contract::TransportContractViolation;
#[must_use]
#[derive(Debug)]
#[non_exhaustive]
pub enum EmbeddedDispatchOutcome {
Reply(RemotingResponse),
OneWay {
request_id: RequestId,
},
Deferred {
request_id: RequestId,
},
NoReply {
request_id: RequestId,
reason: ProtocolNoResponseReason,
},
AdmissionRejected,
IdentityExhausted,
Cancelled,
SessionClosed,
DeadlineExceeded,
CompletionClosed,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub(crate) enum EmbeddedDispatchErrorKind {
Runtime,
RequestConstruction,
ResponseConstruction,
ResponseBinding,
HandlerContract,
DeferredCommit,
OneWayContract,
Response {
operation: &'static str,
progress: WriteProgress,
},
}
pub(crate) struct EmbeddedDispatchError {
kind: EmbeddedDispatchErrorKind,
source: EmbeddedDispatchErrorSource,
}
impl EmbeddedDispatchError {
#[must_use]
#[cfg(test)]
pub const fn kind(&self) -> EmbeddedDispatchErrorKind {
self.kind
}
pub(crate) fn runtime(error: RuntimeError) -> Self {
Self::new(
EmbeddedDispatchErrorKind::Runtime,
EmbeddedDispatchErrorSource::Runtime(error),
)
}
pub(crate) fn request_construction(error: TransportContractViolation) -> Self {
Self::new(
EmbeddedDispatchErrorKind::RequestConstruction,
EmbeddedDispatchErrorSource::Contract(error),
)
}
pub(crate) fn response_construction(error: TransportContractViolation) -> Self {
Self::new(
EmbeddedDispatchErrorKind::ResponseConstruction,
EmbeddedDispatchErrorSource::Contract(error),
)
}
pub(crate) fn response_binding(error: TransportContractViolation) -> Self {
Self::new(
EmbeddedDispatchErrorKind::ResponseBinding,
EmbeddedDispatchErrorSource::Contract(error),
)
}
pub(crate) fn handler_contract(error: TransportContractViolation) -> Self {
Self::new(
EmbeddedDispatchErrorKind::HandlerContract,
EmbeddedDispatchErrorSource::Contract(error),
)
}
pub(crate) fn deferred_commit(error: DeferredCommitError) -> Self {
Self::new(
EmbeddedDispatchErrorKind::DeferredCommit,
EmbeddedDispatchErrorSource::DeferredCommit(error),
)
}
pub(crate) fn one_way_contract(violation: TransportContractViolation) -> Self {
Self::new(
EmbeddedDispatchErrorKind::OneWayContract,
EmbeddedDispatchErrorSource::Contract(violation),
)
}
pub(crate) fn response(error: ResponseOperationalFailure) -> Self {
let kind = EmbeddedDispatchErrorKind::Response {
operation: error.operation(),
progress: error.write_progress(),
};
Self::new(kind, EmbeddedDispatchErrorSource::Response(error))
}
fn new(kind: EmbeddedDispatchErrorKind, source: EmbeddedDispatchErrorSource) -> Self {
Self { kind, source }
}
}
impl fmt::Debug for EmbeddedDispatchError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("EmbeddedDispatchError")
.field("kind", &self.kind)
.finish_non_exhaustive()
}
}
impl fmt::Display for EmbeddedDispatchError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "embedded dispatch failed: {:?}", self.kind)
}
}
impl Error for EmbeddedDispatchError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.source.as_error())
}
}
enum EmbeddedDispatchErrorSource {
Runtime(RuntimeError),
Contract(TransportContractViolation),
DeferredCommit(DeferredCommitError),
Response(ResponseOperationalFailure),
}
impl EmbeddedDispatchErrorSource {
fn as_error(&self) -> &(dyn Error + 'static) {
match self {
Self::Runtime(error) => error,
Self::Contract(error) => error,
Self::DeferredCommit(error) => error,
Self::Response(error) => error,
}
}
}
#[cfg(test)]
mod tests {
use std::io;
use super::*;
#[test]
fn response_operational_failure_retains_typed_source_and_redacts_views() {
let secret = "embedded-sensitive-transport-cause";
let error = EmbeddedDispatchError::response(ResponseOperationalFailure::Transport {
progress: WriteProgress::PossiblyPartial,
source: crate::error_helpers::connection_failed(
crate::error_helpers::TransportStage::Write,
io::Error::other(secret),
),
});
assert_eq!(
error.kind(),
EmbeddedDispatchErrorKind::Response {
operation: "transport",
progress: WriteProgress::PossiblyPartial,
}
);
assert!(
Error::source(&error).is_some_and(|source| source.downcast_ref::<ResponseOperationalFailure>().is_some())
);
assert!(!format!("{error:?}").contains(secret));
assert!(!error.to_string().contains(secret));
}
#[test]
fn one_way_contract_retains_each_closed_outcome_identity() {
for expected in [
TransportContractViolation::OneWayDeferredHandlerOutcome,
TransportContractViolation::OneWayNoReplyHandlerOutcome,
TransportContractViolation::OneWayInvalidRejection,
] {
let error = EmbeddedDispatchError::one_way_contract(expected.clone());
let actual = Error::source(&error)
.and_then(|source| source.downcast_ref::<TransportContractViolation>())
.expect("one-way dispatch error retains its canonical contract source");
assert_eq!(actual, &expected);
assert_eq!(
actual.condition(),
rocketmq_error::CanonicalCondition::FailedPrecondition
);
}
}
}