use alloc::borrow::Cow;
use core::panic::Location;
use crate::{
Config, GErrDefault, IdSource, gerr,
gerr::{ErrorLocation, GErr, Source},
gerr_source::{DataSource, GErrSource},
gerr_view::GErrView,
};
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct DisplayJsonData {
pub id: Option<serde_json::Value>,
pub code: Option<String>,
pub message: String,
pub tags: Option<Vec<String>>,
pub data: Option<serde_json::Value>,
pub help: Option<String>,
pub causes: Option<Vec<DisplayCausesJsonData>>,
}
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct DisplayCausesJsonData {
pub message: String,
pub caused_by: Option<Vec<DisplayCausesJsonData>>,
}
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct JsonData {
pub id: Option<serde_json::Value>,
pub code: Option<String>,
pub message: String,
pub tags: Option<Vec<String>>,
pub data: Option<serde_json::Value>,
pub location: Option<LocationJsonData>,
pub sources: Option<Vec<SourceJsonData>>,
pub help: Option<String>,
pub backtrace: Option<String>,
}
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct LocationJsonData {
pub file: String,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize, Default)]
pub struct SourceJsonData {
pub id: Option<serde_json::Value>,
pub code: Option<String>,
pub message: String,
pub tags: Option<Vec<String>>,
pub data: Option<serde_json::Value>,
pub location: Option<LocationJsonData>,
pub sources: Option<Vec<SourceJsonData>>,
pub help: Option<String>,
}
impl<'a, C: Config, D> From<&GErrView<'a, C, D>> for JsonData
where
C::Id: ::serde::Serialize,
D: ::serde::Serialize,
{
fn from(err: &GErrView<'a, C, D>) -> Self {
JsonData {
id: err
.id
.map(|id| ::serde_json::to_value(id).unwrap_or_default()),
code: err.code.map(|s| s.into()),
message: err.message.into(),
tags: err.tags.map(|t| t.iter().map(|s| s.to_string()).collect()),
data: err
.data
.map(|d| serde_json::to_value(d).unwrap_or_default()),
help: err.help.map(Into::into),
location: Some(LocationJsonData {
file: err.location.file.to_string(),
line: err.location.line,
column: err.location.column,
}),
sources: err.sources.map(|s| s.iter().map(|i| i.into()).collect()),
#[cfg(not(feature = "backtrace"))]
backtrace: None,
#[cfg(feature = "backtrace")]
backtrace: match err.backtrace.status() {
std::backtrace::BacktraceStatus::Disabled => Some("<disabled>".into()),
std::backtrace::BacktraceStatus::Captured => Some(err.backtrace.to_string()),
_ => Some("<unsupported>".into()),
},
}
}
}
impl<'a, C: Config, D> From<&GErrView<'a, C, D>> for DisplayJsonData
where
C::Id: ::serde::Serialize,
D: ::serde::Serialize,
{
fn from(err: &GErrView<'a, C, D>) -> Self {
DisplayJsonData {
id: err
.id
.map(|id| ::serde_json::to_value(id).unwrap_or_default()),
code: err.code.map(|s| s.into()),
message: err.message.into(),
tags: err.tags.map(|t| t.iter().map(|s| s.to_string()).collect()),
data: err
.data
.map(|d| serde_json::to_value(d).unwrap_or_default()),
help: err.help.map(Into::into),
causes: err
.sources
.map(|sources| sources.iter().map(|src| src.into()).collect::<Vec<_>>()),
}
}
}
impl<C: Config, D> TryFrom<JsonData> for GErr<C, D>
where
C::Id: for<'a> ::serde::Deserialize<'a>,
D: for<'a> ::serde::Deserialize<'a>,
{
type Error = GErrDefault;
fn try_from(value: JsonData) -> Result<Self, Self::Error> {
let JsonData {
id,
code,
message,
tags,
data,
help,
location,
sources,
backtrace: _,
} = value;
let de_id: Option<C::Id> = if let Some(the_id) = id {
serde_json::from_value(the_id).map_err(|err| {
gerr!(
"failed converting id to ID = {}",
core::any::type_name::<C::Id>()
)
.add_source(err)
})?
} else {
C::id()
};
let mut err = GErr::<C, D>::new_with_id_untracked(de_id, message, Location::caller());
if let Some(data) = data {
err = err.set_data(serde_json::from_value(data).map_err(|err| {
gerr!(
"failed converting data into D = {}",
core::any::type_name::<D>()
)
.add_source(err)
})?);
}
if let Some(code) = code {
err = err.set_code(code);
} else if let Some(const_code) = C::CODE {
err = err.set_code(const_code);
}
if let Some(tags) = tags {
err = err.add_tags(tags);
}
if let Some(sources) = sources {
let gerr_sources: Vec<Source> = sources.into_iter().map(|s| s.into_source()).collect();
err = err.set_sources(gerr_sources);
}
if let Some(help) = help {
err = err.set_help(help);
}
if let Some(loc) = location {
err = err.set_location(ErrorLocation {
file: Cow::Owned(loc.file),
line: loc.line,
column: loc.column,
});
}
Ok(err)
}
}
impl From<&Source> for SourceJsonData {
fn from(gerr: &Source) -> Self {
match gerr {
Source::Err(err) => Self {
message: err.to_string(),
..Default::default()
},
Source::GErr(gerr) => Self {
id: serde_json::from_value({
match &gerr.id_json {
Some(::serde_json::Value::Number(num)) => {
::serde_json::Value::from(num.as_i64().unwrap_or_default())
}
Some(::serde_json::Value::String(s)) => {
::serde_json::Value::from(s.as_str())
}
Some(::serde_json::Value::Bool(b)) => ::serde_json::Value::from(*b),
Some(::serde_json::Value::Array(arr)) => {
::serde_json::Value::from(arr.as_slice())
}
Some(::serde_json::Value::Object(obj)) => {
::serde_json::Value::from(obj.clone())
}
_ => ::serde_json::Value::Null,
}
})
.unwrap_or_default(),
code: gerr.code.as_deref().map(|s| s.into()),
message: gerr.message.to_string(),
tags: gerr
.tags
.as_ref()
.map(|t| t.iter().map(|t| t.to_string()).collect()),
data: serde_json::from_value({
if let Some(json) = &gerr.data_json {
match json {
::serde_json::Value::Bool(b) => ::serde_json::Value::from(*b),
::serde_json::Value::Number(num) => {
::serde_json::Value::from(num.as_i64().unwrap_or_default())
}
::serde_json::Value::String(s) => ::serde_json::Value::from(s.as_str()),
::serde_json::Value::Array(arr) => {
let slice: &[::serde_json::Value] = arr.as_ref();
::serde_json::Value::from(slice)
}
serde_json::Value::Object(obj) => {
::serde_json::Value::Object(obj.clone())
}
_ => ::serde_json::Value::Null,
}
} else {
::serde_json::Value::Null
}
})
.unwrap_or_default(),
help: gerr.help.as_deref().map(|s| s.into()),
sources: gerr
.sources
.as_ref()
.map(|s| s.iter().map(|s| s.into()).collect()),
location: gerr.location.as_ref().map(|loc| LocationJsonData {
file: loc.file.to_string(),
line: loc.line,
column: loc.column,
}),
},
}
}
}
impl From<&Source> for DisplayCausesJsonData {
fn from(value: &Source) -> Self {
match value {
Source::Err(err) => Self {
message: err.to_string(),
caused_by: None,
},
Source::GErr(gerr) => Self {
message: gerr.to_string(),
caused_by: gerr
.sources
.as_deref()
.map(|s| s.iter().map(|i| i.into()).collect::<Vec<_>>()),
},
}
}
}
impl SourceJsonData {
fn into_source(self) -> Source {
let SourceJsonData {
id,
code,
message,
tags,
data,
help,
location,
sources,
} = self;
let gerr_source = GErrSource {
id: match id {
Some(serde_json::Value::Bool(b)) => Some(Box::new(b)),
Some(serde_json::Value::Number(ref num)) => {
Some(Box::new(num.as_i64().unwrap_or_default()))
}
_ => id
.as_ref()
.map(|id| Box::new(id.to_string()) as Box<dyn IdSource>),
},
id_json: id,
message: message.into(),
code: code.map(Cow::Owned),
sources: sources.map(|s| s.into_iter().map(|sj| sj.into_source()).collect()),
tags: tags.map(|tags| tags.into_iter().map(Cow::Owned).collect()),
data: data
.as_ref()
.map(|v| Box::new(v.to_string()) as Box<dyn DataSource>),
data_json: data,
help: help.map(Cow::Owned),
location: location.map(|loc| ErrorLocation {
file: Cow::Owned(loc.file),
line: loc.line,
column: loc.column,
}),
};
Source::GErr(Box::new(gerr_source))
}
}