Skip to main content

g_err/report/
json_report.rs

1use crate::gerr::Source;
2use crate::report::json_data::{DisplayJsonData, JsonData};
3use crate::{Config, gerr_view::GErrView, report::Report};
4use alloc::borrow::Cow;
5use core::fmt::{Debug, Display};
6
7/// GErr's reporting as JSON for internal reports.
8pub struct JsonReport;
9
10impl JsonReport {
11    /// Internal JSON report data.
12    pub fn data<E, C: Config, D>(err: &E) -> JsonData
13    where
14        for<'a> &'a E: Into<GErrView<'a, C, D>>,
15        C::Id: serde::Serialize,
16        D: serde::Serialize,
17    {
18        let err = &err.into();
19        err.into()
20    }
21
22    /// Public JSON report data.
23    pub fn display_data<E, C: Config, D>(err: &E) -> DisplayJsonData
24    where
25        for<'a> &'a E: Into<GErrView<'a, C, D>>,
26        C::Id: serde::Serialize,
27        D: serde::Serialize,
28    {
29        let err = &err.into();
30        err.into()
31    }
32}
33
34impl Report for JsonReport {
35    fn report<E, C: Config, D>(err: &E) -> String
36    where
37        for<'a> &'a E: Into<GErrView<'a, C, D>>,
38        C::Id: Display + serde::Serialize,
39        D: Debug + serde::Serialize,
40    {
41        let err = &err.into();
42        let resp: JsonReportData = err.into();
43        serde_json::to_string_pretty(&resp).unwrap_or("<invalid json format>".into())
44    }
45}
46
47#[derive(serde::Serialize)]
48struct JsonReportData<'a> {
49    pub id: Option<serde_json::Value>,
50    pub code: Option<&'a str>,
51    pub message: &'a str,
52    pub tags: Option<&'a [Cow<'static, str>]>,
53    pub data: Option<serde_json::Value>,
54    pub location: LocationJson<'a>,
55    pub sources: Option<Vec<SourceJson<'a>>>,
56    pub help: Option<&'a str>,
57
58    #[cfg(feature = "backtrace")]
59    pub backtrace: String,
60}
61
62#[derive(serde::Serialize)]
63struct LocationJson<'a> {
64    pub file: &'a str,
65    pub line: u32,
66    pub column: u32,
67}
68
69#[derive(serde::Serialize)]
70struct SourceJson<'a> {
71    pub id: Option<&'a serde_json::Value>,
72    pub code: Option<&'a str>,
73    pub message: String,
74    pub tags: Option<&'a [Cow<'static, str>]>,
75    pub data: Option<&'a serde_json::Value>,
76    pub location: Option<LocationJson<'a>>,
77    pub sources: Option<Vec<SourceJson<'a>>>,
78    pub help: Option<&'a str>,
79}
80
81impl<'a, C: Config, D> From<&'a GErrView<'a, C, D>> for JsonReportData<'a>
82where
83    C::Id: serde::Serialize,
84    D: serde::Serialize,
85{
86    fn from(value: &'a GErrView<C, D>) -> Self {
87        Self {
88            id: value
89                .id
90                .map(|id| ::serde_json::to_value(id).unwrap_or_default()),
91            code: value.code,
92            message: value.message,
93            tags: value.tags,
94            data: serde_json::to_value(value.data).ok(),
95            location: LocationJson {
96                file: &value.location.file,
97                line: value.location.line,
98                column: value.location.column,
99            },
100            sources: value.sources.map(|s| s.iter().map(|v| v.into()).collect()),
101            help: value.help,
102
103            #[cfg(feature = "backtrace")]
104            backtrace: format!("{:#?}", value.backtrace),
105        }
106    }
107}
108
109pub static NO_ID_JSON: serde_json::Value = serde_json::Value::Null;
110
111impl<'a> From<&'a Source> for SourceJson<'a> {
112    fn from(source: &'a Source) -> Self {
113        match source {
114            Source::Err(err) => Self {
115                id: Some(&NO_ID_JSON),
116                code: None,
117                message: err.to_string(),
118                tags: None,
119                data: None,
120                location: None,
121                sources: None,
122                help: None,
123            },
124            Source::GErr(gerr) => Self {
125                id: gerr.id_json.as_ref(),
126                code: gerr.code.as_deref(),
127                message: gerr.message.to_string(),
128                tags: gerr.tags.as_deref(),
129                data: gerr.data_json.as_ref(),
130                location: gerr.location.as_ref().map(|loc| LocationJson {
131                    file: &loc.file,
132                    line: loc.line,
133                    column: loc.column,
134                }),
135                sources: gerr
136                    .sources
137                    .as_deref()
138                    .map(|s| s.iter().map(|src| src.into()).collect()),
139                help: gerr.help.as_deref(),
140            },
141        }
142    }
143}