Skip to main content

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}