1pub mod error;
8
9use std::{cmp, fmt, num::ParseIntError, ops, str};
10
11pub use error::ErrorCode;
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
16pub enum ExpectedVersion {
17 #[default]
19 Any,
20 Exists,
22 Empty,
24 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 pub fn gap_from(self, current: CurrentVersion) -> VersionGap {
48 match (self, current) {
49 (ExpectedVersion::Any, _) => VersionGap::None,
51
52 (ExpectedVersion::Exists, CurrentVersion::Empty) => VersionGap::Incompatible,
54 (ExpectedVersion::Exists, CurrentVersion::Current(_)) => VersionGap::None,
55
56 (ExpectedVersion::Empty, CurrentVersion::Empty) => VersionGap::None,
58 (ExpectedVersion::Empty, CurrentVersion::Current(n)) => VersionGap::Ahead(n + 1),
59
60 (ExpectedVersion::Exact(expected), CurrentVersion::Empty) => {
62 VersionGap::Behind(expected + 1)
63 }
64 (ExpectedVersion::Exact(expected), CurrentVersion::Current(current)) => {
65 match expected.cmp(¤t) {
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 pub fn is_satisfied_by(self, current: CurrentVersion) -> bool {
76 matches!(self.gap_from(current), VersionGap::None)
77 }
78
79 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)]
114pub enum CurrentVersion {
116 #[default]
118 Empty,
119 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 #[default]
179 None,
180 Ahead(u64),
182 Behind(u64),
184 Incompatible,
186}