nmp_threading/wire/
decode.rs1use crate::{ThreadEdge, ThreadPointer, ThreadingSnapshot, TimelineBlock};
2
3use super::fb;
4
5pub fn decode_threading_snapshot(bytes: &[u8]) -> Result<ThreadingSnapshot, String> {
6 if bytes.len() < 8 || !fb::threading_snapshot_buffer_has_identifier(bytes) {
7 return Err("missing NTHR file identifier".to_string());
8 }
9 let snapshot = fb::root_as_threading_snapshot(bytes)
10 .map_err(|err| format!("not a valid ThreadingSnapshot buffer: {err}"))?;
11 Ok(ThreadingSnapshot {
12 edges: decode_edges(snapshot.edges())?,
13 blocks: decode_blocks(snapshot.blocks())?,
14 pending_ancestor_ids: decode_ids(snapshot.pending_ancestor_ids())?,
15 })
16}
17
18fn decode_edges(
19 edges: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<fb::ThreadEdge<'_>>>>,
20) -> Result<Vec<ThreadEdge>, String> {
21 let mut out = Vec::new();
22 if let Some(edges) = edges {
23 out.reserve(edges.len());
24 for edge in edges.iter() {
25 out.push(decode_edge(edge)?);
26 }
27 }
28 Ok(out)
29}
30
31fn decode_edge(edge: fb::ThreadEdge<'_>) -> Result<ThreadEdge, String> {
32 Ok(ThreadEdge {
33 event_id: str_field(edge.event_id(), "ThreadEdge.event_id")?,
34 author_pubkey: str_field(edge.author_pubkey(), "ThreadEdge.author_pubkey")?,
35 kind: edge.kind(),
36 created_at: edge.created_at(),
37 parent: edge.parent().map(decode_pointer).transpose()?,
38 root: edge.root().map(decode_pointer).transpose()?,
39 parent_author_pubkey: edge.parent_author_pubkey().map(str::to_string),
40 })
41}
42
43fn decode_blocks(
44 blocks: Option<
45 flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<fb::TimelineBlockEntry<'_>>>,
46 >,
47) -> Result<Vec<TimelineBlock>, String> {
48 let mut out = Vec::new();
49 if let Some(blocks) = blocks {
50 out.reserve(blocks.len());
51 for block in blocks.iter() {
52 out.push(decode_block(block)?);
53 }
54 }
55 Ok(out)
56}
57
58fn decode_block(block: fb::TimelineBlockEntry<'_>) -> Result<TimelineBlock, String> {
59 match block.kind() {
60 fb::TimelineBlockKind::Standalone => Ok(TimelineBlock::Standalone {
61 id: str_field(block.standalone_id(), "TimelineBlockEntry.standalone_id")?,
62 root: block.standalone_root().map(decode_pointer).transpose()?,
63 }),
64 fb::TimelineBlockKind::Module => Ok(TimelineBlock::Module {
65 events: decode_ids(block.module_event_ids())?,
66 has_gap: block.module_has_gap(),
67 root: block.module_root().map(decode_pointer).transpose()?,
68 }),
69 other => Err(format!("unknown TimelineBlockKind: {other:?}")),
70 }
71}
72
73fn decode_pointer(pointer: fb::ThreadPointer<'_>) -> Result<ThreadPointer, String> {
74 let kind = optional_kind_num(pointer.has_kind_num(), pointer.kind_num());
75 let relay = pointer.relay().map(str::to_string);
76 match pointer.kind() {
77 fb::ThreadPointerKind::Event => Ok(ThreadPointer::Event {
78 id: str_field(pointer.id(), "ThreadPointer.id")?,
79 relay,
80 kind,
81 }),
82 fb::ThreadPointerKind::Address => Ok(ThreadPointer::Address {
83 coord: str_field(pointer.coord(), "ThreadPointer.coord")?,
84 relay,
85 kind,
86 }),
87 fb::ThreadPointerKind::External => Ok(ThreadPointer::External {
88 uri: str_field(pointer.uri(), "ThreadPointer.uri")?,
89 }),
90 other => Err(format!("unknown ThreadPointerKind: {other:?}")),
91 }
92}
93
94fn decode_ids(
95 ids: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<fb::BlockEventId<'_>>>>,
96) -> Result<Vec<String>, String> {
97 let mut out = Vec::new();
98 if let Some(ids) = ids {
99 out.reserve(ids.len());
100 for id in ids.iter() {
101 out.push(str_field(id.id(), "BlockEventId.id")?);
102 }
103 }
104 Ok(out)
105}
106
107fn str_field(value: Option<&str>, ctx: &str) -> Result<String, String> {
108 value
109 .map(str::to_string)
110 .ok_or_else(|| format!("{ctx}: missing required string field"))
111}
112
113const fn optional_kind_num(present: bool, value: u32) -> Option<u32> {
114 if present {
115 Some(value)
116 } else {
117 None
118 }
119}