use core::error::Error;
use core::fmt::{self, Display, Formatter, Write};
use crate::report::{CauseCollectOptions, Report, SeverityState, StackTrace};
#[cfg(feature = "trace")]
use super::attachment;
#[cfg(feature = "trace")]
use super::trace::{
TraceAttributeValue, TraceContextValue, TraceEventValue, TraceSectionValue,
build_trace_section_value,
};
use super::{
ReportRenderOptions, close_array, close_object, filtered_frames, write_array_item_prefix,
write_error_code, write_json_display, write_json_string, write_object_field,
};
pub(super) fn write_error_object<E>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
error: &E,
) -> fmt::Result
where
E: Display,
{
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "message", |f| {
write_json_display(f, error)
})?;
write_object_field(f, pretty, depth, &mut first, "type", |f| {
write_json_string(f, core::any::type_name::<E>())
})?;
close_object(f, pretty, depth, first)
}
pub(super) fn write_metadata_object<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
let mut first = true;
f.write_char('{')?;
write_meta_gov_fields(f, pretty, depth, &mut first, report)?;
close_object(f, pretty, depth, first)
}
pub(super) fn write_diag_bag<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
options: ReportRenderOptions,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
let mut first = true;
f.write_char('{')?;
write_diag_stack(f, pretty, depth, report, options, &mut first)?;
write_diag_display_causes(f, pretty, depth, report, options, &mut first)?;
write_diag_origin_sources(f, pretty, depth, report, options, &mut first)?;
write_diag_extra_sources(f, pretty, depth, report, options, &mut first)?;
close_object(f, pretty, depth, first)
}
fn write_diag_stack<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
options: ReportRenderOptions,
first: &mut bool,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
if !options.show_stack_trace_section
|| (!options.show_empty_sections && report.stack_trace().is_none())
{
return Ok(());
}
let Some(stack_trace) = report.stack_trace() else {
return Ok(());
};
write_object_field(f, pretty, depth, first, "stack_trace", |f| {
write_stack_trace_object(f, pretty, depth + 1, stack_trace, options)
})
}
fn write_diag_display_causes<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
options: ReportRenderOptions,
first: &mut bool,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
if !options.show_cause_chains_section
|| (!options.show_empty_sections && !has_display_causes(report))
{
return Ok(());
}
let Some(display_causes) = report.display_causes_chain() else {
return Ok(());
};
write_object_field(f, pretty, depth, first, "display_causes", |f| {
write_display_causes(f, pretty, depth + 1, report, display_causes, options)
})
}
fn write_diag_origin_sources<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
options: ReportRenderOptions,
first: &mut bool,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
if !options.show_cause_chains_section
|| (!options.show_empty_sections && !has_origin_source_errors(report))
{
return Ok(());
}
let traversal_options = CauseCollectOptions {
max_depth: options.max_source_depth,
detect_cycle: options.detect_source_cycle,
};
let Some(source_errors) = report.origin_src_err_view(traversal_options) else {
return Ok(());
};
write_object_field(f, pretty, depth, first, "origin_source_errors", |f| {
write_source_errors_chain(f, pretty, depth + 1, &source_errors, true)
})
}
fn write_diag_extra_sources<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
options: ReportRenderOptions,
first: &mut bool,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
if !options.show_cause_chains_section
|| (!options.show_empty_sections && !has_diag_source_errors(report))
{
return Ok(());
}
let traversal_options = CauseCollectOptions {
max_depth: options.max_source_depth,
detect_cycle: options.detect_source_cycle,
};
let Some(source_errors) = report.diag_src_err_view(traversal_options) else {
return Ok(());
};
write_object_field(f, pretty, depth, first, "diagnostic_source_errors", |f| {
write_source_errors_chain(f, pretty, depth + 1, &source_errors, false)
})
}
fn has_display_causes<E, State>(report: &Report<E, State>) -> bool
where
E: Error,
State: SeverityState,
{
report.display_causes_chain().is_some()
}
fn has_origin_source_errors<E, State>(report: &Report<E, State>) -> bool
where
E: Error,
State: SeverityState,
{
report.origin_src_err_chain().is_some() || report.inner().source().is_some()
}
fn has_diag_source_errors<E, State>(report: &Report<E, State>) -> bool
where
E: Error,
State: SeverityState,
{
report.diag_src_err_chain().is_some()
}
fn write_meta_gov_fields<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
first: &mut bool,
report: &Report<E, State>,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
let metadata = report.metadata();
if let Some(code) = metadata.error_code() {
write_object_field(f, pretty, depth, first, "error_code", |f| {
write_error_code(f, code)
})?;
}
if let Some(severity) = report.severity() {
write_object_field(f, pretty, depth, first, "severity", |f| {
write_json_display(f, &severity)
})?;
}
if let Some(category) = metadata.category() {
write_object_field(f, pretty, depth, first, "category", |f| {
write_json_string(f, category)
})?;
}
if let Some(retryable) = metadata.retryable() {
write_object_field(f, pretty, depth, first, "retryable", |f| {
write!(f, "{retryable}")
})?;
}
Ok(())
}
fn write_display_causes<State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<impl Error, State>,
display_causes: &crate::report::DisplayCauseChain,
options: ReportRenderOptions,
) -> fmt::Result
where
State: SeverityState,
{
let traversal_options = CauseCollectOptions {
max_depth: options.max_source_depth,
detect_cycle: options.detect_source_cycle,
};
let mut traversal_state = crate::report::CauseTraversalState::default();
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "items", |f| {
let mut array_first = true;
f.write_char('[')?;
traversal_state = report.visit_causes_ext(traversal_options, |cause| {
write_array_item_prefix(f, pretty, depth + 1, &mut array_first)?;
write_json_display(f, cause)
})?;
close_array(f, pretty, depth + 1, array_first)
})?;
write_object_field(f, pretty, depth, &mut first, "truncated", |f| {
write!(
f,
"{}",
display_causes.truncated || traversal_state.truncated
)
})?;
write_object_field(f, pretty, depth, &mut first, "cycle_detected", |f| {
write!(
f,
"{}",
display_causes.cycle_detected || traversal_state.cycle_detected
)
})?;
close_object(f, pretty, depth, first)
}
fn write_source_error_object(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
message: &str,
type_name: Option<&str>,
source_roots: &[usize],
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "message", |f| {
write_json_string(f, message)
})?;
if let Some(type_name) = type_name {
write_object_field(f, pretty, depth, &mut first, "type", |f| {
write_json_string(f, type_name)
})?;
}
write_object_field(f, pretty, depth, &mut first, "source_roots", |f| {
let mut array_first = true;
f.write_char('[')?;
for &source_id in source_roots {
write_array_item_prefix(f, pretty, depth + 1, &mut array_first)?;
write!(f, "{source_id}")?;
}
close_array(f, pretty, depth + 1, array_first)
})?;
close_object(f, pretty, depth, first)
}
fn write_source_errors_chain(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
source_errors: &crate::report::SourceErrorChain,
hide_report_wrapper_types: bool,
) -> fmt::Result {
let exported = source_errors.export_with_options(hide_report_wrapper_types);
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "roots", |f| {
let mut array_first = true;
f.write_char('[')?;
for &node_id in exported.roots.as_slice() {
write_array_item_prefix(f, pretty, depth + 1, &mut array_first)?;
write!(f, "{node_id}")?;
}
close_array(f, pretty, depth + 1, array_first)
})?;
write_object_field(f, pretty, depth, &mut first, "nodes", |f| {
let mut array_first = true;
f.write_char('[')?;
for node in exported.nodes.iter() {
write_array_item_prefix(f, pretty, depth + 1, &mut array_first)?;
write_source_error_object(
f,
pretty,
depth + 2,
&node.message,
node.type_name.as_deref(),
node.source_roots.as_slice(),
)?;
}
close_array(f, pretty, depth + 1, array_first)
})?;
write_object_field(f, pretty, depth, &mut first, "truncated", |f| {
write!(f, "{}", exported.truncated)
})?;
write_object_field(f, pretty, depth, &mut first, "cycle_detected", |f| {
write!(f, "{}", exported.cycle_detected)
})?;
close_object(f, pretty, depth, first)
}
#[cfg(feature = "trace")]
fn write_trace_context_value(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
context: &TraceContextValue,
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
if let Some(trace_id) = context.trace_id.as_deref() {
write_object_field(f, pretty, depth, &mut first, "trace_id", |f| {
write_json_string(f, trace_id)
})?;
}
if let Some(span_id) = context.span_id.as_deref() {
write_object_field(f, pretty, depth, &mut first, "span_id", |f| {
write_json_string(f, span_id)
})?;
}
if let Some(parent_span_id) = context.parent_span_id.as_deref() {
write_object_field(f, pretty, depth, &mut first, "parent_span_id", |f| {
write_json_string(f, parent_span_id)
})?;
}
if let Some(v) = context.sampled {
write_object_field(f, pretty, depth, &mut first, "sampled", |f| {
write!(f, "{v}")
})?;
}
if let Some(trace_state) = context.trace_state.as_deref() {
write_object_field(f, pretty, depth, &mut first, "trace_state", |f| {
write_json_string(f, trace_state)
})?;
}
close_object(f, pretty, depth, first)
}
#[cfg(feature = "trace")]
pub(super) fn write_trace_object<E, State>(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
report: &Report<E, State>,
options: ReportRenderOptions,
) -> fmt::Result
where
E: Error,
State: SeverityState,
{
let trace = report.trace();
if trace.is_empty() {
f.write_str("{}")?;
return Ok(());
}
write_trace_section_value(f, pretty, depth, &build_trace_section_value(trace), options)
}
#[cfg(feature = "trace")]
fn write_trace_section_value(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
value: &TraceSectionValue,
options: ReportRenderOptions,
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "context", |f| {
write_trace_context_value(f, pretty, depth + 1, &value.context)
})?;
write_object_field(f, pretty, depth, &mut first, "events", |f| {
write_trace_events_array(f, pretty, depth + 1, &value.events, options)
})?;
close_object(f, pretty, depth, first)
}
#[cfg(feature = "trace")]
fn write_trace_events_array(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
events: &[TraceEventValue],
options: ReportRenderOptions,
) -> fmt::Result {
let mut first = true;
f.write_char('[')?;
for event in events {
write_array_item_prefix(f, pretty, depth, &mut first)?;
write_trace_event_value(f, pretty, depth + 1, event, options)?;
}
close_array(f, pretty, depth, first)
}
#[cfg(feature = "trace")]
fn write_trace_event_value(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
event: &TraceEventValue,
options: ReportRenderOptions,
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "name", |f| {
write_json_string(f, event.name.as_ref())
})?;
if options.show_trace_event_details {
if let Some(level) = event.level.as_deref() {
write_object_field(f, pretty, depth, &mut first, "level", |f| {
write_json_string(f, level)
})?;
}
if let Some(v) = event.timestamp_unix_nano {
write_object_field(f, pretty, depth, &mut first, "timestamp_unix_nano", |f| {
write!(f, "{v}")
})?;
}
if !event.attributes.is_empty() {
write_object_field(f, pretty, depth, &mut first, "attributes", |f| {
write_trace_attr_array(f, pretty, depth + 1, &event.attributes)
})?;
}
}
close_object(f, pretty, depth, first)
}
#[cfg(feature = "trace")]
fn write_trace_attr_array(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
attrs: &[TraceAttributeValue],
) -> fmt::Result {
let mut first = true;
f.write_char('[')?;
for attr in attrs {
write_array_item_prefix(f, pretty, depth, &mut first)?;
write_trace_attr_value(f, pretty, depth + 1, attr)?;
}
close_array(f, pretty, depth, first)
}
#[cfg(feature = "trace")]
fn write_trace_attr_value(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
attr: &TraceAttributeValue,
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "key", |f| {
write_json_string(f, attr.key.as_ref())
})?;
write_object_field(f, pretty, depth, &mut first, "value", |f| {
attachment::write_attachment_value(f, pretty, depth + 1, &attr.value)
})?;
close_object(f, pretty, depth, first)
}
fn write_stack_trace_object(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
stack_trace: &StackTrace,
options: ReportRenderOptions,
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
write_object_field(f, pretty, depth, &mut first, "format", |f| {
let label = match stack_trace.format {
crate::report::StackTraceFormat::Native => "native",
crate::report::StackTraceFormat::Raw => "raw",
};
write_json_string(f, label)
})?;
match stack_trace.format {
crate::report::StackTraceFormat::Native => {
write_object_field(f, pretty, depth, &mut first, "frames", |f| {
let mut array_first = true;
f.write_char('[')?;
for (_, frame) in filtered_frames(&stack_trace.frames, &options.stack_trace_filter)
.take(options.stack_trace_max_lines)
{
write_array_item_prefix(f, pretty, depth + 1, &mut array_first)?;
write_stack_frame_object(f, pretty, depth + 2, frame)?;
}
close_array(f, pretty, depth + 1, array_first)
})?;
}
crate::report::StackTraceFormat::Raw => {
if let Some(raw) = stack_trace.raw.as_ref() {
write_object_field(f, pretty, depth, &mut first, "raw", |f| {
write_json_string(f, raw)
})?;
}
}
}
close_object(f, pretty, depth, first)
}
fn write_stack_frame_object(
f: &mut Formatter<'_>,
pretty: bool,
depth: usize,
frame: &crate::report::StackFrame,
) -> fmt::Result {
let mut first = true;
f.write_char('{')?;
if let Some(symbol) = frame.symbol.as_deref() {
write_object_field(f, pretty, depth, &mut first, "symbol", |f| {
write_json_string(f, symbol)
})?;
}
if let Some(module_path) = frame.module_path.as_deref() {
write_object_field(f, pretty, depth, &mut first, "module_path", |f| {
write_json_string(f, module_path)
})?;
}
if let Some(file) = frame.file.as_deref() {
write_object_field(f, pretty, depth, &mut first, "file", |f| {
write_json_string(f, file)
})?;
}
if let Some(v) = frame.line {
write_object_field(f, pretty, depth, &mut first, "line", |f| write!(f, "{v}"))?;
}
if let Some(v) = frame.column {
write_object_field(f, pretty, depth, &mut first, "column", |f| write!(f, "{v}"))?;
}
close_object(f, pretty, depth, first)
}