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
80impl fmt::Display for ExpectedVersion {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            ExpectedVersion::Any => write!(f, "any"),
84            ExpectedVersion::Exists => write!(f, "exists"),
85            ExpectedVersion::Empty => write!(f, "empty"),
86            ExpectedVersion::Exact(version) => version.fmt(f),
87        }
88    }
89}
90
91impl str::FromStr for ExpectedVersion {
92    type Err = ParseIntError;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        match s {
96            "empty" => Ok(ExpectedVersion::Empty),
97            "any" => Ok(ExpectedVersion::Any),
98            "exists" => Ok(ExpectedVersion::Exists),
99            num_str => {
100                let num = num_str.parse::<u64>()?;
101                Ok(ExpectedVersion::Exact(num))
102            }
103        }
104    }
105}
106
107#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
108/// Actual position of a stream.
109pub enum CurrentVersion {
110    /// The stream/partition doesn't exist.
111    Empty,
112    /// The last stream version/partition sequence.
113    Current(u64),
114}
115
116impl CurrentVersion {
117    pub fn next(&self) -> u64 {
118        match self {
119            CurrentVersion::Current(version) => version + 1,
120            CurrentVersion::Empty => 0,
121        }
122    }
123
124    pub fn as_expected_version(&self) -> ExpectedVersion {
125        match self {
126            CurrentVersion::Current(version) => ExpectedVersion::Exact(*version),
127            CurrentVersion::Empty => ExpectedVersion::Empty,
128        }
129    }
130}
131
132impl fmt::Display for CurrentVersion {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        match self {
135            CurrentVersion::Current(version) => version.fmt(f),
136            CurrentVersion::Empty => write!(f, "empty"),
137        }
138    }
139}
140
141impl str::FromStr for CurrentVersion {
142    type Err = ParseIntError;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        match s {
146            "empty" => Ok(CurrentVersion::Empty),
147            num_str => {
148                let num = num_str.parse::<u64>()?;
149                Ok(CurrentVersion::Current(num))
150            }
151        }
152    }
153}
154
155impl ops::AddAssign<u64> for CurrentVersion {
156    fn add_assign(&mut self, rhs: u64) {
157        match self {
158            CurrentVersion::Current(current) => *current += rhs,
159            CurrentVersion::Empty => {
160                if rhs > 0 {
161                    *self = CurrentVersion::Current(rhs - 1)
162                }
163            }
164        }
165    }
166}
167
168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub enum VersionGap {
170    /// No gap - expectation is satisfied
171    None,
172    /// Stream is ahead by this many versions
173    Ahead(u64),
174    /// Stream is behind by this many versions  
175    Behind(u64),
176    /// Incompatible expectation (e.g., expecting exists but stream is empty)
177    Incompatible,
178}