Skip to main content

sierradb_protocol/
lib.rs

1//! # SierraDB Protocol
2//!
3//! Shared protocol types and utilities for SierraDB client and server
4//! communication. This crate defines the common types used across the SierraDB
5//! ecosystem without any external dependencies.
6
7pub mod error;
8
9use std::{cmp, fmt, num::ParseIntError, ops, str};
10
11pub use error::ErrorCode;
12use serde::{Deserialize, Serialize};
13
14/// The expected version **before** the event is inserted.
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
16pub enum ExpectedVersion {
17    /// Accept any version, whether the stream/partition exists or not.
18    #[default]
19    Any,
20    /// The stream/partition must exist (have at least one event).
21    Exists,
22    /// The stream/partition must be empty (have no events yet).
23    Empty,
24    /// The stream/partition must be exactly at this version.
25    Exact(u64),
26}
27
28impl ExpectedVersion {
29    pub fn from_next_version(version: u64) -> Self {
30        if version == 0 {
31            ExpectedVersion::Empty
32        } else {
33            ExpectedVersion::Exact(version - 1)
34        }
35    }
36
37    pub fn into_next_version(self) -> Option<u64> {
38        match self {
39            ExpectedVersion::Empty => Some(0),
40            ExpectedVersion::Exact(version) => version.checked_add(1),
41            _ => panic!("expected no stream or exact version"),
42        }
43    }
44
45    /// Calculate the gap between expected and current version.
46    /// Returns VersionGap::None if the expectation is satisfied.
47    pub fn gap_from(self, current: CurrentVersion) -> VersionGap {
48        match (self, current) {
49            // Any version is acceptable
50            (ExpectedVersion::Any, _) => VersionGap::None,
51
52            // Must exist - check if stream has events
53            (ExpectedVersion::Exists, CurrentVersion::Empty) => VersionGap::Incompatible,
54            (ExpectedVersion::Exists, CurrentVersion::Current(_)) => VersionGap::None,
55
56            // Must be empty - check if stream is empty
57            (ExpectedVersion::Empty, CurrentVersion::Empty) => VersionGap::None,
58            (ExpectedVersion::Empty, CurrentVersion::Current(n)) => VersionGap::Ahead(n + 1),
59
60            // Must be at exact version
61            (ExpectedVersion::Exact(expected), CurrentVersion::Empty) => {
62                VersionGap::Behind(expected + 1)
63            }
64            (ExpectedVersion::Exact(expected), CurrentVersion::Current(current)) => {
65                match expected.cmp(&current) {
66                    cmp::Ordering::Equal => VersionGap::None,
67                    cmp::Ordering::Greater => VersionGap::Behind(expected - current),
68                    cmp::Ordering::Less => VersionGap::Ahead(current - expected),
69                }
70            }
71        }
72    }
73
74    /// Check if the current version satisfies the expectation
75    pub fn is_satisfied_by(self, current: CurrentVersion) -> bool {
76        matches!(self.gap_from(current), VersionGap::None)
77    }
78
79    /// Returns true if this version is allowed in strict concurrency mode.
80    /// Only `Empty` and `Exact(_)` are allowed; `Any` and `Exists` are rejected.
81    pub fn is_strict_allowed(&self) -> bool {
82        matches!(self, ExpectedVersion::Empty | ExpectedVersion::Exact(_))
83    }
84}
85
86impl fmt::Display for ExpectedVersion {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match self {
89            ExpectedVersion::Any => write!(f, "any"),
90            ExpectedVersion::Exists => write!(f, "exists"),
91            ExpectedVersion::Empty => write!(f, "empty"),
92            ExpectedVersion::Exact(version) => version.fmt(f),
93        }
94    }
95}
96
97impl str::FromStr for ExpectedVersion {
98    type Err = ParseIntError;
99
100    fn from_str(s: &str) -> Result<Self, Self::Err> {
101        match s {
102            "empty" => Ok(ExpectedVersion::Empty),
103            "any" => Ok(ExpectedVersion::Any),
104            "exists" => Ok(ExpectedVersion::Exists),
105            num_str => {
106                let num = num_str.parse::<u64>()?;
107                Ok(ExpectedVersion::Exact(num))
108            }
109        }
110    }
111}
112
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
114/// Actual position of a stream.
115pub enum CurrentVersion {
116    /// The stream/partition doesn't exist.
117    #[default]
118    Empty,
119    /// The last stream version/partition sequence.
120    Current(u64),
121}
122
123impl CurrentVersion {
124    pub fn next(&self) -> u64 {
125        match self {
126            CurrentVersion::Current(version) => version + 1,
127            CurrentVersion::Empty => 0,
128        }
129    }
130
131    pub fn as_expected_version(&self) -> ExpectedVersion {
132        match self {
133            CurrentVersion::Current(version) => ExpectedVersion::Exact(*version),
134            CurrentVersion::Empty => ExpectedVersion::Empty,
135        }
136    }
137}
138
139impl fmt::Display for CurrentVersion {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        match self {
142            CurrentVersion::Current(version) => version.fmt(f),
143            CurrentVersion::Empty => write!(f, "empty"),
144        }
145    }
146}
147
148impl str::FromStr for CurrentVersion {
149    type Err = ParseIntError;
150
151    fn from_str(s: &str) -> Result<Self, Self::Err> {
152        match s {
153            "empty" => Ok(CurrentVersion::Empty),
154            num_str => {
155                let num = num_str.parse::<u64>()?;
156                Ok(CurrentVersion::Current(num))
157            }
158        }
159    }
160}
161
162impl ops::AddAssign<u64> for CurrentVersion {
163    fn add_assign(&mut self, rhs: u64) {
164        match self {
165            CurrentVersion::Current(current) => *current += rhs,
166            CurrentVersion::Empty => {
167                if rhs > 0 {
168                    *self = CurrentVersion::Current(rhs - 1)
169                }
170            }
171        }
172    }
173}
174
175#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
176pub enum VersionGap {
177    /// No gap - expectation is satisfied
178    #[default]
179    None,
180    /// Stream is ahead by this many versions
181    Ahead(u64),
182    /// Stream is behind by this many versions  
183    Behind(u64),
184    /// Incompatible expectation (e.g., expecting exists but stream is empty)
185    Incompatible,
186}