use std::{borrow::Cow, error::Error as StdError, fmt, sync::Arc};
use crate::{
diagnostics::DiagnosticsContext,
models::{CosmosResponse, CosmosResponsePayload},
};
pub mod cosmos_status;
pub use cosmos_status::{CosmosStatus, SubStatusCode};
pub(crate) mod backtrace;
pub(crate) use backtrace::Backtrace;
pub use backtrace::{set_backtrace_options, BacktraceOptions};
#[cfg(feature = "__internal_backtrace_bench")]
#[doc(hidden)]
pub use backtrace::__bench as backtrace_bench;
#[derive(Clone)]
pub struct CosmosError {
inner: Arc<CosmosErrorInner>,
}
#[derive(Clone)]
struct CosmosErrorInner {
status: CosmosStatus,
context: ErrorContext,
message: Cow<'static, str>,
source: Option<Arc<dyn StdError + Send + Sync + 'static>>,
backtrace: Option<Backtrace>,
}
#[derive(Clone)]
enum ErrorContext {
Synthetic {
diagnostics: Option<Arc<DiagnosticsContext>>,
},
WirePending { payload: Box<CosmosResponsePayload> },
Wire { response: Box<CosmosResponse> },
}
impl CosmosError {
fn from_inner(mut inner: CosmosErrorInner) -> Self {
if inner.backtrace.is_none() {
let mut cur: Option<&(dyn StdError + 'static)> =
inner.source.as_deref().map(|s| s as _);
for _ in 0..MAX_BACKTRACE_INHERITANCE_DEPTH {
let Some(src) = cur else { break };
if let Some(inner_cosmos) = src.downcast_ref::<CosmosError>() {
inner.backtrace = inner_cosmos.inner.backtrace.clone();
break;
}
cur = src.source();
}
if inner.backtrace.is_none() {
inner.backtrace = Backtrace::capture();
}
}
Self {
inner: Arc::new(inner),
}
}
pub fn status(&self) -> CosmosStatus {
self.inner.status
}
pub fn response(&self) -> Option<&CosmosResponse> {
match &self.inner.context {
ErrorContext::Wire { response } => Some(response),
ErrorContext::WirePending { .. } | ErrorContext::Synthetic { .. } => None,
}
}
pub fn is_from_wire(&self) -> bool {
matches!(&self.inner.context, ErrorContext::Wire { .. })
}
pub fn diagnostics(&self) -> Option<Arc<DiagnosticsContext>> {
match &self.inner.context {
ErrorContext::Wire { response } => Some(response.diagnostics()),
ErrorContext::WirePending { .. } => None,
ErrorContext::Synthetic { diagnostics } => diagnostics.clone(),
}
}
pub(crate) fn diagnostics_ref(&self) -> Option<&Arc<DiagnosticsContext>> {
match &self.inner.context {
ErrorContext::Wire { response } => Some(response.diagnostics_ref()),
ErrorContext::WirePending { .. } => None,
ErrorContext::Synthetic { diagnostics } => diagnostics.as_ref(),
}
}
pub fn backtrace(&self) -> Option<Arc<str>> {
self.inner
.backtrace
.as_ref()
.and_then(Backtrace::rendered)
.cloned()
}
pub(crate) fn wire_payload(&self) -> Option<&CosmosResponsePayload> {
match &self.inner.context {
ErrorContext::WirePending { payload } => Some(payload),
ErrorContext::Wire { response } => Some(response.payload()),
ErrorContext::Synthetic { .. } => None,
}
}
}
impl fmt::Display for CosmosError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_header(f, &self.inner)?;
if f.alternate() {
write_source_chain(f, self, false, true)?;
write_diagnostics(f, self, false, true)?;
write_backtrace(f, self)?;
}
Ok(())
}
}
impl fmt::Debug for CosmosError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let alternate = f.alternate();
write_header(f, &self.inner)?;
write_source_chain(f, self, true, alternate)?;
write_diagnostics(f, self, true, alternate)?;
if alternate {
write_backtrace(f, self)?;
}
Ok(())
}
}
fn write_header(f: &mut fmt::Formatter<'_>, inner: &CosmosErrorInner) -> fmt::Result {
write!(f, "{}: {}", inner.status, inner.message)
}
fn write_source_chain(
f: &mut fmt::Formatter<'_>,
err: &CosmosError,
debug: bool,
alternate: bool,
) -> fmt::Result {
let mut cur: Option<&(dyn StdError + 'static)> = StdError::source(err);
let mut depth = 0;
while let Some(src) = cur {
if depth == 0 {
f.write_str("\n\nCaused by:")?;
}
if depth >= MAX_SOURCE_CHAIN_DEPTH {
write!(
f,
"\n {depth}: ... <source chain truncated at {MAX_SOURCE_CHAIN_DEPTH} frames>"
)?;
break;
}
match (debug, alternate) {
(true, true) => write!(f, "\n {depth}: {src:#?}")?,
(true, false) => write!(f, "\n {depth}: {src:?}")?,
(false, true) => write!(f, "\n {depth}: {src:#}")?,
(false, false) => write!(f, "\n {depth}: {src}")?,
}
cur = src.source();
depth += 1;
}
Ok(())
}
fn write_diagnostics(
f: &mut fmt::Formatter<'_>,
err: &CosmosError,
debug: bool,
alternate: bool,
) -> fmt::Result {
let Some(diag) = err.diagnostics_ref() else {
return Ok(());
};
let diag = diag.as_ref();
f.write_str("\n\nDiagnostics:\n")?;
match (debug, alternate) {
(true, true) => write!(f, "{diag:#?}"),
(true, false) => write!(f, "{diag:?}"),
(false, true) => write!(f, "{diag:#}"),
(false, false) => write!(f, "{diag}"),
}
}
fn write_backtrace(f: &mut fmt::Formatter<'_>, err: &CosmosError) -> fmt::Result {
if let Some(bt) = err.backtrace() {
f.write_str("\n\nStack backtrace:\n")?;
f.write_str(bt.as_ref())?;
}
Ok(())
}
impl StdError for CosmosError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner
.source
.as_deref()
.map(|s| s as &(dyn StdError + 'static))
}
}
const MAX_SOURCE_CHAIN_DEPTH: usize = 64;
const MAX_BACKTRACE_INHERITANCE_DEPTH: usize = 4;
pub type Result<T> = std::result::Result<T, CosmosError>;
impl CosmosError {
pub fn builder() -> CosmosErrorBuilder {
CosmosErrorBuilder::new()
}
}
#[must_use = "CosmosErrorBuilder is inert until `.build()` is called"]
pub struct CosmosErrorBuilder {
base: Option<CosmosError>,
status: Option<CosmosStatus>,
response: Option<CosmosResponse>,
response_parts: Option<Box<CosmosResponsePayload>>,
diagnostics: Option<Arc<DiagnosticsContext>>,
message: Option<Cow<'static, str>>,
source: Option<Arc<dyn StdError + Send + Sync + 'static>>,
context_prefix: Option<Cow<'static, str>>,
}
impl CosmosErrorBuilder {
fn new() -> Self {
Self {
base: None,
status: None,
response: None,
response_parts: None,
diagnostics: None,
message: None,
source: None,
context_prefix: None,
}
}
pub fn from_error(err: CosmosError) -> Self {
Self {
base: Some(err),
status: None,
response: None,
response_parts: None,
diagnostics: None,
message: None,
source: None,
context_prefix: None,
}
}
pub fn with_status(mut self, status: CosmosStatus) -> Self {
self.status = Some(status);
self
}
pub fn with_message(mut self, message: impl Into<Cow<'static, str>>) -> Self {
self.message = Some(message.into());
self
}
pub fn with_source<E>(mut self, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
self.source = Some(Arc::new(source));
self
}
pub fn with_arc_source(mut self, source: Arc<dyn StdError + Send + Sync + 'static>) -> Self {
self.source = Some(source);
self
}
pub fn with_response(mut self, response: CosmosResponse) -> Self {
self.response = Some(response);
self
}
pub fn with_diagnostics(mut self, diagnostics: Arc<DiagnosticsContext>) -> Self {
self.diagnostics = Some(diagnostics);
self
}
pub fn with_context(mut self, context: impl Into<Cow<'static, str>>) -> Self {
self.context_prefix = Some(context.into());
self
}
pub(crate) fn with_response_parts(mut self, payload: CosmosResponsePayload) -> Self {
self.response_parts = Some(Box::new(payload));
self
}
pub fn build(self) -> CosmosError {
let base_status = self.base.as_ref().map(|b| b.inner.status);
let resolved_status = self.status.or(base_status).unwrap_or_else(|| {
CosmosStatus::new(azure_core::http::StatusCode::InternalServerError)
});
let base_context = self.base.as_ref().map(|b| &b.inner.context);
let (status, context) = if let Some(response) = self.response {
let status = response.status();
(
status,
ErrorContext::Wire {
response: Box::new(response),
},
)
} else if let Some(parts) = self.response_parts {
match self.diagnostics {
Some(diag) => {
let payload = *parts;
let response = finalize_response(payload, resolved_status, diag);
let status = response.status();
(
status,
ErrorContext::Wire {
response: Box::new(response),
},
)
}
None => (
resolved_status,
ErrorContext::WirePending { payload: parts },
),
}
} else {
match base_context {
Some(ErrorContext::WirePending { payload }) => match self.diagnostics {
Some(diag) => {
let payload = (**payload).clone();
let response = finalize_response(payload, resolved_status, diag);
let status = response.status();
(
status,
ErrorContext::Wire {
response: Box::new(response),
},
)
}
None => {
let payload = (**payload).clone();
(
resolved_status,
ErrorContext::WirePending {
payload: Box::new(payload),
},
)
}
},
Some(ErrorContext::Wire { response }) => {
let payload = response.payload().clone();
let status = response.status();
let diagnostics = self
.diagnostics
.clone()
.unwrap_or_else(|| response.diagnostics());
let response = finalize_response(payload, status, diagnostics);
(
status,
ErrorContext::Wire {
response: Box::new(response),
},
)
}
Some(ErrorContext::Synthetic {
diagnostics: base_diag,
}) => {
let diagnostics = self.diagnostics.or_else(|| base_diag.clone());
(resolved_status, ErrorContext::Synthetic { diagnostics })
}
None => {
(
resolved_status,
ErrorContext::Synthetic {
diagnostics: self.diagnostics,
},
)
}
}
};
let (mut message, mut source, backtrace) = match &self.base {
Some(base) => (
base.inner.message.clone(),
base.inner.source.clone(),
base.inner.backtrace.clone(),
),
None => (Cow::Borrowed(""), None, None),
};
if let Some(m) = self.message {
message = m;
}
if self.source.is_some() {
source = self.source;
}
if let Some(prefix) = self.context_prefix {
let mut buf = String::with_capacity(prefix.len() + 2 + message.len());
buf.push_str(&prefix);
buf.push_str(": ");
buf.push_str(&message);
message = Cow::Owned(buf);
}
CosmosError::from_inner(CosmosErrorInner {
status,
context,
message,
source,
backtrace,
})
}
}
fn finalize_response(
payload: CosmosResponsePayload,
status: CosmosStatus,
diagnostics: Arc<DiagnosticsContext>,
) -> CosmosResponse {
let (body, headers) = (payload.body().clone(), payload.headers().clone());
CosmosResponse::new(body, headers, status, diagnostics)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{CosmosResponseHeaders, ResponseBody};
use azure_core::http::StatusCode;
use std::sync::Mutex;
static BACKTRACE_TEST_LOCK: &Mutex<()> = &crate::error::backtrace::tests::TEST_LOCK;
fn make_test_diagnostics() -> Arc<DiagnosticsContext> {
use crate::diagnostics::DiagnosticsContextBuilder;
use crate::models::ActivityId;
use crate::options::DiagnosticsOptions;
Arc::new(
DiagnosticsContextBuilder::new(
ActivityId::new_uuid(),
Arc::new(DiagnosticsOptions::default()),
)
.complete(),
)
}
fn make_test_response(
status: CosmosStatus,
diagnostics: Arc<DiagnosticsContext>,
) -> CosmosResponse {
CosmosResponse::new(
ResponseBody::NoPayload,
CosmosResponseHeaders::default(),
status,
diagnostics,
)
}
fn make_test_payload() -> CosmosResponsePayload {
CosmosResponsePayload::new(b"{\"x\":1}".to_vec(), CosmosResponseHeaders::default())
}
#[test]
fn builder_default_status_is_internal_server_error() {
let err = CosmosError::builder().with_message("m").build();
assert_eq!(err.status().status_code(), StatusCode::InternalServerError);
assert_eq!(format!("{err}").split(": ").last().unwrap(), "m");
assert!(err.response().is_none());
}
#[test]
fn builder_with_status_is_preserved_verbatim() {
let err = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::ServiceUnavailable))
.with_message("nope")
.build();
assert_eq!(err.status().status_code(), StatusCode::ServiceUnavailable);
}
#[test]
fn builder_with_source_preserves_via_std_error_source() {
let io = std::io::Error::new(std::io::ErrorKind::Other, "underlying");
let err = CosmosError::builder()
.with_message("wrapped")
.with_source(io)
.build();
let src = StdError::source(&err).expect("source preserved");
assert!(src.to_string().contains("underlying"));
}
#[test]
fn builder_with_arc_source_accepts_shared_handle() {
let inner = Arc::new(CosmosError::builder().with_message("inner").build())
as Arc<dyn StdError + Send + Sync + 'static>;
let outer = CosmosError::builder()
.with_arc_source(inner)
.with_message("outer")
.build();
let src = StdError::source(&outer).expect("source preserved");
assert!(src.to_string().contains("inner"));
}
#[test]
fn builder_with_diagnostics_attaches_to_synthetic_error() {
let diag = make_test_diagnostics();
let err = CosmosError::builder()
.with_message("m")
.with_diagnostics(Arc::clone(&diag))
.build();
assert!(err.response().is_none());
assert!(Arc::ptr_eq(&err.diagnostics().unwrap(), &diag));
}
#[test]
fn builder_with_response_sets_wire_context_and_wins_status_and_diagnostics() {
let resp_diag = make_test_diagnostics();
let response = make_test_response(
CosmosStatus::new(StatusCode::NotFound),
Arc::clone(&resp_diag),
);
let unrelated_diag = make_test_diagnostics();
let err = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::TooManyRequests)) .with_diagnostics(Arc::clone(&unrelated_diag)) .with_response(response)
.with_message("oh")
.build();
assert_eq!(err.status().status_code(), StatusCode::NotFound);
assert!(Arc::ptr_eq(&err.diagnostics().unwrap(), &resp_diag));
assert!(!Arc::ptr_eq(&err.diagnostics().unwrap(), &unrelated_diag));
let wire = err.response().expect("wire response present");
assert_eq!(wire.status().status_code(), StatusCode::NotFound);
}
#[test]
fn builder_with_response_invariant_chain_holds() {
let response = make_test_response(
CosmosStatus::new(StatusCode::Conflict),
make_test_diagnostics(),
);
let err = CosmosError::builder()
.with_response(response)
.with_message("conflict")
.build();
let s_err = err.status().status_code();
let s_resp = err.response().unwrap().status().status_code();
let s_resp_diag = err
.response()
.unwrap()
.diagnostics_ref()
.status()
.map(|s| s.status_code());
assert_eq!(s_err, s_resp);
if let Some(s) = s_resp_diag {
assert_eq!(s_resp, s);
}
}
#[test]
fn builder_with_response_parts_no_diagnostics_yields_wire_pending() {
let err = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::TooManyRequests))
.with_message("staged")
.with_response_parts(make_test_payload())
.build();
assert!(
err.response().is_none(),
"WirePending must not expose response()"
);
assert!(
err.diagnostics().is_none(),
"WirePending must not expose diagnostics()"
);
assert_eq!(err.status().status_code(), StatusCode::TooManyRequests);
assert!(
err.wire_payload().is_some(),
"internal wire_payload must surface staged parts"
);
}
#[test]
fn builder_with_response_parts_and_diagnostics_promotes_to_wire() {
let diag = make_test_diagnostics();
let err = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::NotFound))
.with_message("not found")
.with_response_parts(make_test_payload())
.with_diagnostics(Arc::clone(&diag))
.build();
let wire = err.response().expect("promotion to Wire");
assert_eq!(wire.status().status_code(), StatusCode::NotFound);
assert!(Arc::ptr_eq(&err.diagnostics().unwrap(), &diag));
assert!(Arc::ptr_eq(wire.diagnostics_ref(), &diag));
}
#[test]
fn from_error_wire_pending_with_diagnostics_promotes_to_wire() {
let staged = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::ServiceUnavailable))
.with_message("attempt-failed")
.with_response_parts(make_test_payload())
.build();
assert!(staged.response().is_none(), "staged must be WirePending");
let diag = make_test_diagnostics();
let finalized = CosmosErrorBuilder::from_error(staged)
.with_diagnostics(Arc::clone(&diag))
.build();
let wire = finalized.response().expect("finalization promoted to Wire");
assert_eq!(wire.status().status_code(), StatusCode::ServiceUnavailable);
assert!(Arc::ptr_eq(&finalized.diagnostics().unwrap(), &diag));
assert!(Arc::ptr_eq(wire.diagnostics_ref(), &diag));
}
#[test]
fn from_error_wire_pending_without_diagnostics_carries_forward() {
let staged = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::ServiceUnavailable))
.with_message("attempt-failed")
.with_response_parts(make_test_payload())
.build();
let decorated = CosmosErrorBuilder::from_error(staged)
.with_context("op=createItem")
.build();
assert!(decorated.response().is_none(), "WirePending preserved");
assert!(decorated.diagnostics().is_none());
assert!(decorated.wire_payload().is_some());
assert_eq!(format!("{decorated}"), "503: op=createItem: attempt-failed",);
}
#[test]
fn from_error_wire_carries_response_forward() {
let diag = make_test_diagnostics();
let response =
make_test_response(CosmosStatus::new(StatusCode::Conflict), Arc::clone(&diag));
let original = CosmosError::builder()
.with_response(response)
.with_message("conflict")
.build();
let decorated = CosmosErrorBuilder::from_error(original)
.with_context("op=replace")
.build();
let wire = decorated.response().expect("Wire carried forward");
assert_eq!(wire.status().status_code(), StatusCode::Conflict);
assert!(Arc::ptr_eq(&decorated.diagnostics().unwrap(), &diag));
}
#[test]
fn from_error_wire_with_diagnostics_overrides_response_diagnostics() {
let original_diag = make_test_diagnostics();
let response = make_test_response(
CosmosStatus::new(StatusCode::PreconditionFailed),
Arc::clone(&original_diag),
);
let original = CosmosError::builder()
.with_response(response)
.with_message("etag mismatch")
.build();
let override_diag = make_test_diagnostics();
let decorated = CosmosErrorBuilder::from_error(original)
.with_diagnostics(Arc::clone(&override_diag))
.with_context("op=patch")
.build();
assert!(
Arc::ptr_eq(&decorated.diagnostics().unwrap(), &override_diag),
"with_diagnostics override must replace the base response's diagnostics"
);
let wire = decorated.response().expect("still Wire after override");
assert!(
Arc::ptr_eq(wire.diagnostics_ref(), &override_diag),
"rebuilt response must carry the override diagnostics, not the original"
);
assert_eq!(wire.status().status_code(), StatusCode::PreconditionFailed);
assert!(!Arc::ptr_eq(wire.diagnostics_ref(), &original_diag));
}
#[test]
fn builder_with_context_prepends_to_message() {
let err = CosmosError::builder()
.with_message("bad payload")
.with_context("op=createItem")
.build();
assert_eq!(format!("{err}"), "500: op=createItem: bad payload");
}
#[test]
fn builder_from_error_carries_forward_unset_fields() {
let diag = make_test_diagnostics();
let original = CosmosError::builder()
.with_message("first")
.with_diagnostics(Arc::clone(&diag))
.build();
let cloned = CosmosErrorBuilder::from_error(original.clone()).build();
assert_eq!(
cloned.status().status_code(),
original.status().status_code()
);
assert_eq!(format!("{cloned}"), format!("{original}"));
assert!(Arc::ptr_eq(&cloned.diagnostics().unwrap(), &diag));
}
#[test]
fn builder_message_setter_overrides_base_message() {
let original = CosmosError::builder().with_message("orig").build();
let patched = CosmosErrorBuilder::from_error(original)
.with_message("replaced")
.build();
assert_eq!(format!("{patched}"), "500: replaced");
}
#[test]
fn builder_repeated_setters_last_write_wins() {
let err = CosmosError::builder()
.with_message("first")
.with_message("second")
.with_context("ctx-a")
.with_context("ctx-b")
.build();
assert_eq!(format!("{err}"), "500: ctx-b: second");
}
#[test]
fn end_to_end_timeout_uses_synthetic_status() {
let err = CosmosError::builder()
.with_status(CosmosStatus::from_parts(
StatusCode::RequestTimeout,
Some(SubStatusCode::CLIENT_OPERATION_TIMEOUT),
))
.with_message("e2e timeout")
.build();
assert_eq!(err.status().status_code(), StatusCode::RequestTimeout);
assert_eq!(
err.status().sub_status(),
Some(SubStatusCode::CLIENT_OPERATION_TIMEOUT)
);
assert!(err.status().is_timeout());
assert!(err.status().is_transient());
assert!(err.response().is_none());
}
fn end_to_end_timeout_error(message: &'static str) -> CosmosError {
CosmosError::builder()
.with_status(CosmosStatus::from_parts(
StatusCode::RequestTimeout,
Some(SubStatusCode::CLIENT_OPERATION_TIMEOUT),
))
.with_message(message)
.build()
}
#[test]
fn wrap_inherits_backtrace_from_cosmos_source() {
let _guard = BACKTRACE_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let throttle = crate::error::backtrace::global_capture_throttle();
let resolution = crate::error::backtrace::global_resolution_limiter();
let prev_cap = throttle.capacity();
let prev_res = resolution.capacity();
let result = std::panic::catch_unwind(|| {
crate::error::backtrace::set_backtrace_options(
crate::error::backtrace::BacktraceOptions {
max_captures_per_second: 1000,
max_resolutions_per_second: prev_res,
},
);
let inner = end_to_end_timeout_error("inner");
let inner_bt_id = inner
.inner
.backtrace
.as_ref()
.map(crate::error::backtrace::tests::backtrace_inner_arc_identity);
assert!(
inner_bt_id.is_some(),
"inner must have a captured backtrace for this test to be meaningful"
);
let outer = CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_GENERATED_503)
.with_message("outer")
.with_arc_source(Arc::new(inner))
.build();
let outer_bt_id = outer
.inner
.backtrace
.as_ref()
.map(crate::error::backtrace::tests::backtrace_inner_arc_identity);
assert_eq!(
outer_bt_id, inner_bt_id,
"outer error must share the inner's backtrace Arc, not capture a new one"
);
});
crate::error::backtrace::set_backtrace_options(crate::error::backtrace::BacktraceOptions {
max_captures_per_second: prev_cap,
max_resolutions_per_second: prev_res,
});
if let Err(payload) = result {
std::panic::resume_unwind(payload);
}
}
#[derive(Debug)]
struct ThirdPartyWrapper {
source: Arc<dyn StdError + Send + Sync + 'static>,
}
impl fmt::Display for ThirdPartyWrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("third-party wrapper")
}
}
impl StdError for ThirdPartyWrapper {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.source.as_ref())
}
}
#[test]
fn wrap_inherits_backtrace_through_indirect_third_party_wrapper() {
let _guard = BACKTRACE_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let throttle = crate::error::backtrace::global_capture_throttle();
let resolution = crate::error::backtrace::global_resolution_limiter();
let prev_cap = throttle.capacity();
let prev_res = resolution.capacity();
let result = std::panic::catch_unwind(|| {
crate::error::backtrace::set_backtrace_options(
crate::error::backtrace::BacktraceOptions {
max_captures_per_second: 1000,
max_resolutions_per_second: prev_res,
},
);
let inner = end_to_end_timeout_error("deeply nested");
let inner_bt_id = inner
.inner
.backtrace
.as_ref()
.map(crate::error::backtrace::tests::backtrace_inner_arc_identity);
assert!(
inner_bt_id.is_some(),
"inner must have a captured backtrace for this test to be meaningful",
);
let wrapper = ThirdPartyWrapper {
source: Arc::new(inner),
};
let outer = CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_GENERATED_503)
.with_message("outer")
.with_source(wrapper)
.build();
let outer_bt_id = outer
.inner
.backtrace
.as_ref()
.map(crate::error::backtrace::tests::backtrace_inner_arc_identity);
assert_eq!(
outer_bt_id, inner_bt_id,
"outer error must inherit the inner Cosmos backtrace through the third-party wrapper, not capture a fresh one",
);
});
crate::error::backtrace::set_backtrace_options(crate::error::backtrace::BacktraceOptions {
max_captures_per_second: prev_cap,
max_resolutions_per_second: prev_res,
});
if let Err(payload) = result {
std::panic::resume_unwind(payload);
}
}
#[test]
fn wrap_falls_back_to_fresh_capture_when_chain_exceeds_inheritance_depth() {
let _guard = BACKTRACE_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let throttle = crate::error::backtrace::global_capture_throttle();
let resolution = crate::error::backtrace::global_resolution_limiter();
let prev_cap = throttle.capacity();
let prev_res = resolution.capacity();
let result = std::panic::catch_unwind(|| {
crate::error::backtrace::set_backtrace_options(
crate::error::backtrace::BacktraceOptions {
max_captures_per_second: 1000,
max_resolutions_per_second: prev_res,
},
);
let inner = end_to_end_timeout_error("deeply nested");
let inner_bt_id = inner
.inner
.backtrace
.as_ref()
.map(crate::error::backtrace::tests::backtrace_inner_arc_identity);
assert!(inner_bt_id.is_some());
let mut src: Arc<dyn StdError + Send + Sync + 'static> = Arc::new(inner);
for _ in 0..=MAX_BACKTRACE_INHERITANCE_DEPTH {
src = Arc::new(ThirdPartyWrapper {
source: src.clone(),
});
}
let outer = CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_GENERATED_503)
.with_message("outer")
.with_arc_source(src)
.build();
let outer_bt_id = outer
.inner
.backtrace
.as_ref()
.map(crate::error::backtrace::tests::backtrace_inner_arc_identity);
assert!(
outer_bt_id.is_some(),
"fresh capture must succeed when inheritance is bounded out"
);
assert_ne!(
outer_bt_id, inner_bt_id,
"wrap chain deeper than MAX_BACKTRACE_INHERITANCE_DEPTH must NOT inherit; a fresh backtrace must be captured at the wrap site",
);
});
crate::error::backtrace::set_backtrace_options(crate::error::backtrace::BacktraceOptions {
max_captures_per_second: prev_cap,
max_resolutions_per_second: prev_res,
});
if let Err(payload) = result {
std::panic::resume_unwind(payload);
}
}
#[test]
fn backtrace_emission_paths_render_as_documented() {
let _guard = BACKTRACE_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
crate::error::backtrace::ensure_initialized();
let throttle = crate::error::backtrace::global_capture_throttle();
let resolution = crate::error::backtrace::global_resolution_limiter();
let prev_capture = throttle.capacity();
let prev_resolution = resolution.capacity();
let result = std::panic::catch_unwind(|| {
throttle.set_capacity(1_000_000);
resolution.set_capacity(1_000_000);
let err = CosmosError::builder().with_message("bt-test").build();
let display = format!("{err}");
let display_alt = format!("{err:#}");
let debug = format!("{err:?}");
let debug_alt = format!("{err:#?}");
assert_eq!(display, "500: bt-test");
assert_eq!(debug, "500: bt-test");
const ALT_PREFIX: &str = "500: bt-test\n\nStack backtrace:\n";
let display_alt_tail = display_alt.strip_prefix(ALT_PREFIX).unwrap_or_else(|| {
panic!("alternate Display must start with {ALT_PREFIX:?}, got:\n{display_alt}")
});
let debug_alt_tail = debug_alt.strip_prefix(ALT_PREFIX).unwrap_or_else(|| {
panic!("alternate Debug must start with {ALT_PREFIX:?}, got:\n{debug_alt}")
});
assert_eq!(display_alt_tail, debug_alt_tail);
assert_backtrace_tail_shape(
display_alt_tail,
"::error::tests::backtrace_emission_paths_render_as_documented",
);
});
throttle.set_capacity(prev_capture);
resolution.set_capacity(prev_resolution);
if let Err(payload) = result {
std::panic::resume_unwind(payload);
}
}
fn assert_backtrace_tail_shape(tail: &str, required_symbol_substring: &str) {
const AT_INDENT: &str = " at ";
let mut lines = tail.lines().peekable();
let mut frame_index: u32 = 0;
let mut saw_required_symbol = false;
while let Some(line) = lines.next() {
let after_colon = line
.split_once(": ")
.and_then(|(idx_part, sym)| {
let idx: u32 = idx_part.trim_start().parse().ok()?;
Some((idx, sym))
})
.unwrap_or_else(|| {
panic!(
"expected `{frame_index:>4}: <symbol>` symbol line, got: {line:?}\n\
(full tail under inspection:\n{tail})",
)
});
let (idx, symbol) = after_colon;
assert_eq!(
idx, frame_index,
"frame indices must increment by 1; got idx={idx} for expected index {frame_index}\nline: {line:?}",
);
assert!(
!symbol.is_empty(),
"frame {frame_index} has an empty symbol, line: {line:?}",
);
if !required_symbol_substring.is_empty() && symbol.contains(required_symbol_substring) {
saw_required_symbol = true;
}
if let Some(next) = lines.peek() {
if let Some(rest) = next.strip_prefix(AT_INDENT) {
assert!(
!rest.is_empty(),
"`at` line is empty for frame {frame_index}: {next:?}",
);
if let Some((_path, line_no)) = rest.rsplit_once(':') {
if line_no.chars().all(|c| c.is_ascii_digit()) && !line_no.is_empty() {
} else {
}
}
lines.next();
}
}
frame_index += 1;
}
assert!(
frame_index > 0,
"backtrace tail must contain at least one frame, got:\n{tail}",
);
if !required_symbol_substring.is_empty() {
assert!(
saw_required_symbol,
"no frame symbol contained `{required_symbol_substring}` — the \
captured stack does not appear to originate from the call \
site under inspection. Tail under inspection:\n{tail}",
);
}
}
fn make_error_with_diagnostics_and_source() -> CosmosError {
let inner = end_to_end_timeout_error("inner timeout");
CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_GENERATED_503)
.with_message("outer transport failure")
.with_diagnostics(make_test_diagnostics())
.with_arc_source(Arc::new(inner))
.build()
}
#[test]
fn from_error_with_diagnostics_does_not_mutate_original() {
let original = end_to_end_timeout_error("no diags");
assert!(original.diagnostics().is_none());
let diag = make_test_diagnostics();
let attached = CosmosErrorBuilder::from_error(original.clone())
.with_diagnostics(Arc::clone(&diag))
.build();
assert!(
Arc::ptr_eq(
&attached.diagnostics().expect("diagnostics attached"),
&diag
),
"builder must store the supplied diagnostics Arc verbatim"
);
assert!(
original.diagnostics().is_none(),
"original must be untouched by CosmosErrorBuilder::from_error"
);
assert_eq!(
attached.status().status_code(),
original.status().status_code()
);
}
#[test]
fn display_plain_includes_typed_header_and_message_on_one_line() {
let err = make_error_with_diagnostics_and_source();
assert_eq!(
format!("{err}"),
"503/20003 (TransportGenerated503): outer transport failure",
);
}
#[test]
fn display_alternate_includes_header_source_chain_and_diagnostics() {
let err = make_error_with_diagnostics_and_source();
let rendered = format!("{err:#}");
let (prefix, diag_section) = rendered
.split_once("\n\nDiagnostics:\n")
.expect("alternate Display must include a Diagnostics: block");
let header_and_source = "503/20003 (TransportGenerated503): outer transport failure\n\n\
Caused by:\n \
0: 408/20008 (ClientOperationTimeout): inner timeout";
assert!(
prefix.starts_with(header_and_source),
"alternate Display prefix must start with the header+source-chain block, got: {prefix}",
);
let interposed = &prefix[header_and_source.len()..];
assert!(
interposed.is_empty() || interposed.starts_with("\n\nStack backtrace:\n"),
"interposed content between source chain and diagnostics must be empty or a Stack backtrace block, got: {interposed}",
);
assert!(
diag_section.starts_with("activity="),
"Diagnostics section must start with `activity=…`, got: {diag_section}",
);
}
#[test]
fn debug_omits_backtrace_block_in_plain_form() {
let err = make_error_with_diagnostics_and_source();
let rendered = format!("{err:?}");
let (prefix, diag_section) = rendered
.split_once("\n\nDiagnostics:\n")
.expect("plain Debug must include a Diagnostics: block");
assert_eq!(
prefix,
"503/20003 (TransportGenerated503): outer transport failure\n\n\
Caused by:\n \
0: 408/20008 (ClientOperationTimeout): inner timeout",
);
assert!(
diag_section.starts_with("DiagnosticsContext {"),
"Diagnostics section must start with `DiagnosticsContext {{`, got: {diag_section}",
);
assert!(
!rendered.contains("Stack backtrace:"),
"plain Debug must NOT include the backtrace block, got:\n{rendered}",
);
}
#[test]
fn debug_alternate_propagates_to_source_and_diagnostics() {
let err = make_error_with_diagnostics_and_source();
let rendered = format!("{err:#?}");
let (prefix, diag_section) = rendered
.split_once("\n\nDiagnostics:\n")
.expect("alternate Debug must include a Diagnostics: block");
let header_and_source = "503/20003 (TransportGenerated503): outer transport failure\n\n\
Caused by:\n \
0: 408/20008 (ClientOperationTimeout): inner timeout";
assert!(
prefix.starts_with(header_and_source),
"alternate Debug prefix must start with the header+source-chain block, got: {prefix}",
);
let interposed = &prefix[header_and_source.len()..];
assert!(
interposed.is_empty() || interposed.starts_with("\n\nStack backtrace:\n"),
"interposed content between source chain and diagnostics must be empty or a Stack backtrace block, got: {interposed}",
);
assert!(
diag_section.starts_with("DiagnosticsContext {"),
"Diagnostics section must start with `DiagnosticsContext {{`, got: {diag_section}",
);
}
#[test]
fn source_chain_truncation_caps_pathological_chains() {
#[derive(Debug)]
struct CyclicError;
impl std::fmt::Display for CyclicError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("cyclic")
}
}
impl StdError for CyclicError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
static SELF: CyclicError = CyclicError;
Some(&SELF)
}
}
let err = CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_GENERATED_503)
.with_message("outer")
.with_arc_source(Arc::new(CyclicError))
.build();
let rendered = format!("{err:?}");
assert!(
rendered.contains("<source chain truncated"),
"expected truncation marker for cyclic source chain, got:\n{rendered}"
);
assert!(
rendered.len() < 64 * 1024,
"rendered length ({}) suggests unbounded walk",
rendered.len(),
);
}
#[test]
fn wire_response_error_exposes_status_and_substatus_for_sdk_classifier() {
let diag = make_test_diagnostics();
let response = make_test_response(
CosmosStatus::from_parts(
StatusCode::TooManyRequests,
Some(SubStatusCode::THROTTLE_DUE_TO_SPLIT),
),
Arc::clone(&diag),
);
let err = CosmosError::builder()
.with_response(response)
.with_message("throttled")
.build();
assert!(
err.is_from_wire(),
"is_from_wire must return true so the SDK classifier picks HttpResponse"
);
assert_eq!(err.status().status_code(), StatusCode::TooManyRequests);
assert_eq!(
err.status().sub_status(),
Some(SubStatusCode::THROTTLE_DUE_TO_SPLIT),
"sub-status must round-trip to the SDK as `error_code` on the HttpResponse kind"
);
let wire = err.response().expect("wire response present");
assert_eq!(wire.status().status_code(), StatusCode::TooManyRequests);
}
#[test]
fn synthetic_error_reports_not_from_wire_for_sdk_classifier() {
let err = CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_DNS_FAILED)
.with_message("dns failure")
.build();
assert!(!err.is_from_wire());
assert!(err.response().is_none());
assert_eq!(
err.status().sub_status(),
Some(SubStatusCode::TRANSPORT_DNS_FAILED)
);
}
#[test]
fn wire_pending_reports_not_from_wire() {
let err = CosmosError::builder()
.with_status(CosmosStatus::new(StatusCode::TooManyRequests))
.with_message("staged")
.with_response_parts(make_test_payload())
.build();
assert!(err.response().is_none());
assert!(
!err.is_from_wire(),
"WirePending must not advertise is_from_wire()==true; it would lie to the SDK classifier"
);
assert!(
err.wire_payload().is_some(),
"internal accessor must still expose staged parts for in-pipeline finalization"
);
}
}