Skip to main content

error_stack/
serde.rs

1//! Implementation of general [`Report`] serialization.
2//!
3//! The value can be of any type, currently only printable attachments and context are supported, in
4//! the near future any values will be supported through the use of hooks.
5//!
6//! The serialized [`Report`] is a list of all current sources with the following output:
7//!
8//! ```json
9//! {
10//!     "context": "context display output",
11//!     "attachments": ["all", "attachments", "leading", "up", "to", "this", "context"],
12//!     "sources": [] // recursive render using `frame.sources()`
13//! }
14//! ```
15
16use alloc::{format, vec, vec::Vec};
17use core::error::Error;
18
19use serde_core::{Serialize, Serializer, ser::SerializeMap as _};
20
21use crate::{AttachmentKind, Frame, FrameKind, Report};
22
23struct SerializeAttachment<'a>(&'a Frame);
24
25impl Serialize for SerializeAttachment<'_> {
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: Serializer,
29    {
30        let Self(frame) = self;
31
32        match frame.kind() {
33            FrameKind::Context(_) => {
34                // TODO: for now `Error` is unsupported, upcoming PR will fix via hooks
35                // `SerializeAttachmentList` ensures that no context is ever serialized
36                unimplemented!()
37            }
38            FrameKind::Attachment(AttachmentKind::Opaque(_)) => {
39                // TODO: for now opaque attachments are unsupported, upcoming PR will fix that
40                // `SerializeAttachmentList` ensures that no such attachment is added
41                unimplemented!()
42            }
43            FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
44                format!("{attachment}").serialize(serializer)
45            }
46        }
47    }
48}
49
50struct SerializeAttachmentList<'a, 'b>(&'a [&'b Frame]);
51
52impl Serialize for SerializeAttachmentList<'_, '_> {
53    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
54    where
55        S: Serializer,
56    {
57        serializer.collect_seq(
58            self.0
59                .iter()
60                .copied()
61                .filter(|attachment| {
62                    // for now opaque attachments are ignored
63                    !matches!(
64                        attachment.kind(),
65                        FrameKind::Attachment(AttachmentKind::Opaque(_))
66                    )
67                })
68                .map(SerializeAttachment),
69        )
70    }
71}
72
73struct SerializeContext<'a> {
74    attachments: Vec<&'a Frame>,
75    context: &'a (dyn Error + Send + Sync + 'static),
76    sources: &'a [Frame],
77}
78
79impl Serialize for SerializeContext<'_> {
80    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: Serializer,
83    {
84        let Self {
85            context,
86            attachments,
87            sources,
88        } = self;
89
90        let mut map = serializer.serialize_map(Some(3))?;
91        map.serialize_entry("context", &format!("{context}").as_str())?;
92        map.serialize_entry("attachments", &SerializeAttachmentList(attachments))?;
93        map.serialize_entry("sources", &SerializeSources(sources))?;
94
95        map.end()
96    }
97}
98
99struct SerializeSources<'a>(&'a [Frame]);
100
101impl Serialize for SerializeSources<'_> {
102    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103    where
104        S: Serializer,
105    {
106        serializer.collect_seq(self.0.iter().flat_map(|source| find_next(&[], source)))
107    }
108}
109
110// find the next applicable context and return the serializer
111fn find_next<'a>(head: &[&'a Frame], mut current: &'a Frame) -> Vec<SerializeContext<'a>> {
112    let mut attachments = vec![];
113    attachments.extend(head);
114
115    loop {
116        match current.kind() {
117            FrameKind::Context(context) => {
118                // found the context, return all attachments (reversed)
119                attachments.reverse();
120
121                return vec![SerializeContext {
122                    attachments,
123                    context,
124                    sources: current.sources(),
125                }];
126            }
127            FrameKind::Attachment(_) => match current.sources() {
128                [] => {
129                    // there are no more frames, therefore we need to abandon
130                    // this is theoretically impossible (the bottom is always a context), but not
131                    // enforced
132                    return vec![];
133                }
134                [source] => {
135                    attachments.push(current);
136
137                    current = source;
138                }
139                sources => {
140                    // there are multiple sources, we need to recursively probe each of them
141                    attachments.push(current);
142
143                    return sources
144                        .iter()
145                        .flat_map(|source| find_next(&attachments, source))
146                        .collect();
147                }
148            },
149        }
150    }
151}
152
153impl<C: Error + Send + Sync + 'static> Serialize for Report<C> {
154    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155    where
156        S: Serializer,
157    {
158        SerializeSources(self.current_frames_unchecked()).serialize(serializer)
159    }
160}
161
162impl<C: Error + Send + Sync + 'static> Serialize for Report<[C]> {
163    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164    where
165        S: Serializer,
166    {
167        SerializeSources(self.current_frames_unchecked()).serialize(serializer)
168    }
169}