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