Skip to main content

safemesh_crdt/crdt/
enable_wins_flag.rs

1// SafeMesh — delta-state CRDT convergence, built on crdt-lean.
2// Copyright (C) 2026 Ben Cassie
3// SPDX-License-Identifier: Apache-2.0
4
5use crate::{Crdt, MergeError, Mergeable, RecordId, WireError};
6use alloc::{collections::BTreeSet, vec::Vec};
7
8/// Delta for an observed-token enable-wins flag.
9#[derive(Clone, Debug)]
10pub enum EnableWinsFlagDelta<K> {
11    Enable { token: K },
12    Disable { tokens: Vec<K> },
13}
14
15// Record admission compares Disable tokens as a set. Keep the received vector
16// so callers can inspect the order and repeats that were present on the wire.
17impl<K: Ord> PartialEq for EnableWinsFlagDelta<K> {
18    fn eq(&self, other: &Self) -> bool {
19        match (self, other) {
20            (Self::Enable { token: a }, Self::Enable { token: b }) => a == b,
21            (Self::Disable { tokens: a }, Self::Disable { tokens: b }) => {
22                a.iter().collect::<BTreeSet<_>>() == b.iter().collect::<BTreeSet<_>>()
23            }
24            _ => false,
25        }
26    }
27}
28
29impl<K: Ord> Eq for EnableWinsFlagDelta<K> {}
30
31/// Enable-wins boolean flag.
32///
33/// This is a flat tested-not-proven type. It mirrors an OR-Set over a unit
34/// element: enable adds a unique token, disable tombstones observed tokens, and
35/// concurrent unobserved enables remain live.
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct EnableWinsFlag<K: Ord> {
38    pub(super) enables: BTreeSet<K>,
39    pub(super) tombstones: BTreeSet<K>,
40}
41
42impl<K: Ord> EnableWinsFlag<K> {
43    pub fn new() -> Self {
44        EnableWinsFlag {
45            enables: BTreeSet::new(),
46            tombstones: BTreeSet::new(),
47        }
48    }
49
50    pub fn enable(&mut self, token: K) {
51        self.enables.insert(token);
52    }
53
54    pub fn disable<I>(&mut self, tokens: I)
55    where
56        I: IntoIterator<Item = K>,
57    {
58        self.tombstones.extend(tokens);
59    }
60
61    pub fn merge(&mut self, other: &Self)
62    where
63        K: Clone,
64    {
65        self.enables.extend(other.enables.iter().cloned());
66        self.tombstones.extend(other.tombstones.iter().cloned());
67    }
68
69    pub fn enables(&self) -> &BTreeSet<K> {
70        &self.enables
71    }
72
73    pub fn tombstones(&self) -> &BTreeSet<K> {
74        &self.tombstones
75    }
76}
77
78impl<K: Ord + Clone> EnableWinsFlag<K> {
79    pub fn observed_tokens(&self) -> BTreeSet<K> {
80        self.enables.iter().cloned().collect()
81    }
82
83    pub fn value(&self) -> bool {
84        self.enables
85            .iter()
86            .any(|token| !self.tombstones.contains(token))
87    }
88}
89
90impl<K: Ord> Default for EnableWinsFlag<K> {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl<K: Ord + Clone> Mergeable for EnableWinsFlag<K> {
97    fn merge(&mut self, other: &Self) -> Result<(), MergeError> {
98        EnableWinsFlag::merge(self, other);
99        Ok(())
100    }
101}
102
103impl<K: Ord + Clone> Crdt for EnableWinsFlag<K> {
104    type Delta = EnableWinsFlagDelta<K>;
105
106    /// Accepts every positive-sequence record, by the same argument as [`crate::OrSet`]: an enable joins
107    /// the enable set even when its token is tombstoned, a disable tombstones the
108    /// tokens it names whether observed or not, and a disable naming no tokens is
109    /// the lattice bottom. Every decodable record applies on a fresh flag.
110    fn validate_record(&self, id: RecordId, _delta: &Self::Delta) -> Result<(), WireError> {
111        id.require_positive_sequence("Enable-wins flag")
112    }
113
114    fn apply_delta(&mut self, delta: Self::Delta) {
115        match delta {
116            EnableWinsFlagDelta::Enable { token } => self.enable(token),
117            EnableWinsFlagDelta::Disable { tokens } => self.disable(tokens),
118        }
119    }
120}