1use std::sync::Arc;
11
12use crate::copy::CopyAssessment;
13use crate::copy::CopyFailure;
14use crate::copy::CopyOperation;
15use crate::copy::CopyOptions;
16use crate::copy::CopyOutcome;
17use crate::directory::CreateDirectoryOptions;
18use crate::directory::CreateDirectoryOutcome;
19use crate::directory::DeleteOptions;
20use crate::directory::DeleteOutcome;
21use crate::directory::DirectoryOperation;
22use crate::directory::DirectoryStream;
23use crate::directory::ListOptions;
24use crate::directory::ListScope;
25use crate::error::FsError;
26use crate::error::FsErrorKind;
27use crate::error::FsOperation;
28use crate::error::FsResult;
29use crate::error::OpenFailure;
30use crate::error::OpenFailureStage;
31use crate::facade::facade_core::FacadeCore;
32use crate::metadata::FileMetadata;
33use crate::metadata::FileSystemCapability;
34use crate::metadata::FileSystemProperties;
35use crate::path::Path;
36use crate::read::FileReader;
37use crate::read::ReadOperation;
38use crate::read::ReadOptions;
39use crate::rename::RenameFailure;
40use crate::rename::RenameFailureState;
41use crate::rename::RenameOptions;
42use crate::rename::RenameOutcome;
43use crate::rename::validate_rename_outcome;
44use crate::spi::CreateDirectoryRequest;
45use crate::spi::DeleteDirectoryRequest;
46use crate::spi::DeleteFileRequest;
47use crate::spi::FileSystemSpi;
48use crate::spi::OpenReaderRequest;
49use crate::spi::OpenWriterRequest;
50use crate::spi::RenameRequest;
51use crate::spi::ResolvedCreateDirectoryOptions;
52use crate::spi::ResolvedDeleteOptions;
53use crate::spi::ResolvedReadOptions;
54use crate::spi::ResolvedRenameOptions;
55use crate::spi::ResolvedWriteOptions;
56use crate::spi::StatRequest;
57use crate::temp::RejectedTempResource;
58use crate::temp::TempDirectory;
59use crate::temp::TempFile;
60use crate::temp::TempOptions;
61use crate::write::FileWriter;
62use crate::write::RejectedWriter;
63use crate::write::WriteAllFailure;
64use crate::write::WriteOperation;
65use crate::write::WriteOptions;
66
67#[derive(Clone)]
86pub struct FileSystem {
87 spi: Arc<dyn FileSystemSpi>,
89 core: Arc<FacadeCore>,
91}
92
93impl FileSystem {
94 #[inline]
97 pub fn from_spi<S>(spi: S) -> FsResult<Self>
98 where
99 S: FileSystemSpi + 'static,
100 {
101 Self::from_shared_spi(Arc::new(spi))
102 }
103
104 #[inline]
106 pub fn from_shared_spi(spi: Arc<dyn FileSystemSpi>) -> FsResult<Self> {
107 let core = FacadeCore::new(spi.properties())?;
108 Ok(Self {
109 spi,
110 core: Arc::new(core),
111 })
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn properties(&self) -> &FileSystemProperties {
118 self.core.properties()
119 }
120
121 pub fn validate_write(&self, path: &Path, options: &WriteOptions) -> FsResult<()> {
123 self.core.validate_write_request(path, options)
124 }
125
126 pub fn assess_copy(&self, source: &Path, target: &Path, options: &CopyOptions) -> FsResult<CopyAssessment> {
132 self.core.assess_copy(source, target, options)
133 }
134
135 #[inline]
137 pub(crate) fn core(&self) -> &FacadeCore {
138 &self.core
139 }
140
141 #[inline]
143 pub(crate) fn spi(&self) -> &dyn FileSystemSpi {
144 self.spi.as_ref()
145 }
146
147 pub fn stat(&self, path: &Path) -> FsResult<FileMetadata> {
153 self.validate_path(path, FsOperation::Stat)?;
154 let response = self
155 .spi
156 .stat(StatRequest::new(path, ()))
157 .map_err(|error| self.enrich(error, path, FsOperation::Stat))?;
158 if response.path() != path {
159 return Err(self.contract_error(
160 path,
161 FsOperation::ValidateProviderOutcome,
162 "provider returned metadata for a different path",
163 ));
164 }
165 Ok(response.into_metadata())
166 }
167
168 pub fn exists(&self, path: &Path) -> FsResult<bool> {
174 match self.stat(path) {
175 Ok(_) => Ok(true),
176 Err(error) if error.kind() == FsErrorKind::NotFound => Ok(false),
177 Err(error) => Err(error.with_operation(FsOperation::Exists)),
178 }
179 }
180
181 #[allow(clippy::result_large_err)]
189 pub fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<CopyOutcome, CopyFailure> {
190 CopyOperation::new(self, source, target, options).execute()
191 }
192
193 pub fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<RenameOutcome, RenameFailure> {
199 if let Err(error) = self.rename_preflight(source, target, &options) {
200 return Err(self.contextualize_rename_failure(
201 RenameFailure::new(error, RenameFailureState::Unchanged),
202 source,
203 target,
204 ));
205 }
206 match self.spi.rename(RenameRequest::new(
207 source,
208 target,
209 ResolvedRenameOptions::new(options.clone()),
210 )) {
211 Ok(outcome) => match validate_rename_outcome(&outcome, &options, source, target) {
212 Some(violation) => Err(RenameFailure::new(
213 self.contract_error(source, FsOperation::Rename, violation.message)
214 .with_target(target.clone()),
215 violation.state,
216 )),
217 None => Ok(outcome),
218 },
219 Err(failure) => {
220 let (error, state) = failure.into_parts();
221 Err(RenameFailure::new(
222 error.with_operation(FsOperation::Rename).with_missing_context(
223 source,
224 Some(target),
225 self.properties().info().provider_id(),
226 ),
227 state,
228 ))
229 }
230 }
231 }
232
233 pub fn list(&self, scope: &ListScope, options: ListOptions) -> FsResult<DirectoryStream> {
239 DirectoryOperation::new(self).list(scope, options)
240 }
241
242 pub fn open_reader(&self, path: &Path, options: ReadOptions) -> FsResult<FileReader> {
248 self.open_reader_resolved(path, ResolvedReadOptions::new(options))
249 }
250
251 pub(crate) fn open_reader_resolved(&self, path: &Path, resolved: ResolvedReadOptions) -> FsResult<FileReader> {
253 let options = resolved.options().clone();
254 self.validate_path(path, FsOperation::OpenReader)?;
255 options
256 .validate_against(self.properties().capabilities())
257 .map_err(|error| self.enrich(error, path, FsOperation::OpenReader))?;
258 self.properties()
259 .limits()
260 .validate_read_range(path, options.length())
261 .map_err(|error| self.enrich(error, path, FsOperation::OpenReader))?;
262 self.require(FileSystemCapability::Read, FsOperation::OpenReader, path)?;
263 self.spi
264 .open_reader(OpenReaderRequest::new(path, resolved))
265 .and_then(|opened| {
266 let (info, reader) = opened.into_parts();
267 self.validate_opened_info(&info, path)?;
268 Ok(FileReader::new(info, reader))
269 })
270 .map_err(|error| self.enrich(error, path, FsOperation::OpenReader))
271 }
272
273 pub fn open_writer(&self, path: &Path, options: WriteOptions) -> Result<FileWriter, OpenFailure<RejectedWriter>> {
280 self.core
281 .validate_write_request(path, &options)
282 .map_err(|error| OpenFailure::new(error, OpenFailureStage::Preflight, None))?;
283 let atomicity = options.atomicity();
284 let durability = options.durability();
285 let opened = self
286 .spi
287 .open_writer(OpenWriterRequest::new(path, ResolvedWriteOptions::new(options)))
288 .map_err(|error| {
289 OpenFailure::new(
290 self.enrich(error, path, FsOperation::OpenWriter),
291 OpenFailureStage::ProviderOpen,
292 None,
293 )
294 })?;
295 let (info, session) = opened.into_parts();
296 if let Err(error) = self.validate_opened_info(&info, path) {
297 return Err(OpenFailure::new(
298 error,
299 OpenFailureStage::OutcomeValidation,
300 Some(RejectedWriter::new(
301 session,
302 self.properties().info().provider_id(),
303 Some(path.clone()),
304 )),
305 ));
306 }
307 Ok(FileWriter::new(
308 info,
309 session,
310 atomicity,
311 durability,
312 self.properties().info().provider_id(),
313 self.properties().limits().max_write_bytes().maximum(),
314 ))
315 }
316
317 pub fn create_temp_file(&self, options: TempOptions) -> Result<TempFile, OpenFailure<RejectedTempResource>> {
323 let parent = options.parent().cloned();
324 self.core
325 .validate_temp_parent(parent.as_ref())
326 .map_err(|error| OpenFailure::new(error, OpenFailureStage::Preflight, None))?;
327 self.core
328 .require(FileSystemCapability::TempFile, FsOperation::CreateTemp, parent.as_ref())
329 .map_err(|error| OpenFailure::new(error, OpenFailureStage::Preflight, None))?;
330 let opened = self
331 .spi
332 .create_temp_file(crate::spi::CreateTempFileRequest::new(options))
333 .map_err(|error| {
334 OpenFailure::new(
335 self.core.enrich(error, parent.as_ref(), FsOperation::CreateTemp),
336 OpenFailureStage::ProviderOpen,
337 None,
338 )
339 })?;
340 let (info, session) = opened.into_parts();
341 if let Err(cause) = self.validate_temp_info(&info, crate::metadata::FileKind::File) {
342 let error = FsError::with_source(
343 FsErrorKind::ProviderContractViolation,
344 FsOperation::ValidateProviderOutcome,
345 "provider returned an invalid temporary identity",
346 cause,
347 )
348 .with_provider(self.properties().info().provider_id());
349 let error = match parent.as_ref() {
350 Some(path) => error.with_path(path.clone()),
351 None => error,
352 };
353 return Err(OpenFailure::new(
354 error,
355 OpenFailureStage::OutcomeValidation,
356 Some(RejectedTempResource::new(
357 session,
358 self.properties().info().provider_id(),
359 parent,
360 )),
361 ));
362 }
363 Ok(TempFile::new(self.clone(), info.path().clone(), session))
364 }
365
366 pub fn create_temp_directory(
373 &self,
374 options: TempOptions,
375 ) -> Result<TempDirectory, OpenFailure<RejectedTempResource>> {
376 let parent = options.parent().cloned();
377 self.core
378 .validate_temp_parent(parent.as_ref())
379 .map_err(|error| OpenFailure::new(error, OpenFailureStage::Preflight, None))?;
380 self.core
381 .require(
382 FileSystemCapability::TempDirectory,
383 FsOperation::CreateTemp,
384 parent.as_ref(),
385 )
386 .map_err(|error| OpenFailure::new(error, OpenFailureStage::Preflight, None))?;
387 let opened = self
388 .spi
389 .create_temp_directory(crate::spi::CreateTempDirectoryRequest::new(options))
390 .map_err(|error| {
391 OpenFailure::new(
392 self.core.enrich(error, parent.as_ref(), FsOperation::CreateTemp),
393 OpenFailureStage::ProviderOpen,
394 None,
395 )
396 })?;
397 let (info, session) = opened.into_parts();
398 if let Err(cause) = self.validate_temp_info(&info, crate::metadata::FileKind::Directory) {
399 let error = FsError::with_source(
400 FsErrorKind::ProviderContractViolation,
401 FsOperation::ValidateProviderOutcome,
402 "provider returned an invalid temporary identity",
403 cause,
404 )
405 .with_provider(self.properties().info().provider_id());
406 let error = match parent.as_ref() {
407 Some(path) => error.with_path(path.clone()),
408 None => error,
409 };
410 return Err(OpenFailure::new(
411 error,
412 OpenFailureStage::OutcomeValidation,
413 Some(RejectedTempResource::new(
414 session,
415 self.properties().info().provider_id(),
416 parent,
417 )),
418 ));
419 }
420 Ok(TempDirectory::new(self.clone(), info.path().clone(), session))
421 }
422
423 pub fn create_directory(&self, path: &Path, options: CreateDirectoryOptions) -> FsResult<CreateDirectoryOutcome> {
429 self.validate_path(path, FsOperation::CreateDir)?;
430 self.require(FileSystemCapability::CreateDirectory, FsOperation::CreateDir, path)?;
431 let exists_ok = options.exists_ok();
432 let outcome = self
433 .spi
434 .create_directory(CreateDirectoryRequest::new(
435 path,
436 ResolvedCreateDirectoryOptions::new(options),
437 ))
438 .map_err(|error| self.enrich(error, path, FsOperation::CreateDir))?;
439 if outcome.already_existed() && !exists_ok {
440 return Err(self.contract_error(
441 path,
442 FsOperation::CreateDir,
443 "provider accepted an existing directory without exists_ok",
444 ));
445 }
446 Ok(outcome)
447 }
448
449 #[inline]
455 pub fn delete_file(&self, path: &Path, options: DeleteOptions) -> FsResult<DeleteOutcome> {
456 self.delete(path, options, false)
457 }
458
459 #[inline]
465 pub fn delete_directory(&self, path: &Path, options: DeleteOptions) -> FsResult<DeleteOutcome> {
466 self.delete(path, options, true)
467 }
468
469 fn delete(&self, path: &Path, options: DeleteOptions, directory: bool) -> FsResult<DeleteOutcome> {
471 self.validate_path(path, FsOperation::Delete)?;
472 options
473 .validate_against(self.properties().capabilities())
474 .map_err(|error| self.enrich(error, path, FsOperation::Delete))?;
475 self.require(FileSystemCapability::Delete, FsOperation::Delete, path)?;
476 let missing_ok = options.missing_ok();
477 let outcome = if directory {
478 self.spi
479 .delete_directory(DeleteDirectoryRequest::new(path, ResolvedDeleteOptions::new(options)))
480 } else {
481 self.spi
482 .delete_file(DeleteFileRequest::new(path, ResolvedDeleteOptions::new(options)))
483 }
484 .map_err(|error| self.enrich(error, path, FsOperation::Delete))?;
485 if outcome.already_missing() && !missing_ok {
486 return Err(self.contract_error(
487 path,
488 FsOperation::Delete,
489 "provider accepted a missing target without missing_ok",
490 ));
491 }
492 Ok(outcome)
493 }
494
495 fn rename_preflight(&self, source: &Path, target: &Path, options: &RenameOptions) -> FsResult<()> {
497 self.validate_path(source, FsOperation::Rename)?;
498 self.validate_path(target, FsOperation::Rename)?;
499 options
500 .validate_against(self.properties().capabilities())
501 .map_err(|error| {
502 self.enrich(error, source, FsOperation::Rename)
503 .with_target(target.clone())
504 })?;
505 self.require(FileSystemCapability::Rename, FsOperation::Rename, source)?;
506 if source == target {
507 return Err(FsError::new(
508 FsErrorKind::InvalidOptions,
509 FsOperation::Rename,
510 "rename source and target must differ",
511 )
512 .with_path(source.clone())
513 .with_target(target.clone()));
514 }
515 Ok(())
516 }
517
518 fn contextualize_rename_failure(&self, failure: RenameFailure, source: &Path, target: &Path) -> RenameFailure {
521 let (error, state) = failure.into_parts();
522 RenameFailure::new(
523 error.with_missing_context(source, Some(target), self.properties().info().provider_id()),
524 state,
525 )
526 }
527
528 fn validate_path(&self, path: &Path, operation: FsOperation) -> FsResult<()> {
530 self.core.validate_path(path, operation)
531 }
532
533 fn require(&self, capability: FileSystemCapability, operation: FsOperation, path: &Path) -> FsResult<()> {
535 self.core.require(capability, operation, Some(path))
536 }
537
538 fn enrich(&self, error: FsError, path: &Path, operation: FsOperation) -> FsError {
540 self.core.enrich(error, Some(path), operation)
541 }
542
543 fn contract_error(&self, path: &Path, operation: FsOperation, message: &str) -> FsError {
545 self.core.contract_error(path, operation, message)
546 }
547
548 fn validate_opened_info(&self, info: &crate::metadata::OpenedFileInfo, path: &Path) -> FsResult<()> {
550 if info.filesystem_id() != self.properties().info().id() || info.path() != path {
551 return Err(self.contract_error(
552 path,
553 FsOperation::ValidateProviderOutcome,
554 "provider returned an opened handle with a different identity",
555 ));
556 }
557 Ok(())
558 }
559
560 fn validate_temp_info(
563 &self,
564 info: &crate::metadata::OpenedFileInfo,
565 expected_kind: crate::metadata::FileKind,
566 ) -> FsResult<()> {
567 if info.filesystem_id() != self.properties().info().id() {
568 return Err(self.contract_error(
569 info.path(),
570 FsOperation::ValidateProviderOutcome,
571 "provider returned a temporary handle for a different filesystem",
572 ));
573 }
574 self.validate_path(info.path(), FsOperation::CreateTemp).map_err(|_| {
575 self.contract_error(
576 info.path(),
577 FsOperation::ValidateProviderOutcome,
578 "provider returned a temporary handle with an invalid logical path",
579 )
580 })?;
581 if info.metadata().is_none_or(|metadata| metadata.kind() != &expected_kind) {
582 return Err(self.contract_error(
583 info.path(),
584 FsOperation::ValidateProviderOutcome,
585 "provider returned a temporary handle with an inconsistent resource kind",
586 ));
587 }
588 Ok(())
589 }
590
591 pub(crate) fn preflight_temp_persist(
594 &self,
595 source: &Path,
596 target: &Path,
597 options: &crate::temp::PersistOptions,
598 ) -> FsResult<()> {
599 self.validate_path(source, FsOperation::PersistTemp)?;
600 self.validate_path(target, FsOperation::PersistTemp)?;
601 options
602 .validate_against(self.properties().capabilities())
603 .map_err(|error| {
604 self.enrich(error, source, FsOperation::PersistTemp)
605 .with_target(target.clone())
606 })?;
607 Ok(())
608 }
609
610 pub(crate) fn validate_temp_keep_target(&self, source: &Path, target: &Path) -> FsResult<()> {
612 self.validate_path(target, FsOperation::KeepTemp).map_err(|_| {
613 self.contract_error(
614 source,
615 FsOperation::ValidateProviderOutcome,
616 "provider returned a temporary keep target with an invalid logical path",
617 )
618 .with_target(target.clone())
619 })
620 }
621
622 pub fn read_all(&self, path: &Path, options: ReadOptions, max_bytes: usize) -> FsResult<Vec<u8>> {
631 ReadOperation::new(self).read_all(path, options, max_bytes)
632 }
633
634 pub fn read_prefix(
640 &self,
641 path: &Path,
642 options: ReadOptions,
643 max_bytes: usize,
644 ) -> FsResult<crate::read::PrefixReadOutcome> {
645 ReadOperation::new(self).read_prefix(path, options, max_bytes)
646 }
647
648 pub fn write_all(
654 &self,
655 path: &Path,
656 bytes: &[u8],
657 options: WriteOptions,
658 ) -> Result<crate::metadata::WriteOutcome, WriteAllFailure> {
659 WriteOperation::new(self).write_all(path, bytes, options)
660 }
661}