windows_namespace_request_sys/request.rs
1// Copyright (c) Mike Grier.
2
3//! The seam: what every entry has in common, as a trait.
4//!
5//! Each entry is already a value whose `perform` is the single point where
6//! Win32 is touched. This module adds the trait over that, so a consumer's code
7//! can be written against "a request that produces `T`" rather than against a
8//! concrete entry -- and can therefore be exercised in that consumer's own
9//! tests without a filesystem, a network path, or a device that may not be
10//! present.
11//!
12//! # Why two traits rather than one
13//!
14//! The distinction is real, not cosmetic. An open is a **parameter set**: it
15//! may be performed repeatedly, producing an independent handle each time, so
16//! it takes `&self`. A close is **one-shot**: performing it consumes the
17//! request, which is what makes closing twice through this crate impossible.
18//!
19//! Collapsing them into one trait would have to pick a side, and both choices
20//! lie. A `&self` trait would make a close look repeatable; a `self` trait would
21//! make every open look single-use and force a caller to rebuild a request it
22//! could simply have performed again.
23//!
24//! # Why the error type is an associated type
25//!
26//! Most entries fail only as Windows failed, so their error is a
27//! [`Win32Error`](crate::Win32Error). Two do not:
28//! [`crate::final_path::QueryFinalPath`] and
29//! [`crate::full_path::ResolveFullPath`] each retry a growing buffer, and "the
30//! required size kept changing" is a failure Win32 has no code for. They report
31//! it as [`FinalPathError::Unstable`](crate::FinalPathError::Unstable) and
32//! [`FullPathError::Unstable`](crate::FullPathError::Unstable) respectively.
33//!
34//! Fixing the trait's error to `Win32Error` would have left those entries
35//! outside the seam, which would make the seam not level -- a consumer could
36//! substitute a fake for some entries and not the rest. An associated `Error`
37//! keeps every entry reachable through one trait without any of them having to
38//! invent a code it does not have.
39//!
40//! That last clause is load-bearing rather than decorative. `ResolveFullPath`
41//! did invent one for a while, returning a synthesized
42//! `ERROR_INSUFFICIENT_BUFFER` that Win32 can also produce by itself, so a
43//! caller could not tell the crate's own retry giving up from a genuine Windows
44//! failure. The rule stated here is what the entry now follows.
45//!
46//! # This is a seam, not an abstraction layer
47//!
48//! The traits exist so a *consumer* can substitute a fake. They are not a
49//! plug-in point for alternative implementations of Windows, and nothing in
50//! this crate dispatches through them: the entries keep their inherent
51//! `perform` methods, which is what an ordinary caller uses.
52
53/// A request that may be performed more than once.
54///
55/// Implemented by the entries that carry parameters and produce something new
56/// each time: [`crate::open::OpenFile`],
57/// [`crate::open_by_id::OpenFileByIdentifier`], and
58/// [`crate::watch::WatchDirectory`].
59///
60/// # Example
61///
62/// A consumer writes its own code against the trait, then tests it against a
63/// fake that never touches the filesystem:
64///
65/// ```
66/// use windows_namespace_request_sys::outcome::Outcome;
67/// use windows_namespace_request_sys::request::Request;
68/// use windows_namespace_request_sys::Win32Error;
69/// use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
70///
71/// // The consumer's code: generic over the request, so it can be exercised
72/// // without opening anything.
73/// fn count_successes<R: Request>(requests: &[R], attempts: usize) -> usize {
74/// requests
75/// .iter()
76/// .flat_map(|request| (0..attempts).map(move |_| request.perform()))
77/// .filter(Result::is_ok)
78/// .count()
79/// }
80///
81/// // The consumer's fake: a canned outcome, no Win32 anywhere.
82/// struct AlwaysMissing;
83///
84/// impl Request for AlwaysMissing {
85/// type Error = Win32Error;
86/// type Output = ();
87///
88/// fn perform(&self) -> Outcome<()> {
89/// Err(Win32Error::from_code(ERROR_FILE_NOT_FOUND))
90/// }
91/// }
92///
93/// struct AlwaysOpens;
94///
95/// impl Request for AlwaysOpens {
96/// type Error = Win32Error;
97/// type Output = u32;
98///
99/// fn perform(&self) -> Outcome<u32> {
100/// Ok(7)
101/// }
102/// }
103///
104/// assert_eq!(count_successes(&[AlwaysMissing, AlwaysMissing], 3), 0);
105/// assert_eq!(count_successes(&[AlwaysOpens], 3), 3, "a request may be performed repeatedly");
106/// ```
107pub trait Request {
108 /// What performing the request produces.
109 type Output;
110
111 /// How performing it can fail.
112 ///
113 /// [`Win32Error`](crate::Win32Error) for every entry that fails only as
114 /// Windows failed. The two that also retry a growing buffer --
115 /// [`QueryFinalPath`](crate::final_path::QueryFinalPath) and
116 /// [`ResolveFullPath`](crate::full_path::ResolveFullPath) -- carry their own
117 /// error instead, as the module documentation explains.
118 type Error;
119
120 /// Performs the request on the calling thread.
121 ///
122 /// # Errors
123 ///
124 /// Returns the raw Win32 code, unaltered, per this crate's
125 /// faithful-execution contract -- or, for an entry with a failure Win32 has
126 /// no code for, that entry's own error.
127 fn perform(&self) -> Result<Self::Output, Self::Error>;
128}
129
130/// A request that is consumed by performing it.
131///
132/// Implemented by [`crate::close::CloseRequest`], where performing twice would
133/// mean closing a handle twice. The trait carries that property rather than
134/// leaving it to a comment.
135///
136/// # Example
137///
138/// ```
139/// use windows_namespace_request_sys::outcome::Outcome;
140/// use windows_namespace_request_sys::request::ConsumingRequest;
141/// use windows_namespace_request_sys::Win32Error;
142///
143/// // A consumer's cleanup step, written against the trait.
144/// fn perform_all<R: ConsumingRequest>(requests: Vec<R>) -> usize {
145/// requests
146/// .into_iter()
147/// .filter(|_| true)
148/// .map(ConsumingRequest::perform)
149/// .filter(Result::is_ok)
150/// .count()
151/// }
152///
153/// struct FakeClose;
154///
155/// impl ConsumingRequest for FakeClose {
156/// type Error = Win32Error;
157/// type Output = ();
158///
159/// fn perform(self) -> Outcome<()> {
160/// Ok(())
161/// }
162/// }
163///
164/// assert_eq!(perform_all(vec![FakeClose, FakeClose]), 2);
165/// ```
166pub trait ConsumingRequest {
167 /// What performing the request produces.
168 type Output;
169
170 /// How performing it can fail.
171 type Error;
172
173 /// Performs the request on the calling thread, consuming it.
174 ///
175 /// # Errors
176 ///
177 /// Returns the raw Win32 code, unaltered.
178 fn perform(self) -> Result<Self::Output, Self::Error>;
179}
180
181#[cfg(test)]
182mod tests;