distant_protocol/common/
change.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::iter::FromIterator;
5use std::ops::{BitOr, Sub};
6use std::path::PathBuf;
7use std::str::FromStr;
8
9use derive_more::{Deref, DerefMut, IntoIterator};
10use serde::{Deserialize, Serialize};
11use strum::{EnumString, EnumVariantNames, VariantNames};
12
13/// Change to a path on the filesystem.
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case", deny_unknown_fields)]
16pub struct Change {
17    /// Unix timestamp (in seconds) when the server was notified of this change (not when the
18    /// change occurred)
19    pub timestamp: u64,
20
21    /// Label describing the kind of change
22    pub kind: ChangeKind,
23
24    /// Path that was changed
25    pub path: PathBuf,
26
27    /// Additional details associated with the change
28    #[serde(default, skip_serializing_if = "ChangeDetails::is_empty")]
29    pub details: ChangeDetails,
30}
31
32/// Optional details about a change.
33#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(default, rename_all = "snake_case", deny_unknown_fields)]
35pub struct ChangeDetails {
36    /// Clarity on type of attribute change that occurred (for kind == attribute).
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub attribute: Option<ChangeDetailsAttribute>,
39
40    /// When event is renaming, this will be populated with the resulting name
41    /// when we know both the old and new names (for kind == rename)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub renamed: Option<PathBuf>,
44
45    /// Unix timestamps (in seconds) related to the change. For other platforms, their timestamps
46    /// are converted into a Unix timestamp format.
47    ///
48    /// * For create events, this represents the `ctime` field from stat (or equivalent on other platforms).
49    /// * For modify events, this represents the `mtime` field from stat (or equivalent on other platforms).
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub timestamp: Option<u64>,
52
53    /// Optional information about the change that is typically platform-specific.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub extra: Option<String>,
56}
57
58impl ChangeDetails {
59    /// Returns true if no details are contained within.
60    pub fn is_empty(&self) -> bool {
61        self.attribute.is_none()
62            && self.renamed.is_none()
63            && self.timestamp.is_none()
64            && self.extra.is_none()
65    }
66}
67
68/// Specific details about modification
69#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case", deny_unknown_fields)]
71pub enum ChangeDetailsAttribute {
72    Ownership,
73    Permissions,
74    Timestamp,
75}
76
77/// Represents a label attached to a [`Change`] that describes the kind of change.
78///
79/// This mirrors events seen from `incron`.
80#[derive(
81    Copy,
82    Clone,
83    Debug,
84    strum::Display,
85    EnumString,
86    EnumVariantNames,
87    Hash,
88    PartialEq,
89    Eq,
90    PartialOrd,
91    Ord,
92    Serialize,
93    Deserialize,
94)]
95#[serde(rename_all = "snake_case", deny_unknown_fields)]
96#[strum(serialize_all = "snake_case")]
97pub enum ChangeKind {
98    /// A file was read
99    Access,
100
101    /// A file's or directory's attributes were changed
102    Attribute,
103
104    /// A file open for writing was closed
105    CloseWrite,
106
107    /// A file not open for writing was closed
108    CloseNoWrite,
109
110    /// A file, directory, or something else was created within a watched directory
111    Create,
112
113    /// A file, directory, or something else was deleted
114    Delete,
115
116    /// A file's content was modified
117    Modify,
118
119    /// A file was opened
120    Open,
121
122    /// A file, directory, or something else was renamed in some way
123    Rename,
124
125    /// Catch-all for any other change
126    Unknown,
127}
128
129impl ChangeKind {
130    /// Returns a list of all variants as str names
131    pub const fn variants() -> &'static [&'static str] {
132        Self::VARIANTS
133    }
134
135    /// Returns a list of all variants as a vec
136    pub fn all() -> Vec<ChangeKind> {
137        ChangeKindSet::all().into_sorted_vec()
138    }
139
140    /// Returns true if kind is part of the access family.
141    pub fn is_access(&self) -> bool {
142        matches!(
143            self,
144            Self::Access | Self::CloseWrite | Self::CloseNoWrite | Self::Open
145        )
146    }
147
148    /// Returns true if kind is part of the create family.
149    pub fn is_create(&self) -> bool {
150        matches!(self, Self::Create)
151    }
152
153    /// Returns true if kind is part of the delete family.
154    pub fn is_delete(&self) -> bool {
155        matches!(self, Self::Delete)
156    }
157
158    /// Returns true if kind is part of the modify family.
159    pub fn is_modify(&self) -> bool {
160        matches!(self, Self::Attribute | Self::Modify)
161    }
162
163    /// Returns true if kind is part of the rename family.
164    pub fn is_rename(&self) -> bool {
165        matches!(self, Self::Rename)
166    }
167
168    /// Returns true if kind is unknown.
169    pub fn is_unknown(&self) -> bool {
170        matches!(self, Self::Unknown)
171    }
172}
173
174impl BitOr for ChangeKind {
175    type Output = ChangeKindSet;
176
177    fn bitor(self, rhs: Self) -> Self::Output {
178        let mut set = ChangeKindSet::empty();
179        set.insert(self);
180        set.insert(rhs);
181        set
182    }
183}
184
185/// Represents a distinct set of different change kinds
186#[derive(Clone, Debug, Deref, DerefMut, IntoIterator, Serialize, Deserialize)]
187pub struct ChangeKindSet(HashSet<ChangeKind>);
188
189impl ChangeKindSet {
190    pub fn new(set: impl IntoIterator<Item = ChangeKind>) -> Self {
191        set.into_iter().collect()
192    }
193
194    /// Produces an empty set of [`ChangeKind`]
195    pub fn empty() -> Self {
196        Self(HashSet::new())
197    }
198
199    /// Produces a set of all [`ChangeKind`]
200    pub fn all() -> Self {
201        vec![
202            ChangeKind::Access,
203            ChangeKind::Attribute,
204            ChangeKind::CloseWrite,
205            ChangeKind::CloseNoWrite,
206            ChangeKind::Create,
207            ChangeKind::Delete,
208            ChangeKind::Modify,
209            ChangeKind::Open,
210            ChangeKind::Rename,
211            ChangeKind::Unknown,
212        ]
213        .into_iter()
214        .collect()
215    }
216
217    /// Consumes set and returns a sorted vec of the kinds of changes
218    pub fn into_sorted_vec(self) -> Vec<ChangeKind> {
219        let mut v = self.0.into_iter().collect::<Vec<_>>();
220        v.sort();
221        v
222    }
223}
224
225impl fmt::Display for ChangeKindSet {
226    /// Outputs a comma-separated series of [`ChangeKind`] as string that are sorted
227    /// such that this will always be consistent output
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        let mut kinds = self
230            .0
231            .iter()
232            .map(ToString::to_string)
233            .collect::<Vec<String>>();
234        kinds.sort_unstable();
235        write!(f, "{}", kinds.join(","))
236    }
237}
238
239impl PartialEq for ChangeKindSet {
240    fn eq(&self, other: &Self) -> bool {
241        self.to_string() == other.to_string()
242    }
243}
244
245impl Eq for ChangeKindSet {}
246
247impl Hash for ChangeKindSet {
248    /// Hashes based on the output of [`fmt::Display`]
249    fn hash<H: Hasher>(&self, state: &mut H) {
250        self.to_string().hash(state);
251    }
252}
253
254impl BitOr<ChangeKindSet> for ChangeKindSet {
255    type Output = Self;
256
257    fn bitor(mut self, rhs: ChangeKindSet) -> Self::Output {
258        self.extend(rhs.0);
259        self
260    }
261}
262
263impl BitOr<ChangeKind> for ChangeKindSet {
264    type Output = Self;
265
266    fn bitor(mut self, rhs: ChangeKind) -> Self::Output {
267        self.0.insert(rhs);
268        self
269    }
270}
271
272impl BitOr<ChangeKindSet> for ChangeKind {
273    type Output = ChangeKindSet;
274
275    fn bitor(self, rhs: ChangeKindSet) -> Self::Output {
276        rhs | self
277    }
278}
279
280impl Sub<ChangeKindSet> for ChangeKindSet {
281    type Output = Self;
282
283    fn sub(self, other: Self) -> Self::Output {
284        ChangeKindSet(&self.0 - &other.0)
285    }
286}
287
288impl Sub<&'_ ChangeKindSet> for &ChangeKindSet {
289    type Output = ChangeKindSet;
290
291    fn sub(self, other: &ChangeKindSet) -> Self::Output {
292        ChangeKindSet(&self.0 - &other.0)
293    }
294}
295
296impl FromStr for ChangeKindSet {
297    type Err = strum::ParseError;
298
299    fn from_str(s: &str) -> Result<Self, Self::Err> {
300        let mut change_set = HashSet::new();
301
302        for word in s.split(',') {
303            change_set.insert(ChangeKind::from_str(word.trim())?);
304        }
305
306        Ok(ChangeKindSet(change_set))
307    }
308}
309
310impl FromIterator<ChangeKind> for ChangeKindSet {
311    fn from_iter<I: IntoIterator<Item = ChangeKind>>(iter: I) -> Self {
312        let mut change_set = HashSet::new();
313
314        for i in iter {
315            change_set.insert(i);
316        }
317
318        ChangeKindSet(change_set)
319    }
320}
321
322impl From<ChangeKind> for ChangeKindSet {
323    fn from(change_kind: ChangeKind) -> Self {
324        let mut set = Self::empty();
325        set.insert(change_kind);
326        set
327    }
328}
329
330impl From<Vec<ChangeKind>> for ChangeKindSet {
331    fn from(changes: Vec<ChangeKind>) -> Self {
332        changes.into_iter().collect()
333    }
334}
335
336impl Default for ChangeKindSet {
337    fn default() -> Self {
338        Self::empty()
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    mod change_kind_set {
347        use super::*;
348
349        #[test]
350        fn should_be_able_to_serialize_to_json() {
351            let set = ChangeKindSet::new([ChangeKind::CloseWrite]);
352
353            let value = serde_json::to_value(set).unwrap();
354            assert_eq!(value, serde_json::json!(["close_write"]));
355        }
356
357        #[test]
358        fn should_be_able_to_deserialize_from_json() {
359            let value = serde_json::json!(["close_write"]);
360
361            let set: ChangeKindSet = serde_json::from_value(value).unwrap();
362            assert_eq!(set, ChangeKindSet::new([ChangeKind::CloseWrite]));
363        }
364
365        #[test]
366        fn should_be_able_to_serialize_to_msgpack() {
367            let set = ChangeKindSet::new([ChangeKind::CloseWrite]);
368
369            // NOTE: We don't actually check the output here because it's an implementation detail
370            // and could change as we change how serialization is done. This is merely to verify
371            // that we can serialize since there are times when serde fails to serialize at
372            // runtime.
373            let _ = rmp_serde::encode::to_vec_named(&set).unwrap();
374        }
375
376        #[test]
377        fn should_be_able_to_deserialize_from_msgpack() {
378            // NOTE: It may seem odd that we are serializing just to deserialize, but this is to
379            // verify that we are not corrupting or causing issues when serializing on a
380            // client/server and then trying to deserialize on the other side. This has happened
381            // enough times with minor changes that we need tests to verify.
382            let buf =
383                rmp_serde::encode::to_vec_named(&ChangeKindSet::new([ChangeKind::CloseWrite]))
384                    .unwrap();
385
386            let set: ChangeKindSet = rmp_serde::decode::from_slice(&buf).unwrap();
387            assert_eq!(set, ChangeKindSet::new([ChangeKind::CloseWrite]));
388        }
389    }
390
391    mod change_kind {
392        use super::*;
393
394        #[test]
395        fn should_be_able_to_serialize_to_json() {
396            let kind = ChangeKind::CloseWrite;
397
398            let value = serde_json::to_value(kind).unwrap();
399            assert_eq!(value, serde_json::json!("close_write"));
400        }
401
402        #[test]
403        fn should_be_able_to_deserialize_from_json() {
404            let value = serde_json::json!("close_write");
405
406            let kind: ChangeKind = serde_json::from_value(value).unwrap();
407            assert_eq!(kind, ChangeKind::CloseWrite);
408        }
409
410        #[test]
411        fn should_be_able_to_serialize_to_msgpack() {
412            let kind = ChangeKind::CloseWrite;
413
414            // NOTE: We don't actually check the output here because it's an implementation detail
415            // and could change as we change how serialization is done. This is merely to verify
416            // that we can serialize since there are times when serde fails to serialize at
417            // runtime.
418            let _ = rmp_serde::encode::to_vec_named(&kind).unwrap();
419        }
420
421        #[test]
422        fn should_be_able_to_deserialize_from_msgpack() {
423            // NOTE: It may seem odd that we are serializing just to deserialize, but this is to
424            // verify that we are not corrupting or causing issues when serializing on a
425            // client/server and then trying to deserialize on the other side. This has happened
426            // enough times with minor changes that we need tests to verify.
427            let buf = rmp_serde::encode::to_vec_named(&ChangeKind::CloseWrite).unwrap();
428
429            let kind: ChangeKind = rmp_serde::decode::from_slice(&buf).unwrap();
430            assert_eq!(kind, ChangeKind::CloseWrite);
431        }
432    }
433}