use crate::Code;
use serde::{Serialize, Serializer};
use std::fmt;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct Error {
pub code: Code,
pub message: String,
pub details: Option<serde_json::Value>,
pub trace_id: Option<String>,
pub retryable: bool,
pub status: u16,
pub retry_after: Option<Duration>,
cause_message: Option<String>,
}
impl Error {
pub fn new(code: Code, status: u16, message: impl Into<String>) -> Self {
let message = message.into();
let message = if message.is_empty() {
code.default_message().to_string()
} else {
message
};
let status = if status == 0 {
code.default_status()
} else {
status
};
Self {
code,
message,
details: None,
trace_id: None,
retryable: code.is_retryable_default(),
status,
retry_after: None,
cause_message: None,
}
}
pub fn newf(code: Code, status: u16, message: impl Into<String>) -> Self {
Self::new(code, status, message)
}
pub fn wrap(
code: Code,
status: u16,
message: impl Into<String>,
cause: impl std::error::Error,
) -> Self {
let mut err = Self::new(code, status, message);
err.cause_message = Some(cause.to_string());
err
}
pub fn with_details(mut self, details: serde_json::Value) -> Self {
self.details = Some(details);
self
}
pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
self.trace_id = Some(trace_id.into());
self
}
pub fn with_retryable(mut self, retryable: bool) -> Self {
self.retryable = retryable;
self
}
pub fn with_status(mut self, status: u16) -> Self {
if status != 0 {
self.status = status;
}
self
}
pub fn with_retry_after(mut self, duration: Duration) -> Self {
self.retry_after = Some(duration);
self
}
pub fn with_cause_message(mut self, cause: impl std::error::Error) -> Self {
self.cause_message = Some(cause.to_string());
self
}
pub fn cause(&self) -> Option<&str> {
self.cause_message.as_deref()
}
pub fn status(&self) -> u16 {
self.status
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref cause) = self.cause_message {
write!(f, "{:?}: {} ({})", self.code, self.message, cause)
} else {
write!(f, "{:?}: {}", self.code, self.message)
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeStruct;
let mut field_count = 3; if self.details.is_some() {
field_count += 1;
}
if self.trace_id.is_some() {
field_count += 1;
}
if self.retry_after.is_some() {
field_count += 1;
}
let mut state = serializer.serialize_struct("Error", field_count)?;
state.serialize_field("code", &self.code)?;
state.serialize_field("message", &self.message)?;
if self.details.is_some() {
state.serialize_field("details", &self.details)?;
}
if self.trace_id.is_some() {
state.serialize_field("trace_id", &self.trace_id)?;
}
state.serialize_field("retryable", &self.retryable)?;
if let Some(ref duration) = self.retry_after {
let secs = duration.as_secs();
let formatted = if secs < 60 {
format!("{}s", secs)
} else {
format!("{}m{}s", secs / 60, secs % 60)
};
state.serialize_field("retry_after", &formatted)?;
}
state.end()
}
}