use alloc::borrow::Cow;
use alloc::string::ToString;
use alloc::sync::Arc;
#[cfg(feature = "trace")]
use alloc::vec::Vec;
use core::any;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use ref_str::RefStr;
#[cfg(any(feature = "trace", feature = "json"))]
use ref_str::StaticRefStr;
#[cfg(feature = "json")]
use crate::render_impl::REPORT_JSON_SCHEMA_VERSION;
#[cfg(feature = "trace")]
use crate::report::AttachmentValue;
use crate::report::SourceErrorChain;
#[cfg(feature = "trace")]
use crate::report::StackFrame;
use crate::report::{
Attachment, CauseTraversalState, ContextMap, ErrorCode, HasSeverity, MissingSeverity, Report,
Severity, SeverityState, StackTrace,
};
#[cfg(feature = "trace")]
use crate::report::{ReportTrace, TraceContext, TraceEvent};
#[cfg(feature = "trace")]
use crate::utils::FastMap;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize))]
pub struct DiagnosticIrErrorNode<'a> {
pub message: DiagnosticIrMessage<'a>,
pub r#type: RefStr<'a>,
}
pub type DiagnosticIrError<'a> = DiagnosticIrErrorNode<'a>;
#[derive(Clone)]
pub enum DiagnosticIrMessage<'a> {
Borrowed(&'a str),
Owned(RefStr<'a>),
Display(&'a (dyn Display + 'a)),
}
impl DiagnosticIrMessage<'_> {
pub fn as_cow(&self) -> Cow<'_, str> {
match self {
Self::Borrowed(v) => Cow::Borrowed(v),
Self::Owned(v) => Cow::Borrowed(v),
Self::Display(v) => Cow::Owned(v.to_string()),
}
}
}
impl Display for DiagnosticIrMessage<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Borrowed(v) => f.write_str(v),
Self::Owned(v) => f.write_str(v.as_str()),
Self::Display(v) => write!(f, "{v}"),
}
}
}
impl core::fmt::Debug for DiagnosticIrMessage<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.as_cow())
}
}
impl PartialEq for DiagnosticIrMessage<'_> {
fn eq(&self, other: &Self) -> bool {
self.as_cow() == other.as_cow()
}
}
impl Eq for DiagnosticIrMessage<'_> {}
impl PartialEq<&str> for DiagnosticIrMessage<'_> {
fn eq(&self, other: &&str) -> bool {
match self {
Self::Borrowed(v) => v == other,
Self::Owned(v) => v.as_str() == *other,
Self::Display(v) => v.to_string() == *other,
}
}
}
#[cfg(feature = "json")]
impl serde::Serialize for DiagnosticIrMessage<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.as_cow())
}
}
#[derive(Clone)]
pub struct DiagnosticIrMetadata<'a, State = MissingSeverity> {
error_code: Option<&'a ErrorCode>,
severity: State,
category: Option<&'a str>,
retryable: Option<bool>,
stack_trace: Option<&'a StackTrace>,
}
impl<'a, State> DiagnosticIrMetadata<'a, State>
where
State: SeverityState,
{
pub fn error_code(&self) -> Option<&'a ErrorCode> {
self.error_code
}
pub fn severity(&self) -> Option<Severity> {
self.severity.severity()
}
pub fn category(&self) -> Option<&'a str> {
self.category
}
pub fn retryable(&self) -> Option<bool> {
self.retryable
}
pub fn stack_trace(&self) -> Option<&'a StackTrace> {
self.stack_trace
}
fn map_severity<NewState>(self, severity: NewState) -> DiagnosticIrMetadata<'a, NewState>
where
NewState: SeverityState,
{
DiagnosticIrMetadata {
error_code: self.error_code,
severity,
category: self.category,
retryable: self.retryable,
stack_trace: self.stack_trace,
}
}
pub fn with_severity(self, level: Severity) -> DiagnosticIrMetadata<'a, HasSeverity> {
self.map_severity(HasSeverity::new(level))
}
}
impl DiagnosticIrMetadata<'_, HasSeverity> {
pub const fn required_severity(&self) -> Severity {
self.severity.severity()
}
}
#[derive(Clone)]
pub struct DiagnosticIr<'a, State = MissingSeverity> {
#[cfg(feature = "json")]
pub schema_version: StaticRefStr,
pub error: DiagnosticIrError<'a>,
pub metadata: DiagnosticIrMetadata<'a, State>,
#[cfg(feature = "trace")]
pub trace: &'a ReportTrace,
pub context: &'a ContextMap,
pub system: &'a ContextMap,
pub attachments: &'a [Attachment],
pub display_causes: &'a [Arc<dyn Display + Send + Sync + 'static>],
pub display_causes_state: CauseTraversalState,
pub origin_source_errors: Option<SourceErrorChain>,
pub diagnostic_source_errors: Option<SourceErrorChain>,
}
impl<'a, State> DiagnosticIr<'a, State>
where
State: SeverityState,
{
pub fn with_severity(self, level: Severity) -> DiagnosticIr<'a, HasSeverity> {
let Self {
#[cfg(feature = "json")]
schema_version,
error,
metadata,
#[cfg(feature = "trace")]
trace,
context,
system,
attachments,
display_causes,
display_causes_state,
origin_source_errors,
diagnostic_source_errors,
} = self;
DiagnosticIr {
#[cfg(feature = "json")]
schema_version,
error,
metadata: metadata.with_severity(level),
#[cfg(feature = "trace")]
trace,
context,
system,
attachments,
display_causes,
display_causes_state,
origin_source_errors,
diagnostic_source_errors,
}
}
}
impl<E, State> Report<E, State>
where
State: SeverityState,
{
pub fn to_diagnostic_ir(&self) -> DiagnosticIr<'_, State>
where
E: Error,
{
let metadata = Report::<E, State>::metadata(self);
let display_causes_state = Report::<E, State>::visit_causes_ext(
self,
Report::<E, State>::options(self).as_cause_options(),
|_| Ok(()),
)
.unwrap_or_default();
DiagnosticIr {
#[cfg(feature = "json")]
schema_version: REPORT_JSON_SCHEMA_VERSION.into(),
error: DiagnosticIrErrorNode {
message: DiagnosticIrMessage::Display(Report::<E, State>::inner(self)),
r#type: any::type_name::<E>().into(),
},
metadata: DiagnosticIrMetadata {
error_code: metadata.error_code(),
severity: Report::<E, State>::severity_state(self),
category: metadata.category(),
retryable: metadata.retryable(),
stack_trace: Report::<E, State>::stack_trace(self),
},
#[cfg(feature = "trace")]
trace: Report::<E, State>::trace(self),
context: Report::<E, State>::context(self),
system: Report::<E, State>::system(self),
attachments: Report::<E, State>::attachments(self),
display_causes: Report::<E, State>::display_causes(self),
display_causes_state,
origin_source_errors: Report::<E, State>::origin_src_err_view(
self,
Report::<E, State>::options(self).as_cause_options(),
),
diagnostic_source_errors: Report::<E, State>::diag_src_err_view(
self,
Report::<E, State>::options(self).as_cause_options(),
),
}
}
}
#[cfg(feature = "trace")]
pub(crate) fn build_ctx_and_attachments(
context: &ContextMap,
system: &ContextMap,
attachments: &[Attachment],
) -> (AttachmentValue, AttachmentValue, Vec<AttachmentValue>) {
let mut context_map = FastMap::new();
let mut system_map = FastMap::new();
let mut attachment_items = Vec::new();
for (key, value) in context {
context_map.insert(key.clone(), AttachmentValue::from(value));
}
for (key, value) in system {
system_map.insert(key.clone(), AttachmentValue::from(value));
}
for attachment in attachments {
match attachment {
Attachment::Note { message } => {
let mut map = FastMap::new();
map.insert("kind".into(), AttachmentValue::String("note".into()));
map.insert(
"message".into(),
AttachmentValue::String(message.to_string().into()),
);
attachment_items.push(AttachmentValue::Object(map));
}
Attachment::Payload {
name,
value,
media_type,
} => {
let mut map = FastMap::new();
map.insert("kind".into(), AttachmentValue::String("payload".into()));
map.insert("name".into(), AttachmentValue::String(name.clone()));
map.insert("value".into(), value.clone());
if let Some(media_type) = media_type.as_ref() {
map.insert(
"media_type".into(),
AttachmentValue::String(media_type.clone()),
);
}
attachment_items.push(AttachmentValue::Object(map));
}
}
}
(
AttachmentValue::Object(context_map),
AttachmentValue::Object(system_map),
attachment_items,
)
}
#[cfg(feature = "trace")]
pub(crate) fn build_error_value(error: &DiagnosticIrError<'_>) -> AttachmentValue {
let mut map = FastMap::new();
map.insert(
"message".into(),
AttachmentValue::String(error.message.to_string().into()),
);
map.insert(
"type".into(),
AttachmentValue::String(error.r#type.to_string().into()),
);
AttachmentValue::Object(map)
}
#[cfg(feature = "trace")]
pub(crate) fn build_trace_value(
trace: &ReportTrace,
error: &DiagnosticIrError<'_>,
) -> AttachmentValue {
let mut trace_obj = FastMap::new();
trace_obj.insert("error".into(), build_error_value(error));
trace_obj.insert("context".into(), build_trace_ctx_value_opt(trace.context()));
trace_obj.insert(
"events".into(),
AttachmentValue::Array(
trace
.events()
.map(|e| e.iter().map(build_trace_event_value).collect())
.unwrap_or_default(),
),
);
AttachmentValue::Object(trace_obj)
}
#[cfg(feature = "trace")]
fn build_trace_ctx_value_opt(context: Option<&TraceContext>) -> AttachmentValue {
match context {
Some(ctx) => build_trace_ctx_value(ctx),
None => AttachmentValue::Object(FastMap::new()),
}
}
#[cfg(feature = "trace")]
fn build_trace_ctx_value(context: &TraceContext) -> AttachmentValue {
let mut ctx = FastMap::new();
if let Some(value) = context.trace_id.as_ref() {
ctx.insert(
"trace_id".into(),
AttachmentValue::String(value.to_string().into()),
);
}
if let Some(value) = context.span_id.as_ref() {
ctx.insert(
"span_id".into(),
AttachmentValue::String(value.to_string().into()),
);
}
if let Some(value) = context.parent_span_id.as_ref() {
ctx.insert(
"parent_span_id".into(),
AttachmentValue::String(value.to_string().into()),
);
}
if let Some(value) = context.sampled {
ctx.insert("sampled".into(), AttachmentValue::Bool(value));
}
if let Some(value) = context.trace_state.as_ref() {
ctx.insert(
"trace_state".into(),
AttachmentValue::String(value.as_static_ref().clone()),
);
}
AttachmentValue::Object(ctx)
}
#[cfg(feature = "trace")]
fn build_trace_event_value(event: &TraceEvent) -> AttachmentValue {
let mut map = FastMap::new();
map.insert("name".into(), AttachmentValue::String(event.name.clone()));
if let Some(value) = event.level {
map.insert(
"level".into(),
AttachmentValue::String(value.as_str().into()),
);
}
if let Some(value) = event.timestamp_unix_nano {
map.insert(
"timestamp_unix_nano".into(),
AttachmentValue::Unsigned(value),
);
}
map.insert(
"attributes".into(),
AttachmentValue::Array(
event
.attributes
.iter()
.map(|attr| {
let mut kv = FastMap::new();
kv.insert("key".into(), AttachmentValue::String(attr.key.clone()));
kv.insert("value".into(), attr.value.clone());
AttachmentValue::Object(kv)
})
.collect(),
),
);
AttachmentValue::Object(map)
}
#[cfg(feature = "trace")]
pub(crate) fn build_stack_trace_value(stack_trace: &StackTrace) -> AttachmentValue {
let mut map = FastMap::new();
let format = match stack_trace.format {
crate::report::StackTraceFormat::Native => "native",
crate::report::StackTraceFormat::Raw => "raw",
};
map.insert("format".into(), AttachmentValue::String(format.into()));
map.insert(
"frames".into(),
AttachmentValue::Array(
stack_trace
.frames
.iter()
.map(build_stack_frame_value)
.collect(),
),
);
if let Some(value) = stack_trace.raw.as_ref() {
map.insert("raw".into(), AttachmentValue::String(value.clone()));
}
AttachmentValue::Object(map)
}
#[cfg(feature = "trace")]
fn build_stack_frame_value(frame: &StackFrame) -> AttachmentValue {
let mut map = FastMap::new();
if let Some(value) = frame.symbol.as_ref() {
map.insert("symbol".into(), AttachmentValue::String(value.clone()));
}
if let Some(value) = frame.module_path.as_ref() {
map.insert("module_path".into(), AttachmentValue::String(value.clone()));
}
if let Some(value) = frame.file.as_ref() {
map.insert("file".into(), AttachmentValue::String(value.clone()));
}
if let Some(value) = frame.line {
map.insert("line".into(), AttachmentValue::Unsigned(value as u64));
}
if let Some(value) = frame.column {
map.insert("column".into(), AttachmentValue::Unsigned(value as u64));
}
AttachmentValue::Object(map)
}
#[cfg(feature = "trace")]
pub(crate) fn build_display_causes(
display_causes: &[Arc<dyn Display + Send + Sync + 'static>],
state: CauseTraversalState,
) -> AttachmentValue {
let mut map = FastMap::new();
map.insert(
"items".into(),
AttachmentValue::Array(
display_causes
.iter()
.map(|v| AttachmentValue::String(v.to_string().into()))
.collect(),
),
);
map.insert("truncated".into(), AttachmentValue::Bool(state.truncated));
map.insert(
"cycle_detected".into(),
AttachmentValue::Bool(state.cycle_detected),
);
AttachmentValue::Object(map)
}
#[cfg(feature = "trace")]
pub(crate) fn build_origin_src_errs_val(source_errors: &SourceErrorChain) -> AttachmentValue {
build_source_errors_value(source_errors, true)
}
#[cfg(feature = "trace")]
pub(crate) fn build_diag_src_errs_val(source_errors: &SourceErrorChain) -> AttachmentValue {
build_source_errors_value(source_errors, false)
}
#[cfg(feature = "trace")]
fn build_source_errors_value(
source_errors: &SourceErrorChain,
hide_report_wrapper_types: bool,
) -> AttachmentValue {
let exported = source_errors.export_with_options(hide_report_wrapper_types);
let mut map = FastMap::new();
map.insert(
"roots".into(),
AttachmentValue::Array(
exported
.roots
.iter()
.copied()
.map(|id| AttachmentValue::Integer(id as i64))
.collect(),
),
);
map.insert(
"nodes".into(),
AttachmentValue::Array(
exported
.nodes
.iter()
.map(|node| {
build_source_err_node(
&node.message,
node.type_name.clone(),
node.source_roots.as_slice(),
)
})
.collect(),
),
);
map.insert(
"truncated".into(),
AttachmentValue::Bool(exported.truncated),
);
map.insert(
"cycle_detected".into(),
AttachmentValue::Bool(exported.cycle_detected),
);
AttachmentValue::Object(map)
}
#[cfg(feature = "trace")]
fn build_source_err_node(
message: &str,
type_name: Option<StaticRefStr>,
source_roots: &[usize],
) -> AttachmentValue {
let mut map = FastMap::new();
map.insert(
"message".into(),
AttachmentValue::String(message.to_string().into()),
);
if let Some(value) = type_name {
map.insert("type".into(), AttachmentValue::String(value));
}
map.insert(
"source_roots".into(),
AttachmentValue::Array(
source_roots
.iter()
.copied()
.map(|id| AttachmentValue::Integer(id as i64))
.collect(),
),
);
AttachmentValue::Object(map)
}