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            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                )
184                .add_source(err)
185            })?
186        } else {
187            C::id()
188        };
189
190        let mut err = GErr::<C, D>::new_with_id_untracked(de_id, message, Location::caller());
191
192        if let Some(data) = data {
193            err = err.set_data(serde_json::from_value(data).map_err(|err| {
194                gerr!(
195                    "failed converting data into D = {}",
196                    core::any::type_name::<D>()
197                )
198                .add_source(err)
199            })?);
200        }
201
202        if let Some(code) = code {
203            err = err.set_code(code);
204        } else if let Some(const_code) = C::CODE {
205            err = err.set_code(const_code);
206        }
207
208        if let Some(tags) = tags {
209            err = err.add_tags(tags);
210        }
211
212        if let Some(sources) = sources {
213            let gerr_sources: Vec<Source> = sources.into_iter().map(|s| s.into_source()).collect();
214            err = err.set_sources(gerr_sources);
215        }
216
217        if let Some(help) = help {
218            err = err.set_help(help);
219        }
220
221        if let Some(loc) = location {
222            err = err.set_location(ErrorLocation {
223                file: Cow::Owned(loc.file),
224                line: loc.line,
225                column: loc.column,
226            });
227        }
228
229        Ok(err)
230    }
231}
232
233impl From<&Source> for SourceJsonData {
234    fn from(gerr: &Source) -> Self {
235        match gerr {
236            Source::Err(err) => Self {
237                message: err.to_string(),
238                ..Default::default()
239            },
240            Source::GErr(gerr) => Self {
241                id: serde_json::from_value({
242                    match &gerr.id_json {
243                        Some(::serde_json::Value::Number(num)) => {
244                            ::serde_json::Value::from(num.as_i64().unwrap_or_default())
245                        }
246                        Some(::serde_json::Value::String(s)) => {
247                            ::serde_json::Value::from(s.as_str())
248                        }
249                        Some(::serde_json::Value::Bool(b)) => ::serde_json::Value::from(*b),
250                        Some(::serde_json::Value::Array(arr)) => {
251                            ::serde_json::Value::from(arr.as_slice())
252                        }
253                        Some(::serde_json::Value::Object(obj)) => {
254                            ::serde_json::Value::from(obj.clone())
255                        }
256                        _ => ::serde_json::Value::Null,
257                    }
258                })
259                .unwrap_or_default(),
260                code: gerr.code.as_deref().map(|s| s.into()),
261                message: gerr.message.to_string(),
262                tags: gerr
263                    .tags
264                    .as_ref()
265                    .map(|t| t.iter().map(|t| t.to_string()).collect()),
266                data: serde_json::from_value({
267                    if let Some(json) = &gerr.data_json {
268                        match json {
269                            ::serde_json::Value::Bool(b) => ::serde_json::Value::from(*b),
270                            ::serde_json::Value::Number(num) => {
271                                ::serde_json::Value::from(num.as_i64().unwrap_or_default())
272                            }
273                            ::serde_json::Value::String(s) => ::serde_json::Value::from(s.as_str()),
274                            ::serde_json::Value::Array(arr) => {
275                                let slice: &[::serde_json::Value] = arr.as_ref();
276                                ::serde_json::Value::from(slice)
277                            }
278                            serde_json::Value::Object(obj) => {
279                                ::serde_json::Value::Object(obj.clone())
280                            }
281                            _ => ::serde_json::Value::Null,
282                        }
283                    } else {
284                        ::serde_json::Value::Null
285                    }
286                })
287                .unwrap_or_default(),
288                help: gerr.help.as_deref().map(|s| s.into()),
289                sources: gerr
290                    .sources
291                    .as_ref()
292                    .map(|s| s.iter().map(|s| s.into()).collect()),
293                location: gerr.location.as_ref().map(|loc| LocationJsonData {
294                    file: loc.file.to_string(),
295                    line: loc.line,
296                    column: loc.column,
297                }),
298            },
299        }
300    }
301}
302
303impl From<&Source> for DisplayCausesJsonData {
304    fn from(value: &Source) -> Self {
305        match value {
306            Source::Err(err) => Self {
307                message: err.to_string(),
308                caused_by: None,
309            },
310            Source::GErr(gerr) => Self {
311                message: gerr.to_string(),
312                caused_by: gerr
313                    .sources
314                    .as_deref()
315                    .map(|s| s.iter().map(|i| i.into()).collect::<Vec<_>>()),
316            },
317        }
318    }
319}
320
321impl SourceJsonData {
322    fn into_source(self) -> Source {
323        let SourceJsonData {
324            id,
325            code,
326            message,
327            tags,
328            data,
329            help,
330            location,
331            sources,
332        } = self;
333
334        let gerr_source = GErrSource {
335            id: match id {
336                Some(serde_json::Value::Bool(b)) => Some(Box::new(b)),
337                Some(serde_json::Value::Number(ref num)) => {
338                    Some(Box::new(num.as_i64().unwrap_or_default()))
339                }
340                _ => id
341                    .as_ref()
342                    .map(|id| Box::new(id.to_string()) as Box<dyn IdSource>),
343            },
344
345            id_json: id,
346
347            message: message.into(),
348
349            code: code.map(Cow::Owned),
350
351            sources: sources.map(|s| s.into_iter().map(|sj| sj.into_source()).collect()),
352
353            tags: tags.map(|tags| tags.into_iter().map(Cow::Owned).collect()),
354
355            data: data
356                .as_ref()
357                .map(|v| Box::new(v.to_string()) as Box<dyn DataSource>),
358
359            data_json: data,
360
361            help: help.map(Cow::Owned),
362
363            location: location.map(|loc| ErrorLocation {
364                file: Cow::Owned(loc.file),
365                line: loc.line,
366                column: loc.column,
367            }),
368        };
369
370        Source::GErr(Box::new(gerr_source))
371    }
372}