Skip to main content

windows_file_enumeration_sys/
completion.rs

1// Copyright (c) 2026 Mike Grier
2//! What a receiver observes: entries, and exactly one terminal per enumeration.
3//!
4//! The completion ring carries two kinds of record and no more. In particular a
5//! failure is *inside* its terminal rather than beside it, which is what makes
6//! one reserved slot per accepted enumeration sufficient: reporting a failure
7//! can never need room that a full ring does not have.
8
9use crate::entry::DirectoryEntry;
10use crate::error::EnumerationError;
11
12/// Identifies one accepted enumeration within a session.
13///
14/// Every completion record carries one, because several enumerations may share
15/// a session and their records interleave. Values are unique for the life of the
16/// process, so an identifier retained past its enumeration names nothing rather
17/// than aliasing a later one.
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct EnumerationId(u64);
20
21impl EnumerationId {
22    /// Reconstruct an identifier from a previously observed raw value.
23    #[must_use]
24    pub const fn from_raw(value: u64) -> Self {
25        Self(value)
26    }
27
28    /// The raw value, for logging or for carrying the identity through a
29    /// caller's own data structures.
30    #[must_use]
31    pub const fn get(self) -> u64 {
32        self.0
33    }
34}
35
36impl std::fmt::Display for EnumerationId {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        write!(f, "enumeration {}", self.0)
39    }
40}
41
42/// How one enumeration ended.
43///
44/// Exactly one of these is delivered per accepted enumeration, after every entry
45/// that enumeration produced -- with one deliberate exception: an enumeration
46/// whose receiver has been dropped emits nothing, because no observer remains to
47/// owe an outcome to.
48#[derive(Debug)]
49pub enum TerminalOutcome {
50    /// The directory was enumerated to exhaustion.
51    ///
52    /// Every entry that satisfied the predicate has already been delivered.
53    Completed,
54    /// The enumeration stopped early because it was cancelled.
55    ///
56    /// Entries already queued when cancellation was observed are still
57    /// delivered; cancellation discards only what had not yet been parsed.
58    Cancelled,
59    /// The enumeration stopped early because it failed.
60    ///
61    /// Entries delivered before the failure remain valid: a late failure
62    /// truncates the listing rather than retracting it.
63    Failed(EnumerationError),
64}
65
66impl TerminalOutcome {
67    /// Whether this outcome is [`Completed`](Self::Completed).
68    #[must_use]
69    pub const fn is_completed(&self) -> bool {
70        matches!(self, TerminalOutcome::Completed)
71    }
72
73    /// The failure, if this outcome is [`Failed`](Self::Failed).
74    #[must_use]
75    pub const fn failure(&self) -> Option<&EnumerationError> {
76        match self {
77            TerminalOutcome::Failed(error) => Some(error),
78            _ => None,
79        }
80    }
81}
82
83/// One record taken from a session's completion ring.
84#[derive(Debug)]
85pub enum Completion {
86    /// One directory entry that satisfied its request's predicate.
87    Entry {
88        /// The enumeration that produced it.
89        enumeration: EnumerationId,
90        /// The entry.
91        entry: DirectoryEntry,
92    },
93    /// The single terminal outcome of one enumeration.
94    ///
95    /// No further record for that [`EnumerationId`] follows.
96    Terminal {
97        /// The enumeration that ended.
98        enumeration: EnumerationId,
99        /// How it ended.
100        outcome: TerminalOutcome,
101    },
102}
103
104impl Completion {
105    /// The enumeration this record belongs to.
106    #[must_use]
107    pub const fn enumeration(&self) -> EnumerationId {
108        match self {
109            Completion::Entry { enumeration, .. } | Completion::Terminal { enumeration, .. } => {
110                *enumeration
111            }
112        }
113    }
114
115    /// Whether this record ends its enumeration.
116    #[must_use]
117    pub const fn is_terminal(&self) -> bool {
118        matches!(self, Completion::Terminal { .. })
119    }
120}
121
122#[cfg(test)]
123mod tests;