Skip to main content

g_err/report/
json_data.rs

1use alloc::borrow::Cow;
2use core::panic::Location;
3
4use crate::{
5    Config, GErrDefault, IdSource, gerr,
6    gerr::{ErrorLocation, GErr, Source},
7    gerr_source::{DataSource, GErrSource},
8    gerr_view::GErrView,
9};
10
11/// JSON data for public display.
12#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
13pub struct DisplayJsonData {
14    /// Error ID: can be in form of Number or String.
15    pub id: Option<serde_json::Value>,
16    /// Error code.
17    pub code: Option<String>,
18    /// Error message.
19    pub message: String,
20    /// Error tags.
21    pub tags: Option<Vec<String>>,
22    /// Error data: can be in any JSON values.
23    pub data: Option<serde_json::Value>,
24    /// Error help hint.
25    pub help: Option<String>,
26    /// Error causes
27    pub causes: Option<Vec<DisplayCausesJsonData>>,
28}
29
30/// Display JSON data causes
31#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
32pub struct DisplayCausesJsonData {
33    /// Cause message
34    pub message: String,
35    /// caused by
36    pub caused_by: Option<Vec<DisplayCausesJsonData>>,
37}
38
39/// JSON data for internal display.
40#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
41pub struct JsonData {
42    /// Error ID: can be in form of Number or String
43    pub id: Option<serde_json::Value>,
44    /// Error code
45    pub code: Option<String>,
46    /// Error message
47    pub message: String,
48    /// Error tags
49    pub tags: Option<Vec<String>>,
50    /// Error data: can be in any JSON values.
51    pub data: Option<serde_json::Value>,
52    /// Error location
53    pub location: Option<LocationJsonData>,
54    /// Error sources
55    pub sources: Option<Vec<SourceJsonData>>,
56    /// Error help hint
57    pub help: Option<String>,
58
59    /// Error stack trace
60    pub backtrace: Option<String>,
61}
62
63/// JSON data for error location.
64#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
65pub struct LocationJsonData {
66    /// File where error happen
67    pub file: String,
68    /// Line where error happen
69    pub line: u32,
70    /// Column where error happen
71    pub column: u32,
72}
73
74/// JSON data for error sources
75#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize, Default)]
76pub struct SourceJsonData {
77    /// Error ID: can be in form of Number or String
78    pub id: Option<serde_json::Value>,
79    /// Error code
80    pub code: Option<String>,
81    /// Error message
82    pub message: String,
83    /// Error tags
84    pub tags: Option<Vec<String>>,
85    /// Error data: can be in any JSON values.
86    pub data: Option<serde_json::Value>,
87    /// Error location
88    pub location: Option<LocationJsonData>,
89    /// Error sources
90    pub sources: Option<Vec<SourceJsonData>>,
91    /// Error help hint
92    pub help: Option<String>,
93}
94
95impl<'a, C: Config, D> From<&GErrView<'a, C, D>> for JsonData
96where
97    C::Id: ::serde::Serialize,
98    D: ::serde::Serialize,
99{
100    fn from(err: &GErrView<'a, C, D>) -> Self {
101        JsonData {
102            id: err
103                .id
104                .map(|id| ::serde_json::to_value(id).unwrap_or_default()),
105            code: err.code.map(|s| s.into()),
106            message: err.message.into(),
107            tags: err.tags.map(|t| t.iter().map(|s| s.to_string()).collect()),
108            data: err
109                .data
110                .map(|d| serde_json::to_value(d).unwrap_or_default()),
111            help: err.help.map(Into::into),
112            location: Some(LocationJsonData {
113                file: err.location.file.to_string(),
114                line: err.location.line,
115                column: err.location.column,
116            }),
117            sources: err.sources.map(|s| s.iter().map(|i| i.into()).collect()),
118            #[cfg(not(feature = "backtrace"))]
119            backtrace: None,
120            #[cfg(feature = "backtrace")]
121            backtrace: match err.backtrace.status() {
122                std::backtrace::BacktraceStatus::Disabled => Some("<disabled>".into()),
123                std::backtrace::BacktraceStatus::Captured => Some(err.backtrace.to_string()),
124                _ => Some("<unsupported>".into()),
125            },
126        }
127    }
128}
129
130impl<'a, C: Config, D> From<&GErrView<'a, C, D>> for DisplayJsonData
131where
132    C::Id: ::serde::Serialize,
133    D: ::serde::Serialize,
134{
135    fn from(err: &GErrView<'a, C, D>) -> Self {
136        DisplayJsonData {
137            id: err
138                .id
139                .map(|id| ::serde_json::to_value(id).unwrap_or_default()),
140            code: err.code.map(|s| s.into()),
141            message: err.message.into(),
142            tags: err.tags.map(|t| t.iter().map(|s| s.to_string()).collect()),
143            data: err
144                .data
145                .map(|d| serde_json::to_value(d).unwrap_or_default()),
146            help: err.help.map(Into::into),
147            causes: err
148                .sources
149                .map(|sources| sources.iter().map(|src| src.into()).collect::<Vec<_>>()),
150        }
151    }
152}
153
154/// Convert `JsonData` into `GErr<C, D>`.
155///
156/// If id and code are empty from JsonData,
157/// C: Config will auto-generate them.
158impl<C: Config, D> TryFrom<JsonData> for GErr<C, D>
159where
160    C::Id: for<'a> ::serde::Deserialize<'a>,
161    D: for<'a> ::serde::Deserialize<'a>,
162{
163    type Error = GErrDefault;
164
165    fn try_from(value: JsonData) -> Result<Self, Self::Error> {
166        let JsonData {
167            id,
168            code,
169            message,
170            tags,
171            data,
172            help,
173            location,
174            sources,
175            backtrace: _,
176        } = value;
177
178        let de_id: Option<C::Id> = if let Some(the_id) = id {
179            let id_val: C::Id = serde_json::from_value(the_id).map_err(|err| {
180                gerr!(
181                    "failed converting id to ID = {}",
182                    core::any::type_name::<C::Id>();
183                    source = err,
184                )
185            })?;
186            Some(id_val)
187        } else {
188            C::id()
189        };
190
191        let mut err = GErr::<C, D>::new_with_id_untracked(de_id, message, Location::caller());
192
193        if let Some(data) = data {
194            err = err.set_data(serde_json::from_value(data).map_err(|err| {
195                gerr!(
196                    "failed converting data into D = {}",
197                    core::any::type_name::<D>();
198                    source = err,
199                )
200            })?);
201        }
202
203        if let Some(code) = code {
204            err = err.set_code(code);
205        } else if let Some(const_code) = C::CODE {
206            err = err.set_code(const_code);
207        }
208
209        if let Some(tags) = tags {
210            err = err.add_tags(tags);
211        }
212
213        if let Some(sources) = sources {
214            let gerr_sources: Vec<Source> = sources.into_iter().map(|s| s.into_source()).collect();
215            err = err.set_sources(gerr_sources);
216        }
217
218        if let Some(help) = help {
219            err = err.set_help(help);
220        }
221
222        if let Some(loc) = location {
223            err = err.set_location(ErrorLocation {
224                file: Cow::Owned(loc.file),
225                line: loc.line,
226                column: loc.column,
227            });
228        }
229
230        Ok(err)
231    }
232}
233
234impl From<&Source> for SourceJsonData {
235    fn from(gerr: &Source) -> Self {
236        match gerr {
237            Source::Err(err) => Self {
238                message: err.to_string(),
239                ..Default::default()
240            },
241            Source::GErr(gerr) => Self {
242                id: serde_json::from_value({
243                    match &gerr.id_json {
244                        Some(::serde_json::Value::Number(num)) => {
245                            ::serde_json::Value::from(num.as_i64().unwrap_or_default())
246                        }
247                        Some(::serde_json::Value::String(s)) => {
248                            ::serde_json::Value::from(s.as_str())
249                        }
250                        Some(::serde_json::Value::Bool(b)) => ::serde_json::Value::from(*b),
251                        Some(::serde_json::Value::Array(arr)) => {
252                            ::serde_json::Value::from(arr.as_slice())
253                        }
254                        Some(::serde_json::Value::Object(obj)) => {
255                            ::serde_json::Value::from(obj.clone())
256                        }
257                        _ => ::serde_json::Value::Null,
258                    }
259                })
260                .unwrap_or_default(),
261                code: gerr.code.as_deref().map(|s| s.into()),
262                message: gerr.message.to_string(),
263                tags: gerr
264                    .tags
265                    .as_ref()
266                    .map(|t| t.iter().map(|t| t.to_string()).collect()),
267                data: serde_json::from_value({
268                    if let Some(json) = &gerr.data_json {
269                        match json {
270                            ::serde_json::Value::Bool(b) => ::serde_json::Value::from(*b),
271                            ::serde_json::Value::Number(num) => {
272                                ::serde_json::Value::from(num.as_i64().unwrap_or_default())
273                            }
274                            ::serde_json::Value::String(s) => ::serde_json::Value::from(s.as_str()),
275                            ::serde_json::Value::Array(arr) => {
276                                let slice: &[::serde_json::Value] = arr.as_ref();
277                                ::serde_json::Value::from(slice)
278                            }
279                            serde_json::Value::Object(obj) => {
280                                ::serde_json::Value::Object(obj.clone())
281                            }
282                            _ => ::serde_json::Value::Null,
283                        }
284                    } else {
285                        ::serde_json::Value::Null
286                    }
287                })
288                .unwrap_or_default(),
289                help: gerr.help.as_deref().map(|s| s.into()),
290                sources: gerr
291                    .sources
292                    .as_ref()
293                    .map(|s| s.iter().map(|s| s.into()).collect()),
294                location: gerr.location.as_ref().map(|loc| LocationJsonData {
295                    file: loc.file.to_string(),
296                    line: loc.line,
297                    column: loc.column,
298                }),
299            },
300        }
301    }
302}
303
304impl From<&Source> for DisplayCausesJsonData {
305    fn from(value: &Source) -> Self {
306        match value {
307            Source::Err(err) => Self {
308                message: err.to_string(),
309                caused_by: None,
310            },
311            Source::GErr(gerr) => Self {
312                message: gerr.to_string(),
313                caused_by: gerr
314                    .sources
315                    .as_deref()
316                    .map(|s| s.iter().map(|i| i.into()).collect::<Vec<_>>()),
317            },
318        }
319    }
320}
321
322impl SourceJsonData {
323    fn into_source(self) -> Source {
324        let SourceJsonData {
325            id,
326            code,
327            message,
328            tags,
329            data,
330            help,
331            location,
332            sources,
333        } = self;
334
335        let gerr_source = GErrSource {
336            id: match id {
337                Some(serde_json::Value::Bool(b)) => Some(Box::new(b)),
338                Some(serde_json::Value::Number(ref num)) => {
339                    Some(Box::new(num.as_i64().unwrap_or_default()))
340                }
341                _ => id
342                    .as_ref()
343                    .map(|id| Box::new(id.to_string()) as Box<dyn IdSource>),
344            },
345
346            id_json: id,
347
348            message: message.into(),
349
350            code: code.map(Cow::Owned),
351
352            sources: sources.map(|s| s.into_iter().map(|sj| sj.into_source()).collect()),
353
354            tags: tags.map(|tags| tags.into_iter().map(Cow::Owned).collect()),
355
356            data: data
357                .as_ref()
358                .map(|v| Box::new(v.to_string()) as Box<dyn DataSource>),
359
360            data_json: data,
361
362            help: help.map(Cow::Owned),
363
364            location: location.map(|loc| ErrorLocation {
365                file: Cow::Owned(loc.file),
366                line: loc.line,
367                column: loc.column,
368            }),
369        };
370
371        Source::GErr(Box::new(gerr_source))
372    }
373}