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,
11 pub message: String,
13 pub details: Option<serde_json::Value>,
15 pub trace_id: Option<String>,
17 pub retryable: bool,
19
20 pub status: u16,
22 pub retry_after: Option<Duration>,
24
25 cause_message: Option<String>,
26}
27
28impl Error {
29 pub fn new(code: Code, status: u16, message: impl Into<String>) -> Self {
31 let message = message.into();
32 let message = if message.is_empty() {
33 code.default_message().to_string()
34 } else {
35 message
36 };
37
38 let status = if status == 0 {
39 code.default_status()
40 } else {
41 status
42 };
43
44 Self {
45 code,
46 message,
47 details: None,
48 trace_id: None,
49 retryable: code.is_retryable_default(),
50 status,
51 retry_after: None,
52 cause_message: None,
53 }
54 }
55
56 pub fn newf(code: Code, status: u16, message: impl Into<String>) -> Self {
68 Self::new(code, status, message)
69 }
70
71 pub fn wrap(
73 code: Code,
74 status: u16,
75 message: impl Into<String>,
76 cause: impl std::error::Error,
77 ) -> Self {
78 let mut err = Self::new(code, status, message);
79 err.cause_message = Some(cause.to_string());
80 err
81 }
82
83 pub fn with_details(mut self, details: serde_json::Value) -> Self {
85 self.details = Some(details);
86 self
87 }
88
89 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
91 self.trace_id = Some(trace_id.into());
92 self
93 }
94
95 pub fn with_retryable(mut self, retryable: bool) -> Self {
97 self.retryable = retryable;
98 self
99 }
100
101 pub fn with_status(mut self, status: u16) -> Self {
103 if status != 0 {
104 self.status = status;
105 }
106 self
107 }
108
109 pub fn with_retry_after(mut self, duration: Duration) -> Self {
111 self.retry_after = Some(duration);
112 self
113 }
114
115 pub fn with_cause_message(mut self, cause: impl std::error::Error) -> Self {
138 self.cause_message = Some(cause.to_string());
139 self
140 }
141
142 pub fn cause(&self) -> Option<&str> {
144 self.cause_message.as_deref()
145 }
146
147 pub fn status(&self) -> u16 {
149 self.status
150 }
151}
152
153impl fmt::Display for Error {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 if let Some(ref cause) = self.cause_message {
156 write!(f, "{:?}: {} ({})", self.code, self.message, cause)
157 } else {
158 write!(f, "{:?}: {}", self.code, self.message)
159 }
160 }
161}
162
163impl std::error::Error for Error {
164 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
165 None
167 }
168}
169
170impl Serialize for Error {
172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: Serializer,
175 {
176 use serde::ser::SerializeStruct;
177
178 let mut field_count = 3; if self.details.is_some() {
181 field_count += 1;
182 }
183 if self.trace_id.is_some() {
184 field_count += 1;
185 }
186 if self.retry_after.is_some() {
187 field_count += 1;
188 }
189
190 let mut state = serializer.serialize_struct("Error", field_count)?;
191
192 state.serialize_field("code", &self.code)?;
193 state.serialize_field("message", &self.message)?;
194
195 if self.details.is_some() {
196 state.serialize_field("details", &self.details)?;
197 }
198
199 if self.trace_id.is_some() {
200 state.serialize_field("trace_id", &self.trace_id)?;
201 }
202
203 state.serialize_field("retryable", &self.retryable)?;
204
205 if let Some(ref duration) = self.retry_after {
206 let secs = duration.as_secs();
207 let formatted = if secs < 60 {
208 format!("{}s", secs)
209 } else {
210 format!("{}m{}s", secs / 60, secs % 60)
211 };
212 state.serialize_field("retry_after", &formatted)?;
213 }
214
215 state.end()
216 }
217}