Skip to main content

made_client/
ceremony_tree.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use made_proto::v1::CeremonyInstanceState;
4
5use crate::{CeremonyTreeNode, MadeClientError};
6
7/// A non-authoritative hierarchy assembled from one bounded server result.
8#[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}