1use std::{fmt::Display, ops::Deref, sync::Arc};
2
3use crate::ErrorWith;
4
5use super::{
6 context::{CallContext, OperationContext},
7 domain::DomainReason,
8 ContextAdd, ErrorCode,
9};
10use thiserror::Error;
11
12#[macro_export]
13macro_rules! location {
14 () => {
15 format!("{}:{}:{}", file!(), line!(), column!())
16 };
17}
18
19pub trait StructErrorTrait<T: DomainReason> {
20 fn get_reason(&self) -> &T;
21 fn get_detail(&self) -> Option<&String>;
22 fn get_target(&self) -> Option<String>;
23}
24
25impl<T: DomainReason + ErrorCode> ErrorCode for StructError<T> {
26 fn error_code(&self) -> i32 {
27 self.reason.error_code()
28 }
29}
30
31#[derive(Error, Debug, Clone, PartialEq)]
34pub struct StructError<T: DomainReason> {
35 imp: Box<StructErrorImpl<T>>,
36}
37
38#[cfg(feature = "serde")]
39impl<T: DomainReason> serde::Serialize for StructError<T>
40where
41 StructErrorImpl<T>: serde::Serialize,
42{
43 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44 where
45 S: serde::Serializer,
46 {
47 self.imp.serialize(serializer)
48 }
49}
50
51impl<T: DomainReason> StructError<T> {
52 pub fn imp(&self) -> &StructErrorImpl<T> {
53 &self.imp
54 }
55}
56
57impl<T: DomainReason> Deref for StructError<T> {
58 type Target = StructErrorImpl<T>;
59
60 fn deref(&self) -> &Self::Target {
61 &self.imp
62 }
63}
64impl<T: DomainReason> StructError<T> {
65 pub fn new(
66 reason: T,
67 detail: Option<String>,
68 position: Option<String>,
69 context: Vec<OperationContext>,
70 ) -> Self {
71 StructError {
72 imp: Box::new(StructErrorImpl {
73 reason,
74 detail,
75 position,
76 context: Arc::new(context),
77 }),
78 }
79 }
80}
81
82impl<T> From<T> for StructError<T>
83where
84 T: DomainReason,
85{
86 fn from(value: T) -> Self {
87 StructError::new(value, None, None, Vec::new())
88 }
89}
90
91#[derive(Error, Debug, Clone, PartialEq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct StructErrorImpl<T: DomainReason> {
94 reason: T,
95 detail: Option<String>,
96 position: Option<String>,
97 context: Arc<Vec<OperationContext>>,
98}
99
100impl<T: DomainReason> StructErrorImpl<T> {
101 pub fn reason(&self) -> &T {
102 &self.reason
103 }
104
105 pub fn detail(&self) -> &Option<String> {
106 &self.detail
107 }
108
109 pub fn position(&self) -> &Option<String> {
110 &self.position
111 }
112
113 pub fn context(&self) -> &Arc<Vec<OperationContext>> {
114 &self.context
115 }
116}
117
118pub fn convert_error<R1, R2>(other: StructError<R1>) -> StructError<R2>
119where
120 R1: DomainReason,
121 R2: DomainReason + From<R1>,
122{
123 StructError::new(
124 other.imp.reason.into(),
125 other.imp.detail,
126 other.imp.position,
127 Arc::try_unwrap(other.imp.context).unwrap_or_else(|arc| (*arc).clone()),
128 )
129}
130
131impl<T: DomainReason> StructError<T> {
132 pub fn builder(reason: T) -> StructErrorBuilder<T> {
133 StructErrorBuilder {
134 reason,
135 detail: None,
136 position: None,
137 contexts: Vec::new(),
138 }
139 }
140
141 #[must_use]
144 pub fn with_position(mut self, position: impl Into<String>) -> Self {
145 self.imp.position = Some(position.into());
146 self
147 }
148 #[must_use]
149 pub fn with_context(mut self, context: CallContext) -> Self {
150 Arc::make_mut(&mut self.imp.context).push(OperationContext::from(context));
151 self
152 }
153
154 pub fn contexts(&self) -> &[OperationContext] {
155 self.imp.context.as_ref()
156 }
157
158 #[must_use]
160 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
161 self.imp.detail = Some(detail.into());
162 self
163 }
164 pub fn err<V>(self) -> Result<V, Self> {
165 Err(self)
166 }
167 pub fn target(&self) -> Option<String> {
168 self.context.first().and_then(|x| x.target().clone())
169 }
170}
171
172impl<T: DomainReason> StructErrorTrait<T> for StructError<T> {
173 fn get_reason(&self) -> &T {
174 &self.reason
175 }
176
177 fn get_detail(&self) -> Option<&String> {
178 self.detail.as_ref()
179 }
180
181 fn get_target(&self) -> Option<String> {
182 self.target()
183 }
184}
185
186impl<T: DomainReason> ContextAdd<&OperationContext> for StructError<T> {
195 fn add_context(&mut self, ctx: &OperationContext) {
196 Arc::make_mut(&mut self.imp.context).push(ctx.clone());
197 }
198}
199impl<T: DomainReason> ContextAdd<OperationContext> for StructError<T> {
200 fn add_context(&mut self, ctx: OperationContext) {
201 Arc::make_mut(&mut self.imp.context).push(ctx);
202 }
203}
204
205impl<T: std::fmt::Display + DomainReason + ErrorCode> Display for StructError<T> {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 write!(f, "[{}] {reason}", self.error_code(), reason = self.reason)?;
209
210 if let Some(pos) = &self.position {
212 write!(f, "\n -> At: {pos}")?;
213 }
214
215 if let Some(target) = &self.target() {
217 write!(f, "\n -> Want: {target}")?;
218 }
219
220 if let Some(detail) = &self.detail {
222 write!(f, "\n -> Details: {detail}")?;
223 }
224
225 if !self.context.is_empty() {
227 writeln!(f, "\n -> Context stack:")?;
228
229 for (i, c) in self.context.iter().enumerate() {
230 writeln!(f, "context {i}: ")?;
231 writeln!(f, "{c}")?;
232 }
233 }
234
235 Ok(())
236 }
237}
238
239pub struct StructErrorBuilder<T: DomainReason> {
240 reason: T,
241 detail: Option<String>,
242 position: Option<String>,
243 contexts: Vec<OperationContext>,
244}
245
246impl<T: DomainReason> StructErrorBuilder<T> {
247 pub fn detail(mut self, detail: impl Into<String>) -> Self {
248 self.detail = Some(detail.into());
249 self
250 }
251
252 pub fn position(mut self, position: impl Into<String>) -> Self {
253 self.position = Some(position.into());
254 self
255 }
256
257 pub fn context(mut self, ctx: OperationContext) -> Self {
258 self.contexts.push(ctx);
259 self
260 }
261
262 pub fn context_ref(mut self, ctx: &OperationContext) -> Self {
263 self.contexts.push(ctx.clone());
264 self
265 }
266
267 pub fn finish(self) -> StructError<T> {
268 StructError::new(self.reason, self.detail, self.position, self.contexts)
269 }
270}
271
272impl<T: DomainReason> ErrorWith for StructError<T> {
273 fn want<S: Into<String>>(mut self, desc: S) -> Self {
274 let desc = desc.into();
275 let ctx_stack = Arc::make_mut(&mut self.imp.context);
276 if ctx_stack.is_empty() {
277 ctx_stack.push(OperationContext::want(desc));
278 } else if let Some(x) = ctx_stack.last_mut() {
279 x.with_want(desc);
280 }
281 self
282 }
283 fn position<S: Into<String>>(mut self, pos: S) -> Self {
284 self.imp.position = Some(pos.into());
285 self
286 }
287
288 fn with<C: Into<OperationContext>>(mut self, ctx: C) -> Self {
289 let ctx = ctx.into();
290 self.add_context(ctx);
291 self
292 }
293}
294
295#[cfg(all(test, feature = "serde"))]
296mod tests {
297
298 use crate::UvsReason;
299
300 use super::*;
301 use derive_more::From;
302
303 #[derive(Debug, Clone, PartialEq, Error, From)]
305 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
306 enum TestDomainReason {
307 #[error("test error")]
308 TestError,
309 #[error("{0}")]
310 Uvs(UvsReason),
311 }
312
313 impl ErrorCode for TestDomainReason {
314 fn error_code(&self) -> i32 {
315 match self {
316 TestDomainReason::TestError => 1001,
317 TestDomainReason::Uvs(uvs_reason) => uvs_reason.error_code(),
318 }
319 }
320 }
321
322 #[test]
323 fn test_struct_error_serialization() {
324 let mut context = CallContext::default();
326 context
327 .items
328 .push(("key1".to_string(), "value1".to_string()));
329 context
330 .items
331 .push(("key2".to_string(), "value2".to_string()));
332
333 let error = StructError::new(
335 TestDomainReason::TestError,
336 Some("Detailed error description".to_string()),
337 Some("file.rs:10:5".to_string()),
338 vec![OperationContext::from(context)],
339 );
340
341 let json_value = serde_json::to_value(&error).unwrap();
343 println!("{json_value:#}");
344 }
345}