use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::btree_set;
use crate::quorum::HierarchicalQuorumSetError;
use crate::quorum::QuorumIntersection;
use crate::quorum::QuorumSet;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Group<ID> {
pub quorum_size: u64,
pub ids: BTreeSet<ID>,
pub groups: BTreeMap<String, Group<ID>>,
}
impl<ID> Group<ID>
where ID: Ord + Clone
{
pub fn of_ids<I>(quorum_size: u64, ids: I) -> Self
where I: IntoIterator<Item = ID> {
Self {
quorum_size,
ids: ids.into_iter().collect(),
groups: BTreeMap::new(),
}
}
pub fn of_groups<I, K>(quorum_size: u64, groups: I) -> Self
where
I: IntoIterator<Item = (K, Group<ID>)>,
K: Into<String>,
{
let mut by_name = BTreeMap::new();
for (name, group) in groups {
by_name.insert(name.into(), group);
}
Self {
quorum_size,
ids: BTreeSet::new(),
groups: by_name,
}
}
fn member_count(&self) -> u64 {
let member_count = self.ids.len() + self.groups.len();
member_count as u64
}
fn is_reached_by(&self, candidates: &BTreeSet<&ID>) -> bool {
let held_ids = self.ids.iter().filter(|id| candidates.contains(id));
let held = held_ids.count() as u64;
let reached_groups = self.groups.values().filter(|group| group.is_reached_by(candidates));
let reached = reached_groups.count() as u64;
held + reached >= self.quorum_size
}
fn collect_ids(&self, ids: &mut BTreeSet<ID>) {
ids.extend(self.ids.iter().cloned());
for group in self.groups.values() {
group.collect_ids(ids);
}
}
fn dual(&self) -> Self {
let quorum_size = self.member_count() - self.quorum_size + 1;
let mut groups = BTreeMap::new();
for (name, group) in self.groups.iter() {
groups.insert(name.clone(), group.dual());
}
Self {
quorum_size,
ids: self.ids.clone(),
groups,
}
}
fn proves_intersection(&self, other: &Self) -> bool {
let same_ids = self.ids == other.ids;
let same_names = self.groups.keys().eq(other.groups.keys());
if !same_ids || !same_names {
return false;
}
let quorum_sizes = self.quorum_size + other.quorum_size;
if quorum_sizes <= self.member_count() {
return false;
}
let children = self.groups.values().zip(other.groups.values());
for (group, other_group) in children {
let proved = group.proves_intersection(other_group);
if !proved {
return false;
}
}
true
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HierarchicalQuorumSet<ID> {
root: Group<ID>,
}
impl<ID> HierarchicalQuorumSet<ID>
where ID: Ord + Clone
{
pub fn new(root: Group<ID>) -> Result<Self, HierarchicalQuorumSetError<ID>> {
let mut group_of = BTreeMap::new();
check_group(&root, &[], &mut group_of)?;
let rule = Self { root };
Ok(rule)
}
pub fn root(&self) -> &Group<ID> {
&self.root
}
pub fn dual(&self) -> Self {
let root = self.root.dual();
Self { root }
}
}
impl<ID> QuorumSet for HierarchicalQuorumSet<ID>
where ID: Ord + Clone
{
type Id = ID;
type Iter = btree_set::IntoIter<ID>;
fn is_quorum<'a, I>(&self, ids: I) -> bool
where
ID: 'a,
I: Iterator<Item = &'a ID> + Clone,
{
let candidates: BTreeSet<&ID> = ids.collect();
self.root.is_reached_by(&candidates)
}
fn ids(&self) -> Self::Iter {
let mut ids = BTreeSet::new();
self.root.collect_ids(&mut ids);
ids.into_iter()
}
}
impl<ID> QuorumIntersection<HierarchicalQuorumSet<ID>> for HierarchicalQuorumSet<ID>
where ID: Ord + Clone
{
fn intersects_with(&self, other: &HierarchicalQuorumSet<ID>) -> Option<bool> {
let proved = self.root.proves_intersection(&other.root);
if proved { Some(true) } else { None }
}
}
fn check_group<ID>(
group: &Group<ID>,
path: &[String],
group_of: &mut BTreeMap<ID, Vec<String>>,
) -> Result<(), HierarchicalQuorumSetError<ID>>
where
ID: Ord + Clone,
{
let member_count = group.member_count();
if member_count == 0 {
let empty = HierarchicalQuorumSetError::EmptyGroup {
group: path.to_vec(),
};
return Err(empty);
}
let size_fits = (1..=member_count).contains(&group.quorum_size);
if !size_fits {
let invalid = HierarchicalQuorumSetError::InvalidQuorumSize {
group: path.to_vec(),
quorum_size: group.quorum_size,
member_count,
};
return Err(invalid);
}
for id in &group.ids {
let Some(first) = group_of.insert(id.clone(), path.to_vec()) else {
continue;
};
let shared = HierarchicalQuorumSetError::SharedId {
id: id.clone(),
first,
second: path.to_vec(),
};
return Err(shared);
}
for (name, child) in group.groups.iter() {
let mut child_path = path.to_vec();
child_path.push(name.clone());
check_group(child, &child_path, group_of)?;
}
Ok(())
}