1use crate::Code;
2use serde::{Serialize, Serializer};
3use std::fmt;
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
8pub struct Error {
9 pub code: Code,
10 pub message: String,
11 pub details: Option<serde_json::Value>,
12 pub trace_id: Option<String>,
13 pub retryable: bool,
14
15 pub status: u16,
17 pub retry_after: Option<Duration>,
18
19 cause_message: Option<String>,
21}
22
23impl Error {
24 pub fn new(code: Code, status: u16, message: impl Into<String>) -> Self {
26 let message = message.into();
27 let message = if message.is_empty() {
28 code.default_message().to_string()
29 } else {
30 message
31 };
32
33 let status = if status == 0 {
34 code.default_status()
35 } else {
36 status
37 };
38
39 Self {
40 code,
41 message,
42 details: None,
43 trace_id: None,
44 retryable: code.is_retryable_default(),
45 status,
46 retry_after: None,
47 cause_message: None,
48 }
49 }
50
51 pub fn newf(code: Code, status: u16, message: impl Into<String>) -> Self {
63 Self::new(code, status, message)
64 }
65
66 pub fn wrap(
68 code: Code,
69 status: u16,
70 message: impl Into<String>,
71 cause: impl std::error::Error,
72 ) -> Self {
73 let mut err = Self::new(code, status, message);
74 err.cause_message = Some(cause.to_string());
75 err
76 }
77
78 pub fn with_details(mut self, details: serde_json::Value) -> Self {
80 self.details = Some(details);
81 self
82 }
83
84 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
86 self.trace_id = Some(trace_id.into());
87 self
88 }
89
90 pub fn with_retryable(mut self, retryable: bool) -> Self {
92 self.retryable = retryable;
93 self
94 }
95
96 pub fn with_status(mut self, status: u16) -> Self {
98 if status != 0 {
99 self.status = status;
100 }
101 self
102 }
103
104 pub fn with_retry_after(mut self, duration: Duration) -> Self {
106 self.retry_after = Some(duration);
107 self
108 }
109
110 pub fn cause(&self) -> Option<&str> {
112 self.cause_message.as_deref()
113 }
114
115 pub fn status(&self) -> u16 {
117 self.status
118 }
119}
120
121impl fmt::Display for Error {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 if let Some(ref cause) = self.cause_message {
124 write!(f, "{:?}: {} ({})", self.code, self.message, cause)
125 } else {
126 write!(f, "{:?}: {}", self.code, self.message)
127 }
128 }
129}
130
131impl std::error::Error for Error {
132 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
133 None
135 }
136}
137
138impl Serialize for Error {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141 where
142 S: Serializer,
143 {
144 use serde::ser::SerializeStruct;
145
146 let mut field_count = 3; if self.details.is_some() {
149 field_count += 1;
150 }
151 if self.trace_id.is_some() {
152 field_count += 1;
153 }
154 if self.retry_after.is_some() {
155 field_count += 1;
156 }
157
158 let mut state = serializer.serialize_struct("Error", field_count)?;
159
160 state.serialize_field("code", &self.code)?;
161 state.serialize_field("message", &self.message)?;
162
163 if self.details.is_some() {
164 state.serialize_field("details", &self.details)?;
165 }
166
167 if self.trace_id.is_some() {
168 state.serialize_field("trace_id", &self.trace_id)?;
169 }
170
171 state.serialize_field("retryable", &self.retryable)?;
172
173 if let Some(ref duration) = self.retry_after {
174 let secs = duration.as_secs();
175 let formatted = if secs < 60 {
176 format!("{}s", secs)
177 } else {
178 format!("{}m{}s", secs / 60, secs % 60)
179 };
180 state.serialize_field("retry_after", &formatted)?;
181 }
182
183 state.end()
184 }
185}