notify_types/
event.rs

1//! The `Event` type and the hierarchical `EventKind` descriptor.
2
3use std::{
4    fmt,
5    hash::{Hash, Hasher},
6    path::PathBuf,
7};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// An event describing open or close operations on files.
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
16pub enum AccessMode {
17    /// The catch-all case, to be used when the specific kind of event is unknown.
18    Any,
19
20    /// An event emitted when the file is executed, or the folder opened.
21    Execute,
22
23    /// An event emitted when the file is opened for reading.
24    Read,
25
26    /// An event emitted when the file is opened for writing.
27    Write,
28
29    /// An event which specific kind is known but cannot be represented otherwise.
30    Other,
31}
32
33/// An event describing non-mutating access operations on files.
34#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "serde", serde(tag = "kind", content = "mode"))]
37#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
38pub enum AccessKind {
39    /// The catch-all case, to be used when the specific kind of event is unknown.
40    Any,
41
42    /// An event emitted when the file is read.
43    Read,
44
45    /// An event emitted when the file, or a handle to the file, is opened.
46    Open(AccessMode),
47
48    /// An event emitted when the file, or a handle to the file, is closed.
49    Close(AccessMode),
50
51    /// An event which specific kind is known but cannot be represented otherwise.
52    Other,
53}
54
55/// An event describing creation operations on files.
56#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58#[cfg_attr(feature = "serde", serde(tag = "kind"))]
59#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
60pub enum CreateKind {
61    /// The catch-all case, to be used when the specific kind of event is unknown.
62    Any,
63
64    /// An event which results in the creation of a file.
65    File,
66
67    /// An event which results in the creation of a folder.
68    Folder,
69
70    /// An event which specific kind is known but cannot be represented otherwise.
71    Other,
72}
73
74/// An event emitted when the data content of a file is changed.
75#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
78pub enum DataChange {
79    /// The catch-all case, to be used when the specific kind of event is unknown.
80    Any,
81
82    /// An event emitted when the size of the data is changed.
83    Size,
84
85    /// An event emitted when the content of the data is changed.
86    Content,
87
88    /// An event which specific kind is known but cannot be represented otherwise.
89    Other,
90}
91
92/// An event emitted when the metadata of a file or folder is changed.
93#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
94#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
95#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
96pub enum MetadataKind {
97    /// The catch-all case, to be used when the specific kind of event is unknown.
98    Any,
99
100    /// An event emitted when the access time of the file or folder is changed.
101    AccessTime,
102
103    /// An event emitted when the write or modify time of the file or folder is changed.
104    WriteTime,
105
106    /// An event emitted when the permissions of the file or folder are changed.
107    Permissions,
108
109    /// An event emitted when the ownership of the file or folder is changed.
110    Ownership,
111
112    /// An event emitted when an extended attribute of the file or folder is changed.
113    ///
114    /// If the extended attribute's name or type is known, it should be provided in the
115    /// `Info` event attribute.
116    Extended,
117
118    /// An event which specific kind is known but cannot be represented otherwise.
119    Other,
120}
121
122/// An event emitted when the name of a file or folder is changed.
123#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
124#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
125#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
126pub enum RenameMode {
127    /// The catch-all case, to be used when the specific kind of event is unknown.
128    Any,
129
130    /// An event emitted on the file or folder resulting from a rename.
131    To,
132
133    /// An event emitted on the file or folder that was renamed.
134    From,
135
136    /// A single event emitted with both the `From` and `To` paths.
137    ///
138    /// This event should be emitted when both source and target are known. The paths should be
139    /// provided in this exact order (from, to).
140    Both,
141
142    /// An event which specific kind is known but cannot be represented otherwise.
143    Other,
144}
145
146/// An event describing mutation of content, name, or metadata.
147#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
148#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
149#[cfg_attr(feature = "serde", serde(tag = "kind", content = "mode"))]
150#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
151pub enum ModifyKind {
152    /// The catch-all case, to be used when the specific kind of event is unknown.
153    Any,
154
155    /// An event emitted when the data content of a file is changed.
156    Data(DataChange),
157
158    /// An event emitted when the metadata of a file or folder is changed.
159    Metadata(MetadataKind),
160
161    /// An event emitted when the name of a file or folder is changed.
162    #[cfg_attr(feature = "serde", serde(rename = "rename"))]
163    Name(RenameMode),
164
165    /// An event which specific kind is known but cannot be represented otherwise.
166    Other,
167}
168
169/// An event describing removal operations on files.
170#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
171#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
172#[cfg_attr(feature = "serde", serde(tag = "kind"))]
173#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
174pub enum RemoveKind {
175    /// The catch-all case, to be used when the specific kind of event is unknown.
176    Any,
177
178    /// An event emitted when a file is removed.
179    File,
180
181    /// An event emitted when a folder is removed.
182    Folder,
183
184    /// An event which specific kind is known but cannot be represented otherwise.
185    Other,
186}
187
188/// Top-level event kind.
189///
190/// This is arguably the most important classification for events. All subkinds below this one
191/// represent details that may or may not be available for any particular backend, but most tools
192/// and Notify systems will only care about which of these four general kinds an event is about.
193#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
194#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
195#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
196#[cfg_attr(
197    all(feature = "serde", not(feature = "serialization-compat-6")),
198    serde(tag = "type")
199)]
200pub enum EventKind {
201    /// The catch-all event kind, for unsupported/unknown events.
202    ///
203    /// This variant should be used as the "else" case when mapping native kernel bitmasks or
204    /// bitmaps, such that if the mask is ever extended with new event types the backend will not
205    /// gain bugs due to not matching new unknown event types.
206    ///
207    /// This variant is also the default variant used when Notify is in "imprecise" mode.
208    #[default]
209    Any,
210
211    /// An event describing non-mutating access operations on files.
212    ///
213    /// This event is about opening and closing file handles, as well as executing files, and any
214    /// other such event that is about accessing files, folders, or other structures rather than
215    /// mutating them.
216    ///
217    /// Only some platforms are capable of generating these.
218    Access(AccessKind),
219
220    /// An event describing creation operations on files.
221    ///
222    /// This event is about the creation of files, folders, or other structures but not about e.g.
223    /// writing new content into them.
224    Create(CreateKind),
225
226    /// An event describing mutation of content, name, or metadata.
227    ///
228    /// This event is about the mutation of files', folders', or other structures' content, name
229    /// (path), or associated metadata (attributes).
230    Modify(ModifyKind),
231
232    /// An event describing removal operations on files.
233    ///
234    /// This event is about the removal of files, folders, or other structures but not e.g. erasing
235    /// content from them. This may also be triggered for renames/moves that move files _out of the
236    /// watched subpath_.
237    ///
238    /// Some editors also trigger Remove events when saving files as they may opt for removing (or
239    /// renaming) the original then creating a new file in-place.
240    Remove(RemoveKind),
241
242    /// An event not fitting in any of the above four categories.
243    ///
244    /// This may be used for meta-events about the watch itself.
245    Other,
246}
247
248impl EventKind {
249    /// Indicates whether an event is an Access variant.
250    #[must_use]
251    pub fn is_access(&self) -> bool {
252        matches!(self, EventKind::Access(_))
253    }
254
255    /// Indicates whether an event is a Create variant.
256    #[must_use]
257    pub fn is_create(&self) -> bool {
258        matches!(self, EventKind::Create(_))
259    }
260
261    /// Indicates whether an event is a Modify variant.
262    #[must_use]
263    pub fn is_modify(&self) -> bool {
264        matches!(self, EventKind::Modify(_))
265    }
266
267    /// Indicates whether an event is a Remove variant.
268    #[must_use]
269    pub fn is_remove(&self) -> bool {
270        matches!(self, EventKind::Remove(_))
271    }
272
273    /// Indicates whether an event is an Other variant.
274    #[must_use]
275    pub fn is_other(&self) -> bool {
276        matches!(self, EventKind::Other)
277    }
278}
279
280/// Notify event.
281///
282/// You might want to check [`Event::need_rescan`] to make sure no event was missed before you
283/// received this one.
284#[derive(Clone)]
285#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
286pub struct Event {
287    /// Kind or type of the event.
288    ///
289    /// This is a hierarchy of enums describing the event as precisely as possible. All enums in
290    /// the hierarchy have two variants always present, `Any` and `Other`, accompanied by one or
291    /// more specific variants.
292    ///
293    /// `Any` should be used when more detail about the event is not known beyond the variant
294    /// already selected. For example, `AccessMode::Any` means a file has been accessed, but that's
295    /// all we know.
296    ///
297    /// `Other` should be used when more detail _is_ available, but cannot be encoded as one of the
298    /// defined variants. When specifying `Other`, the event attributes should contain an `Info`
299    /// entry with a short string identifying this detail. That string is to be considered part of
300    /// the interface of the backend (i.e. a change should probably be breaking).
301    ///
302    /// For example, `CreateKind::Other` with an `Info("mount")` may indicate the binding of a
303    /// mount. The documentation of the particular backend should indicate if any `Other` events
304    /// are generated, and what their description means.
305    ///
306    /// The `EventKind::Any` variant should be used as the "else" case when mapping native kernel
307    /// bitmasks or bitmaps, such that if the mask is ever extended with new event types the
308    /// backend will not gain bugs due to not matching new unknown event types.
309    #[cfg_attr(
310        all(feature = "serde", not(feature = "serialization-compat-6")),
311        serde(flatten)
312    )]
313    #[cfg_attr(
314        all(feature = "serde", feature = "serialization-compat-6"),
315        serde(rename = "type")
316    )]
317    pub kind: EventKind,
318
319    /// Paths the event is about, if known.
320    ///
321    /// If an event concerns two or more paths, and the paths are known at the time of event
322    /// creation, they should all go in this `Vec`. Otherwise, using the `Tracker` attr may be more
323    /// appropriate.
324    ///
325    /// The order of the paths is likely to be significant! For example, renames where both ends of
326    /// the name change are known will have the "source" path first, and the "target" path last.
327    pub paths: Vec<PathBuf>,
328
329    // "What should be in the struct" and "what can go in the attrs" is an interesting question.
330    //
331    // Technically, the paths could go in the attrs. That would reduce the type size to 4 pointer
332    // widths, instead of 7 like it is now. Anything 8 and below is probably good — on x64 that's
333    // the size of an L1 cache line. The entire kind classification fits in 3 bytes, and an AnyMap
334    // is 3 pointers. A Vec<PathBuf> is another 3 pointers.
335    //
336    // Type size aside, what's behind these structures? A Vec and a PathBuf is stored on the heap.
337    // An AnyMap is stored on the heap. But a Vec is directly there, requiring about one access to
338    // get, while retrieving anything in the AnyMap requires some accesses as overhead.
339    //
340    // So things that are used often should be on the struct, and things that are used more rarely
341    // should go in the attrs. Additionally, arbitrary data can _only_ go in the attrs.
342    //
343    // The kind and the paths vie for first place on this scale, depending on how downstream wishes
344    // to use the information. Everything else is secondary. So far, that's why paths live here.
345    //
346    // In the future, it might be possible to have more data and to benchmark things properly, so
347    // the performance can be actually quantified. Also, it might turn out that I have no idea what
348    // I was talking about, so the above may be discarded or reviewed. We'll see!
349    //
350    /// Additional attributes of the event.
351    ///
352    /// Arbitrary data may be added to this field, without restriction beyond the `Sync` and
353    /// `Clone` properties. Some data added here is considered for comparing and hashing, but not
354    /// all: at this writing this is `Tracker`, `Flag`, `Info`, and `Source`.
355    #[cfg_attr(feature = "serde", serde(default))]
356    pub attrs: EventAttributes,
357}
358
359/// Additional attributes of the event.
360#[derive(Clone, Default, Debug)]
361#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
362pub struct EventAttributes {
363    #[cfg_attr(feature = "serde", serde(flatten))]
364    inner: Option<Box<EventAttributesInner>>,
365}
366
367#[derive(Clone, Default, Debug)]
368#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
369struct EventAttributesInner {
370    /// Tracking ID for events that are related.
371    ///
372    /// For events generated by backends with the `TrackRelated` capability. Those backends _may_
373    /// emit events that are related to each other, and tag those with an identical "tracking id"
374    /// or "cookie". The value is normalised to `usize`.
375    #[cfg_attr(
376        feature = "serde",
377        serde(default, skip_serializing_if = "Option::is_none")
378    )]
379    tracker: Option<usize>,
380
381    /// Special Notify flag on the event.
382    #[cfg_attr(
383        feature = "serde",
384        serde(default, skip_serializing_if = "Option::is_none")
385    )]
386    flag: Option<Flag>,
387
388    /// Additional information on the event.
389    ///
390    /// This is to be used for all `Other` variants of the event kind hierarchy. The variant
391    /// indicates that a consumer should look into the `attrs` for an `Info` value; if that value
392    /// is missing it should be considered a backend bug.
393    ///
394    /// This attribute may also be present for non-`Other` variants of the event kind, if doing so
395    /// provides useful precision. For example, the `Modify(Metadata(Extended))` kind suggests
396    /// using this attribute when information about _what_ extended metadata changed is available.
397    ///
398    /// This should be a short string, and changes may be considered breaking.
399    #[cfg_attr(
400        feature = "serde",
401        serde(default, skip_serializing_if = "Option::is_none")
402    )]
403    info: Option<String>,
404
405    /// The source of the event.
406    ///
407    /// In most cases this should be a short string, identifying the backend unambiguously. In some
408    /// cases this may be dynamically generated, but should contain a prefix to make it unambiguous
409    /// between backends.
410    #[cfg_attr(
411        feature = "serde",
412        serde(default, skip_serializing_if = "Option::is_none")
413    )]
414    source: Option<String>,
415
416    /// The process ID of the originator of the event.
417    ///
418    /// This attribute is experimental and, while included in Notify itself, is not considered
419    /// stable or standard enough to be part of the serde, eq, hash, and debug representations.
420    #[cfg_attr(
421        feature = "serde",
422        serde(default, skip_serializing, skip_deserializing)
423    )]
424    process_id: Option<u32>,
425}
426
427impl EventAttributes {
428    /// Creates a new `EventAttributes`.
429    #[must_use]
430    pub fn new() -> Self {
431        Self { inner: None }
432    }
433
434    /// Retrieves the tracker ID for an event directly, if present.
435    #[must_use]
436    pub fn tracker(&self) -> Option<usize> {
437        self.inner.as_ref().and_then(|inner| inner.tracker)
438    }
439
440    /// Retrieves the Notify flag for an event directly, if present.
441    #[must_use]
442    pub fn flag(&self) -> Option<Flag> {
443        self.inner.as_ref().and_then(|inner| inner.flag)
444    }
445
446    /// Retrieves the additional info for an event directly, if present.
447    #[must_use]
448    pub fn info(&self) -> Option<&str> {
449        self.inner.as_ref().and_then(|inner| inner.info.as_deref())
450    }
451
452    /// Retrieves the source for an event directly, if present.
453    #[must_use]
454    pub fn source(&self) -> Option<&str> {
455        self.inner
456            .as_ref()
457            .and_then(|inner| inner.source.as_deref())
458    }
459
460    /// The process ID of the originator of the event.
461    ///
462    /// This attribute is experimental and, while included in Notify itself, is not considered
463    /// stable or standard enough to be part of the serde, eq, hash, and debug representations.
464    #[must_use]
465    pub fn process_id(&self) -> Option<u32> {
466        self.inner.as_ref().and_then(|inner| inner.process_id)
467    }
468
469    /// Sets the tracker.
470    pub fn set_tracker(&mut self, tracker: usize) {
471        self.inner_mut().tracker = Some(tracker);
472    }
473
474    /// Sets the Notify flag onto the event.
475    pub fn set_flag(&mut self, flag: Flag) {
476        self.inner_mut().flag = Some(flag);
477    }
478
479    /// Sets additional info onto the event.
480    pub fn set_info(&mut self, info: &str) {
481        self.inner_mut().info = Some(info.to_string());
482    }
483
484    /// Sets the process id onto the event.
485    pub fn set_process_id(&mut self, process_id: u32) {
486        self.inner_mut().process_id = Some(process_id);
487    }
488
489    fn inner_mut(&mut self) -> &mut EventAttributesInner {
490        self.inner.get_or_insert_with(Box::default)
491    }
492}
493
494/// Special Notify flag on the event.
495///
496/// This attribute is used to flag certain kinds of events that Notify either marks or generates in
497/// particular ways.
498#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
499#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
500#[cfg_attr(
501    all(feature = "serde", not(feature = "serialization-compat-6")),
502    serde(rename_all = "camelCase")
503)]
504pub enum Flag {
505    /// Rescan notices are emitted by some platforms (and may also be emitted by Notify itself).
506    /// They indicate either a lapse in the events or a change in the filesystem such that events
507    /// received so far can no longer be relied on to represent the state of the filesystem now.
508    ///
509    /// An application that simply reacts to file changes may not care about this. An application
510    /// that keeps an in-memory representation of the filesystem will need to care, and will need
511    /// to refresh that representation directly from the filesystem.
512    Rescan,
513}
514
515impl Event {
516    /// Returns whether some events may have been missed. If true, you should assume any file or
517    /// folder might have been modified.
518    ///
519    /// See [`Flag::Rescan`] for more information.
520    #[must_use]
521    pub fn need_rescan(&self) -> bool {
522        matches!(self.flag(), Some(Flag::Rescan))
523    }
524    /// Retrieves the tracker ID for an event directly, if present.
525    #[must_use]
526    pub fn tracker(&self) -> Option<usize> {
527        self.attrs.tracker()
528    }
529
530    /// Retrieves the Notify flag for an event directly, if present.
531    #[must_use]
532    pub fn flag(&self) -> Option<Flag> {
533        self.attrs.flag()
534    }
535
536    /// Retrieves the additional info for an event directly, if present.
537    #[must_use]
538    pub fn info(&self) -> Option<&str> {
539        self.attrs.info()
540    }
541
542    /// Retrieves the source for an event directly, if present.
543    #[must_use]
544    pub fn source(&self) -> Option<&str> {
545        self.attrs.source()
546    }
547
548    /// Creates a new `Event` given a kind.
549    #[must_use]
550    pub fn new(kind: EventKind) -> Self {
551        Self {
552            kind,
553            paths: Vec::new(),
554            attrs: EventAttributes::new(),
555        }
556    }
557
558    /// Sets the kind.
559    #[must_use]
560    pub fn set_kind(mut self, kind: EventKind) -> Self {
561        self.kind = kind;
562        self
563    }
564
565    /// Adds a path to the event.
566    #[must_use]
567    pub fn add_path(mut self, path: PathBuf) -> Self {
568        self.paths.push(path);
569        self
570    }
571
572    /// Adds a path to the event if the argument is Some.
573    #[must_use]
574    pub fn add_some_path(self, path: Option<PathBuf>) -> Self {
575        if let Some(path) = path {
576            self.add_path(path)
577        } else {
578            self
579        }
580    }
581
582    /// Sets the tracker.
583    #[must_use]
584    pub fn set_tracker(mut self, tracker: usize) -> Self {
585        self.attrs.set_tracker(tracker);
586        self
587    }
588
589    /// Sets additional info onto the event.
590    #[must_use]
591    pub fn set_info(mut self, info: &str) -> Self {
592        self.attrs.set_info(info);
593        self
594    }
595
596    /// Sets the Notify flag onto the event.
597    #[must_use]
598    pub fn set_flag(mut self, flag: Flag) -> Self {
599        self.attrs.set_flag(flag);
600        self
601    }
602
603    /// Sets the process id onto the event.
604    #[must_use]
605    pub fn set_process_id(mut self, process_id: u32) -> Self {
606        self.attrs.set_process_id(process_id);
607        self
608    }
609}
610
611#[expect(
612    clippy::missing_fields_in_debug,
613    reason = "attr fields are shown individually"
614)]
615impl fmt::Debug for Event {
616    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
617        f.debug_struct("Event")
618            .field("kind", &self.kind)
619            .field("paths", &self.paths)
620            .field("attr:tracker", &self.tracker())
621            .field("attr:flag", &self.flag())
622            .field("attr:info", &self.info())
623            .field("attr:source", &self.source())
624            .finish()
625    }
626}
627impl Default for Event {
628    fn default() -> Self {
629        Self {
630            kind: EventKind::default(),
631            paths: Vec::new(),
632            attrs: EventAttributes::new(),
633        }
634    }
635}
636
637impl Eq for Event {}
638impl PartialEq for Event {
639    fn eq(&self, other: &Self) -> bool {
640        self.kind.eq(&other.kind)
641            && self.paths.eq(&other.paths)
642            && self.tracker().eq(&other.tracker())
643            && self.flag().eq(&other.flag())
644            && self.info().eq(&other.info())
645            && self.source().eq(&other.source())
646    }
647}
648
649impl Hash for Event {
650    fn hash<H: Hasher>(&self, state: &mut H) {
651        self.kind.hash(state);
652        self.paths.hash(state);
653        self.tracker().hash(state);
654        self.flag().hash(state);
655        self.info().hash(state);
656        self.source().hash(state);
657    }
658}
659
660#[cfg(all(test, feature = "serde", not(feature = "serialization-compat-6")))]
661mod tests {
662    use super::*;
663
664    use insta::assert_snapshot;
665    use rstest::rstest;
666
667    #[rustfmt::skip]
668    #[rstest]
669    #[case("any", EventKind::Any)]
670    #[case("access-any", EventKind::Access(AccessKind::Any))]
671    #[case("access-read", EventKind::Access(AccessKind::Read))]
672    #[case("access-open-any", EventKind::Access(AccessKind::Open(AccessMode::Any)))]
673    #[case("access-open-execute", EventKind::Access(AccessKind::Open(AccessMode::Execute)))]
674    #[case("access-open-read", EventKind::Access(AccessKind::Open(AccessMode::Read)))]
675    #[case("access-open-write", EventKind::Access(AccessKind::Open(AccessMode::Write)))]
676    #[case("access-open-other", EventKind::Access(AccessKind::Open(AccessMode::Other)))]
677    #[case("access-close-any", EventKind::Access(AccessKind::Close(AccessMode::Any)))]
678    #[case("access-close-execute", EventKind::Access(AccessKind::Close(AccessMode::Execute)))]
679    #[case("access-close-read", EventKind::Access(AccessKind::Close(AccessMode::Read)))]
680    #[case("access-close-write", EventKind::Access(AccessKind::Close(AccessMode::Write)))]
681    #[case("access-close-other", EventKind::Access(AccessKind::Close(AccessMode::Other)))]
682    #[case("access-other", EventKind::Access(AccessKind::Other))]
683    #[case("create-any", EventKind::Create(CreateKind::Any))]
684    #[case("create-file", EventKind::Create(CreateKind::File))]
685    #[case("create-folder", EventKind::Create(CreateKind::Folder))]
686    #[case("create-other", EventKind::Create(CreateKind::Other))]
687    #[case("modify-any", EventKind::Modify(ModifyKind::Any))]
688    #[case("modify-data-any", EventKind::Modify(ModifyKind::Data(DataChange::Any)))]
689    #[case("modify-data-size", EventKind::Modify(ModifyKind::Data(DataChange::Size)))]
690    #[case("modify-data-content", EventKind::Modify(ModifyKind::Data(DataChange::Content)))]
691    #[case("modify-data-other", EventKind::Modify(ModifyKind::Data(DataChange::Other)))]
692    #[case("modify-metadata-any", EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)))]
693    #[case("modify-metadata-accesstime", EventKind::Modify(ModifyKind::Metadata(MetadataKind::AccessTime)))]
694    #[case("modify-metadata-writetime", EventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime)))]
695    #[case("modify-metadata-permissions", EventKind::Modify(ModifyKind::Metadata(MetadataKind::Permissions)))]
696    #[case("modify-metadata-ownership", EventKind::Modify(ModifyKind::Metadata(MetadataKind::Ownership)))]
697    #[case("modify-metadata-extended", EventKind::Modify(ModifyKind::Metadata(MetadataKind::Extended)))]
698    #[case("modify-metadata-other", EventKind::Modify(ModifyKind::Metadata(MetadataKind::Other)))]
699    #[case("modify-name-any", EventKind::Modify(ModifyKind::Name(RenameMode::Any)))]
700    #[case("modify-name-to", EventKind::Modify(ModifyKind::Name(RenameMode::To)))]
701    #[case("modify-name-from", EventKind::Modify(ModifyKind::Name(RenameMode::From)))]
702    #[case("modify-name-both", EventKind::Modify(ModifyKind::Name(RenameMode::Both)))]
703    #[case("modify-name-other", EventKind::Modify(ModifyKind::Name(RenameMode::Other)))]
704    #[case("modify-other", EventKind::Modify(ModifyKind::Other))]
705    #[case("remove-any", EventKind::Remove(RemoveKind::Any))]
706    #[case("remove-file", EventKind::Remove(RemoveKind::File))]
707    #[case("remove-folder", EventKind::Remove(RemoveKind::Folder))]
708    #[case("remove-other", EventKind::Remove(RemoveKind::Other))]
709    #[case("other", EventKind::Other)]
710    fn serialize_event_kind(
711        #[case] name: &str,
712        #[case] event_kind: EventKind,
713    ) {
714        let event = Event::new(event_kind);
715        let json = serde_json::to_string(&event).unwrap();
716        assert_snapshot!(name, json);
717    }
718
719    #[test]
720    fn serialize_event_with_attrs() {
721        let event = Event::new(EventKind::Any)
722            .set_tracker(123)
723            .set_flag(Flag::Rescan)
724            .set_info("test event")
725            .set_process_id(0);
726        let json = serde_json::to_string(&event).unwrap();
727        assert_snapshot!(json);
728    }
729}