Skip to main content

notify_win_types/
event.rs

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