error_stack/
serde.rs

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