made_client/
ceremony_tree.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use made_proto::v1::CeremonyInstanceState;
4
5use crate::{CeremonyTreeNode, MadeClientError};
6
7#[derive(Clone, Debug)]
9pub struct CeremonyTree {
10 roots: Vec<CeremonyTreeNode>,
11}
12
13impl CeremonyTree {
14 pub fn from_instances(instances: Vec<CeremonyInstanceState>) -> Result<Self, MadeClientError> {
15 let mut by_id = BTreeMap::new();
16 let mut children = BTreeMap::<String, Vec<String>>::new();
17 for instance in instances {
18 if instance.ceremony_id.is_empty() {
19 return Err(MadeClientError::ProtocolViolation(
20 "ceremony listing contains an empty id".to_owned(),
21 ));
22 }
23 let id = instance.ceremony_id.clone();
24 if by_id.insert(id.clone(), instance).is_some() {
25 return Err(MadeClientError::ProtocolViolation(format!(
26 "ceremony listing repeats {id}"
27 )));
28 }
29 }
30 for (id, instance) in &by_id {
31 if let Some(lineage) = &instance.lineage {
32 if !lineage.parent_id.is_empty() && by_id.contains_key(&lineage.parent_id) {
33 children
34 .entry(lineage.parent_id.clone())
35 .or_default()
36 .push(id.clone());
37 }
38 }
39 }
40 for child_ids in children.values_mut() {
41 child_ids.sort_by(|left, right| {
42 let left_state = &by_id[left];
43 let right_state = &by_id[right];
44 let left_position = left_state
45 .lineage
46 .as_ref()
47 .map_or(0, |value| value.position);
48 let right_position = right_state
49 .lineage
50 .as_ref()
51 .map_or(0, |value| value.position);
52 left_position.cmp(&right_position).then(left.cmp(right))
53 });
54 }
55
56 let mut roots: Vec<_> = by_id
57 .iter()
58 .filter(|(_, instance)| {
59 instance.lineage.as_ref().is_none_or(|lineage| {
60 lineage.parent_id.is_empty() || !by_id.contains_key(&lineage.parent_id)
61 })
62 })
63 .map(|(id, _)| id.clone())
64 .collect();
65 roots.sort();
66
67 let mut visiting = BTreeSet::new();
68 let mut built = BTreeSet::new();
69 let roots = roots
70 .iter()
71 .map(|id| build_node(id, &by_id, &children, &mut visiting, &mut built))
72 .collect::<Result<Vec<_>, _>>()?;
73 if built.len() != by_id.len() {
74 return Err(MadeClientError::ProtocolViolation(
75 "ceremony lineage contains a cycle disconnected from every root".to_owned(),
76 ));
77 }
78 Ok(Self { roots })
79 }
80
81 #[must_use]
82 pub fn roots(&self) -> &[CeremonyTreeNode] {
83 &self.roots
84 }
85}
86
87fn build_node(
88 id: &str,
89 by_id: &BTreeMap<String, CeremonyInstanceState>,
90 children: &BTreeMap<String, Vec<String>>,
91 visiting: &mut BTreeSet<String>,
92 built: &mut BTreeSet<String>,
93) -> Result<CeremonyTreeNode, MadeClientError> {
94 if !visiting.insert(id.to_owned()) {
95 return Err(MadeClientError::ProtocolViolation(format!(
96 "ceremony lineage contains a cycle at {id}"
97 )));
98 }
99 let nested = children
100 .get(id)
101 .into_iter()
102 .flatten()
103 .map(|child| build_node(child, by_id, children, visiting, built))
104 .collect::<Result<Vec<_>, _>>()?;
105 visiting.remove(id);
106 built.insert(id.to_owned());
107 let instance = by_id.get(id).cloned().ok_or_else(|| {
108 MadeClientError::ProtocolViolation(format!("missing ceremony tree node {id}"))
109 })?;
110 Ok(CeremonyTreeNode::new(instance, nested))
111}