qubit_fs/spi/async_file_system_spi.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8// facade.
9//! Runtime-neutral asynchronous provider implementation contract.
10
11use super::CopyAttempt;
12use super::CopyDeclineReason;
13use super::CopyRequest;
14use super::CreateDirectoryRequest;
15use super::CreateTempDirectoryRequest;
16use super::CreateTempFileRequest;
17use super::DeleteDirectoryRequest;
18use super::DeleteFileRequest;
19use super::ListRequest;
20use super::OpenReaderRequest;
21use super::OpenWriterRequest;
22use super::OpenedAsyncDirectoryStream;
23use super::OpenedAsyncReader;
24use super::OpenedAsyncTempDirectory;
25use super::OpenedAsyncTempFile;
26use super::OpenedAsyncWriter;
27use super::ProviderProperties;
28use super::RenameRequest;
29use super::SpiCopyFailure;
30use super::SpiFuture;
31use super::SpiRenameFailure;
32use super::StatRequest;
33use super::StatResponse;
34use crate::directory::CreateDirectoryOutcome;
35use crate::directory::DeleteOutcome;
36use crate::error::FsEffectState;
37use crate::error::FsError;
38use crate::error::FsErrorKind;
39use crate::error::FsOperation;
40use crate::error::FsResult;
41use crate::rename::RenameFailureState;
42use crate::rename::RenameOutcome;
43
44/// Object-safe asynchronous provider implementation contract.
45///
46/// Operation futures perform provider I/O only while polled. Dropping a
47/// pending future cancels local polling but does not imply that remote work was
48/// rolled back; mutation failures must preserve confirmed progress in their
49/// typed failure state.
50///
51/// # Examples
52///
53/// ```
54/// use qubit_fs::spi::AsyncFileSystemSpi;
55///
56/// fn accepts_provider<T: AsyncFileSystemSpi>() {}
57/// ```
58pub trait AsyncFileSystemSpi: Send + Sync {
59 /// Returns one immutable provider property snapshot without asynchronous
60 /// I/O.
61 ///
62 /// # Returns
63 /// The provider's immutable property snapshot.
64 fn properties(&self) -> ProviderProperties;
65
66 /// Asynchronously reads metadata for a validated request.
67 ///
68 /// # Parameters
69 /// - `request`: Facade-validated stat request.
70 ///
71 /// # Returns
72 /// A future resolving to path-bound metadata.
73 ///
74 /// # Errors
75 /// Resolves to the provider lookup failure with filesystem context.
76 fn stat<'a>(&'a self, request: StatRequest<'a>) -> SpiFuture<'a, FsResult<StatResponse>>;
77
78 /// Asynchronously opens a directory stream.
79 ///
80 /// # Parameters
81 /// - `request`: Facade-validated list request.
82 ///
83 /// # Returns
84 /// A future resolving to an opened enumeration session.
85 ///
86 /// # Errors
87 /// Resolves to the provider open failure with filesystem context.
88 fn list<'a>(&'a self, request: ListRequest<'a>) -> SpiFuture<'a, FsResult<OpenedAsyncDirectoryStream>> {
89 Box::pin(async move {
90 Err(match request.scope().path() {
91 Some(path) => unsupported(FsOperation::List, path),
92 None => FsError::new(
93 FsErrorKind::UnsupportedOperation,
94 FsOperation::List,
95 "provider operation is not supported",
96 ),
97 })
98 })
99 }
100
101 /// Asynchronously opens a reader.
102 ///
103 /// # Parameters
104 /// - `request`: Facade-validated reader request.
105 ///
106 /// # Returns
107 /// A future resolving to an identity-bound reader.
108 ///
109 /// # Errors
110 /// Resolves to the provider open failure with filesystem context.
111 fn open_reader<'a>(&'a self, request: OpenReaderRequest<'a>) -> SpiFuture<'a, FsResult<OpenedAsyncReader>> {
112 Box::pin(async move { Err(unsupported(FsOperation::OpenReader, request.path())) })
113 }
114
115 /// Asynchronously opens a writer.
116 ///
117 /// An unsuccessful open must describe its external effects. Attach
118 /// `FsEffectState::Unchanged` only when no mutation occurred and no cleanup
119 /// responsibility remains. Missing evidence is conservatively indeterminate
120 /// in aggregate operations, including an `AlreadyExists` skip request.
121 ///
122 /// # Parameters
123 /// - `request`: Facade-validated writer request.
124 ///
125 /// # Returns
126 /// A future resolving to an identity-bound writer.
127 ///
128 /// # Errors
129 /// Resolves to the provider open failure with filesystem context.
130 fn open_writer<'a>(&'a self, request: OpenWriterRequest<'a>) -> SpiFuture<'a, FsResult<OpenedAsyncWriter>> {
131 Box::pin(async move {
132 Err(unsupported(FsOperation::OpenWriter, request.path()).with_effect_state(FsEffectState::Unchanged))
133 })
134 }
135
136 /// Asynchronously creates a directory.
137 ///
138 /// # Parameters
139 /// - `request`: Facade-validated directory-creation request.
140 ///
141 /// # Returns
142 /// A future resolving to the confirmed creation outcome.
143 ///
144 /// # Errors
145 /// Resolves to the provider creation failure with filesystem context.
146 fn create_directory<'a>(
147 &'a self,
148 request: CreateDirectoryRequest<'a>,
149 ) -> SpiFuture<'a, FsResult<CreateDirectoryOutcome>> {
150 Box::pin(async move { Err(unsupported(FsOperation::CreateDir, request.path())) })
151 }
152
153 /// Asynchronously deletes a file.
154 ///
155 /// # Parameters
156 /// - `request`: Facade-validated file-deletion request.
157 ///
158 /// # Returns
159 /// A future resolving to the confirmed deletion outcome.
160 ///
161 /// # Errors
162 /// Resolves to the provider deletion failure with filesystem context.
163 fn delete_file<'a>(&'a self, request: DeleteFileRequest<'a>) -> SpiFuture<'a, FsResult<DeleteOutcome>> {
164 Box::pin(async move { Err(unsupported(FsOperation::Delete, request.path())) })
165 }
166
167 /// Asynchronously deletes a directory.
168 ///
169 /// # Parameters
170 /// - `request`: Facade-validated directory-deletion request.
171 ///
172 /// # Returns
173 /// A future resolving to the confirmed deletion outcome.
174 ///
175 /// # Errors
176 /// Resolves to the provider deletion failure with filesystem context.
177 fn delete_directory<'a>(&'a self, request: DeleteDirectoryRequest<'a>) -> SpiFuture<'a, FsResult<DeleteOutcome>> {
178 Box::pin(async move { Err(unsupported(FsOperation::Delete, request.path())) })
179 }
180
181 /// Attempts an optional native asynchronous copy primitive.
182 ///
183 /// # Parameters
184 /// - `_request`: Facade-validated copy request.
185 ///
186 /// # Returns
187 /// A future resolving to a completed outcome or a typed decline reason.
188 ///
189 /// # Errors
190 /// Resolves to a typed failure preserving confirmed publication progress.
191 #[inline]
192 fn try_copy<'a>(&'a self, _request: CopyRequest<'a>) -> SpiFuture<'a, Result<CopyAttempt, SpiCopyFailure>> {
193 Box::pin(async { Ok(CopyAttempt::Declined(CopyDeclineReason::NotImplemented)) })
194 }
195
196 /// Asynchronously renames a resource.
197 ///
198 /// # Parameters
199 /// - `request`: Facade-validated rename request.
200 ///
201 /// # Returns
202 /// A future resolving to the confirmed rename outcome.
203 ///
204 /// # Errors
205 /// Resolves to a typed failure preserving confirmed rename progress.
206 fn rename<'a>(&'a self, request: RenameRequest<'a>) -> SpiFuture<'a, Result<RenameOutcome, SpiRenameFailure>> {
207 Box::pin(async move {
208 Err(SpiRenameFailure::new(
209 unsupported(FsOperation::Rename, request.source()).with_target(request.target().clone()),
210 RenameFailureState::Unchanged,
211 ))
212 })
213 }
214
215 /// Asynchronously creates a temporary file.
216 ///
217 /// # Parameters
218 /// - `request`: Validated temporary-file creation request.
219 ///
220 /// # Returns
221 /// A future resolving to an identity-bound temporary-file session.
222 ///
223 /// # Errors
224 /// Resolves to the provider creation failure with filesystem context.
225 fn create_temp_file<'a>(&'a self, _request: CreateTempFileRequest) -> SpiFuture<'a, FsResult<OpenedAsyncTempFile>> {
226 Box::pin(async {
227 Err(FsError::new(
228 FsErrorKind::UnsupportedOperation,
229 FsOperation::CreateTemp,
230 "provider does not implement this operation",
231 ))
232 })
233 }
234
235 /// Asynchronously creates a temporary directory.
236 ///
237 /// # Parameters
238 /// - `request`: Validated temporary-directory creation request.
239 ///
240 /// # Returns
241 /// A future resolving to an identity-bound temporary-directory session.
242 ///
243 /// # Errors
244 /// Resolves to the provider creation failure with filesystem context.
245 fn create_temp_directory<'a>(
246 &'a self,
247 _request: CreateTempDirectoryRequest,
248 ) -> SpiFuture<'a, FsResult<OpenedAsyncTempDirectory>> {
249 Box::pin(async {
250 Err(FsError::new(
251 FsErrorKind::UnsupportedOperation,
252 FsOperation::CreateTemp,
253 "provider does not implement this operation",
254 ))
255 })
256 }
257}
258
259/// Builds a standard unsupported-operation error for a validated path request.
260fn unsupported(operation: FsOperation, path: &crate::path::Path) -> FsError {
261 FsError::new(
262 FsErrorKind::UnsupportedOperation,
263 operation,
264 "provider does not implement this operation",
265 )
266 .with_path(path.clone())
267}