Skip to main content

use_change_event/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6macro_rules! string_newtype {
7    ($(#[$meta:meta])* $name:ident) => {
8        $(#[$meta])*
9        #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10        pub struct $name(String);
11
12        impl $name {
13            /// Creates a new string-backed primitive.
14            pub fn new(value: impl Into<String>) -> Self {
15                Self(value.into())
16            }
17
18            /// Returns the stored string value.
19            pub fn as_str(&self) -> &str {
20                &self.0
21            }
22        }
23
24        impl AsRef<str> for $name {
25            fn as_ref(&self) -> &str {
26                self.as_str()
27            }
28        }
29
30        impl From<String> for $name {
31            fn from(value: String) -> Self {
32                Self::new(value)
33            }
34        }
35
36        impl From<&str> for $name {
37            fn from(value: &str) -> Self {
38                Self::new(value)
39            }
40        }
41
42        impl fmt::Display for $name {
43            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44                formatter.write_str(self.as_str())
45            }
46        }
47    };
48}
49
50string_newtype! {
51    /// A change event identifier.
52    ChangeEventId
53}
54string_newtype! {
55    /// A change stream cursor.
56    ChangeCursor
57}
58string_newtype! {
59    /// A change stream resume token.
60    ResumeToken
61}
62string_newtype! {
63    /// A changed document reference.
64    ChangedDocument
65}
66
67/// A change sequence number.
68#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
69pub struct ChangeSequence(u64);
70
71impl ChangeSequence {
72    /// Creates a change sequence value.
73    pub const fn new(value: u64) -> Self {
74        Self(value)
75    }
76
77    /// Returns the sequence value.
78    pub const fn value(self) -> u64 {
79        self.0
80    }
81}
82
83impl fmt::Display for ChangeSequence {
84    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85        write!(formatter, "{}", self.0)
86    }
87}
88
89/// Change event kind labels.
90#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
91pub enum ChangeEventKind {
92    Insert,
93    Update,
94    Delete,
95    Replace,
96    Upsert,
97    Expire,
98    #[default]
99    Unknown,
100}
101
102impl ChangeEventKind {
103    /// Returns a stable lowercase label.
104    pub const fn as_str(self) -> &'static str {
105        match self {
106            Self::Insert => "insert",
107            Self::Update => "update",
108            Self::Delete => "delete",
109            Self::Replace => "replace",
110            Self::Upsert => "upsert",
111            Self::Expire => "expire",
112            Self::Unknown => "unknown",
113        }
114    }
115}
116
117impl fmt::Display for ChangeEventKind {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        formatter.write_str(self.as_str())
120    }
121}
122
123/// A vendor-neutral change event.
124#[derive(Clone, Debug, Eq, PartialEq)]
125pub struct ChangeEvent {
126    id: ChangeEventId,
127    kind: ChangeEventKind,
128    document: ChangedDocument,
129    cursor: Option<ChangeCursor>,
130    resume_token: Option<ResumeToken>,
131    sequence: Option<ChangeSequence>,
132}
133
134impl ChangeEvent {
135    /// Creates a change event.
136    pub fn new(id: ChangeEventId, kind: ChangeEventKind, document: impl Into<String>) -> Self {
137        Self {
138            id,
139            kind,
140            document: ChangedDocument::new(document),
141            cursor: None,
142            resume_token: None,
143            sequence: None,
144        }
145    }
146
147    /// Sets the cursor.
148    pub fn with_cursor(mut self, cursor: ChangeCursor) -> Self {
149        self.cursor = Some(cursor);
150        self
151    }
152
153    /// Sets the resume token.
154    pub fn with_resume_token(mut self, resume_token: ResumeToken) -> Self {
155        self.resume_token = Some(resume_token);
156        self
157    }
158
159    /// Sets the sequence.
160    pub const fn with_sequence(mut self, sequence: ChangeSequence) -> Self {
161        self.sequence = Some(sequence);
162        self
163    }
164
165    /// Returns the event identifier.
166    pub const fn id(&self) -> &ChangeEventId {
167        &self.id
168    }
169
170    /// Returns the event kind.
171    pub const fn kind(&self) -> ChangeEventKind {
172        self.kind
173    }
174
175    /// Returns the changed document reference.
176    pub const fn document(&self) -> &ChangedDocument {
177        &self.document
178    }
179
180    /// Returns the cursor, if present.
181    pub const fn cursor(&self) -> Option<&ChangeCursor> {
182        self.cursor.as_ref()
183    }
184
185    /// Returns the resume token, if present.
186    pub const fn resume_token(&self) -> Option<&ResumeToken> {
187        self.resume_token.as_ref()
188    }
189
190    /// Returns the sequence, if present.
191    pub const fn sequence(&self) -> Option<ChangeSequence> {
192        self.sequence
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::{
199        ChangeCursor, ChangeEvent, ChangeEventId, ChangeEventKind, ChangeSequence, ResumeToken,
200    };
201
202    #[test]
203    fn formats_change_event_kinds() {
204        assert_eq!(ChangeEventKind::Insert.to_string(), "insert");
205        assert_eq!(ChangeEventKind::Update.to_string(), "update");
206        assert_eq!(ChangeEventKind::Delete.to_string(), "delete");
207        assert_eq!(ChangeEventKind::Unknown.to_string(), "unknown");
208    }
209
210    #[test]
211    fn builds_change_events() {
212        let event = ChangeEvent::new(
213            ChangeEventId::new("evt_1"),
214            ChangeEventKind::Replace,
215            "customer_123",
216        )
217        .with_cursor(ChangeCursor::new("cursor_1"))
218        .with_resume_token(ResumeToken::new("token_1"))
219        .with_sequence(ChangeSequence::new(42));
220
221        assert_eq!(event.id().as_str(), "evt_1");
222        assert_eq!(event.kind(), ChangeEventKind::Replace);
223        assert_eq!(event.document().as_ref(), "customer_123");
224        assert_eq!(event.sequence(), Some(ChangeSequence::new(42)));
225    }
226}