use std::backtrace::Backtrace;
use std::error::Error;
use std::fmt::{Display, Formatter};
pub type HandlerResult<T> = Result<T, HandlerError>;
#[derive(Debug)]
pub struct HandlerError {
message: String,
error_type: String,
retry_after_seconds: Option<i32>,
stack: Backtrace,
}
impl HandlerError {
pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
Self {
message: message.into(),
error_type: error_type.into(),
retry_after_seconds: None,
stack: Backtrace::force_capture(),
}
}
pub fn from_error<E: Error>(error: E) -> Self {
Self {
message: error.to_string(),
error_type: std::any::type_name::<E>().to_string(),
retry_after_seconds: None,
stack: Backtrace::force_capture(),
}
}
pub fn retry_after(
after_seconds: i32,
error_type: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
message: message.into(),
error_type: error_type.into(),
retry_after_seconds: Some(after_seconds),
stack: Backtrace::force_capture(),
}
}
pub(crate) fn retry_after_seconds(&self) -> i32 {
self.retry_after_seconds.unwrap_or(0)
}
pub(crate) fn invalid_step_result(
flow_type: &str,
step_type: Option<&str>,
method: &str,
detail: impl std::fmt::Display,
) -> Self {
let target = step_type.map_or_else(
|| format!("RPC in Flow {flow_type}"),
|step_type| format!("Flow {flow_type} Step {step_type}"),
);
Self {
message: format!("{target} {method} returned an invalid result: {detail}"),
error_type: "dex_sdk::SdkError::InvalidStepResult".to_string(),
retry_after_seconds: None,
stack: Backtrace::force_capture(),
}
}
pub(crate) fn error_type(&self) -> &str {
&self.error_type
}
pub(crate) fn attach_finalizer_error(&mut self, failure: &Self) {
self.message
.push_str("; buffered Stream finalization failed: ");
self.message.push_str(&failure.message);
}
pub(crate) fn stack_trace(&self) -> &Backtrace {
&self.stack
}
}
impl Display for HandlerError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.message)
}
}
impl Error for HandlerError {}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct SampleDbError;
impl Display for SampleDbError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str("connection refused")
}
}
impl Error for SampleDbError {}
#[test]
fn invalid_step_result_has_stable_context_and_type() {
let error = HandlerError::invalid_step_result(
"OrderFlow",
Some("ApproveOrder"),
"execute",
"missing movement",
);
assert_eq!("dex_sdk::SdkError::InvalidStepResult", error.error_type());
assert!(
error
.to_string()
.contains("Flow OrderFlow Step ApproveOrder")
);
}
#[test]
fn new_stores_explicit_error_type() {
let error = HandlerError::new("PaymentDeclined", "payment failed");
assert_eq!("PaymentDeclined", error.error_type());
assert_eq!("payment failed", error.to_string());
}
#[test]
fn from_error_stores_concrete_type_name() {
let error = HandlerError::from_error(SampleDbError);
assert_eq!(std::any::type_name::<SampleDbError>(), error.error_type());
assert_eq!("connection refused", error.to_string());
}
#[test]
fn new_captures_stack_at_construction_site() {
fn marker_frame() -> HandlerError {
HandlerError::new("TestFailure", "boom")
}
let rendered = format!("{}", marker_frame().stack_trace());
assert!(
rendered.contains("marker_frame"),
"expected construction-site frame, got {rendered}"
);
}
#[test]
fn from_error_captures_stack_at_construction_site() {
fn marker_frame() -> HandlerError {
HandlerError::from_error(SampleDbError)
}
let rendered = format!("{}", marker_frame().stack_trace());
assert!(
rendered.contains("marker_frame"),
"expected construction-site frame, got {rendered}"
);
}
}