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