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
16#[cfg_attr(feature = "std", allow(unused_imports))]
17use alloc::{format, vec, vec::Vec};
18
19use serde::{ser::SerializeMap, Serialize, Serializer};
20
21use crate::{AttachmentKind, Context, Frame, FrameKind, Report};
22
23struct SerializeAttachment<'a>(&'a Frame);
24
25impl<'a> Serialize for SerializeAttachment<'a> {
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        #[allow(clippy::match_same_arms)]
33        match frame.kind() {
34            FrameKind::Context(_) => {
35                // TODO: for now `Context` 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<'a, 'b> Serialize for SerializeAttachmentList<'a, 'b> {
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<'a> Serialize for SerializeContext<'a> {
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<'a> Serialize for SerializeSources<'a> {
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        if let FrameKind::Context(context) = current.kind() {
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        } else if current.sources().len() > 1 {
127            // current is an attachment, add to attachments and recursively probe
128            attachments.push(current);
129
130            return current
131                .sources()
132                .iter()
133                .flat_map(|source| find_next(&attachments, source))
134                .collect();
135        } else if current.sources().len() == 1 {
136            attachments.push(current);
137
138            current = &current.sources()[0];
139        } else {
140            // there are no more frames, therefore we need to abandon
141            // this is theoretically impossible (the bottom is always a context), but not enforced
142            return vec![];
143        }
144    }
145}
146
147impl<C: Context> Serialize for Report<C> {
148    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149    where
150        S: Serializer,
151    {
152        SerializeSources(self.current_frames()).serialize(serializer)
153    }
154}