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;