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 cause(&self) -> Option<&str> {
117 self.cause_message.as_deref()
118 }
119
120 pub fn status(&self) -> u16 {
122 self.status
123 }
124}
125
126impl fmt::Display for Error {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 if let Some(ref cause) = self.cause_message {
129 write!(f, "{:?}: {} ({})", self.code, self.message, cause)
130 } else {
131 write!(f, "{:?}: {}", self.code, self.message)
132 }
133 }
134}
135
136impl std::error::Error for Error {
137 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
138 None
140 }
141}
142
143impl Serialize for Error {
145 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146 where
147 S: Serializer,
148 {
149 use serde::ser::SerializeStruct;
150
151 let mut field_count = 3; if self.details.is_some() {
154 field_count += 1;
155 }
156 if self.trace_id.is_some() {
157 field_count += 1;
158 }
159 if self.retry_after.is_some() {
160 field_count += 1;
161 }
162
163 let mut state = serializer.serialize_struct("Error", field_count)?;
164
165 state.serialize_field("code", &self.code)?;
166 state.serialize_field("message", &self.message)?;
167
168 if self.details.is_some() {
169 state.serialize_field("details", &self.details)?;
170 }
171
172 if self.trace_id.is_some() {
173 state.serialize_field("trace_id", &self.trace_id)?;
174 }
175
176 state.serialize_field("retryable", &self.retryable)?;
177
178 if let Some(ref duration) = self.retry_after {
179 let secs = duration.as_secs();
180 let formatted = if secs < 60 {
181 format!("{}s", secs)
182 } else {
183 format!("{}m{}s", secs / 60, secs % 60)
184 };
185 state.serialize_field("retry_after", &formatted)?;
186 }
187
188 state.end()
189 }
190}