1use async_trait::async_trait;
2use once_cell::sync::OnceCell;
3use std::ffi::OsString;
4use std::fmt;
5use std::io::{self, ErrorKind, Read, Seek, Write};
6use std::path::{Path, PathBuf};
7use std::sync::{Arc, RwLock};
8use std::time::SystemTime;
9
10#[cfg(not(target_arch = "wasm32"))]
11mod native;
12#[cfg(not(target_arch = "wasm32"))]
13pub mod remote;
14#[cfg(not(target_arch = "wasm32"))]
15pub mod sandbox;
16#[cfg(target_arch = "wasm32")]
17mod wasm;
18
19#[cfg(not(target_arch = "wasm32"))]
20pub use native::NativeFsProvider;
21#[cfg(not(target_arch = "wasm32"))]
22pub use remote::{RemoteFsConfig, RemoteFsProvider};
23#[cfg(not(target_arch = "wasm32"))]
24pub use sandbox::SandboxFsProvider;
25#[cfg(target_arch = "wasm32")]
26pub use wasm::PlaceholderFsProvider;
27
28pub mod data_contract;
29
30use data_contract::{
31 DataChunkUploadRequest, DataChunkUploadTarget, DataManifestDescriptor, DataManifestRequest,
32};
33
34pub trait FileHandle: Read + Write + Seek + Send + Sync {}
35
36impl<T> FileHandle for T where T: Read + Write + Seek + Send + Sync + 'static {}
37
38#[derive(Clone, Debug, Default)]
39pub struct OpenFlags {
40 pub read: bool,
41 pub write: bool,
42 pub append: bool,
43 pub truncate: bool,
44 pub create: bool,
45 pub create_new: bool,
46}
47
48#[derive(Clone, Debug)]
49pub struct OpenOptions {
50 flags: OpenFlags,
51}
52
53impl OpenOptions {
54 pub fn new() -> Self {
55 Self {
56 flags: OpenFlags::default(),
57 }
58 }
59
60 pub fn read(&mut self, value: bool) -> &mut Self {
61 self.flags.read = value;
62 self
63 }
64
65 pub fn write(&mut self, value: bool) -> &mut Self {
66 self.flags.write = value;
67 self
68 }
69
70 pub fn append(&mut self, value: bool) -> &mut Self {
71 self.flags.append = value;
72 self
73 }
74
75 pub fn truncate(&mut self, value: bool) -> &mut Self {
76 self.flags.truncate = value;
77 self
78 }
79
80 pub fn create(&mut self, value: bool) -> &mut Self {
81 self.flags.create = value;
82 self
83 }
84
85 pub fn create_new(&mut self, value: bool) -> &mut Self {
86 self.flags.create_new = value;
87 self
88 }
89
90 pub fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
91 let resolved = resolve_path(path.as_ref());
92 with_provider(|provider| provider.open(&resolved, &self.flags)).map(File::from_handle)
93 }
94
95 pub fn flags(&self) -> &OpenFlags {
96 &self.flags
97 }
98}
99
100impl Default for OpenOptions {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub enum FsFileType {
108 Directory,
109 File,
110 Symlink,
111 Other,
112 Unknown,
113}
114
115#[derive(Clone, Debug)]
116pub struct FsMetadata {
117 file_type: FsFileType,
118 len: u64,
119 modified: Option<SystemTime>,
120 readonly: bool,
121 hash: Option<String>,
122}
123
124impl FsMetadata {
125 pub fn new(
126 file_type: FsFileType,
127 len: u64,
128 modified: Option<SystemTime>,
129 readonly: bool,
130 ) -> Self {
131 Self {
132 file_type,
133 len,
134 modified,
135 readonly,
136 hash: None,
137 }
138 }
139
140 pub fn new_with_hash(
141 file_type: FsFileType,
142 len: u64,
143 modified: Option<SystemTime>,
144 readonly: bool,
145 hash: Option<String>,
146 ) -> Self {
147 Self {
148 file_type,
149 len,
150 modified,
151 readonly,
152 hash,
153 }
154 }
155
156 pub fn file_type(&self) -> FsFileType {
157 self.file_type
158 }
159
160 pub fn is_dir(&self) -> bool {
161 matches!(self.file_type, FsFileType::Directory)
162 }
163
164 pub fn is_file(&self) -> bool {
165 matches!(self.file_type, FsFileType::File)
166 }
167
168 pub fn is_symlink(&self) -> bool {
169 matches!(self.file_type, FsFileType::Symlink)
170 }
171
172 pub fn len(&self) -> u64 {
173 self.len
174 }
175
176 pub fn hash(&self) -> Option<&str> {
177 self.hash.as_deref()
178 }
179
180 pub fn is_empty(&self) -> bool {
181 self.len == 0
182 }
183
184 pub fn modified(&self) -> Option<SystemTime> {
185 self.modified
186 }
187
188 pub fn is_readonly(&self) -> bool {
189 self.readonly
190 }
191}
192
193#[derive(Clone, Debug)]
194pub struct DirEntry {
195 path: PathBuf,
196 file_name: OsString,
197 file_type: FsFileType,
198}
199
200#[derive(Clone, Debug)]
201pub struct ReadManyEntry {
202 path: PathBuf,
203 bytes: Option<Vec<u8>>,
204}
205
206impl ReadManyEntry {
207 pub fn new(path: PathBuf, bytes: Option<Vec<u8>>) -> Self {
208 Self { path, bytes }
209 }
210
211 pub fn path(&self) -> &Path {
212 &self.path
213 }
214
215 pub fn bytes(&self) -> Option<&[u8]> {
216 self.bytes.as_deref()
217 }
218
219 pub fn into_bytes(self) -> Option<Vec<u8>> {
220 self.bytes
221 }
222}
223
224impl DirEntry {
225 pub fn new(path: PathBuf, file_name: OsString, file_type: FsFileType) -> Self {
226 Self {
227 path,
228 file_name,
229 file_type,
230 }
231 }
232
233 pub fn path(&self) -> &Path {
234 &self.path
235 }
236
237 pub fn file_name(&self) -> &OsString {
238 &self.file_name
239 }
240
241 pub fn file_type(&self) -> FsFileType {
242 self.file_type
243 }
244
245 pub fn is_dir(&self) -> bool {
246 matches!(self.file_type, FsFileType::Directory)
247 }
248}
249
250#[async_trait(?Send)]
251pub trait FsProvider: Send + Sync + 'static {
252 fn open(&self, path: &Path, flags: &OpenFlags) -> io::Result<Box<dyn FileHandle>>;
253 async fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
254 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()>;
255 async fn remove_file(&self, path: &Path) -> io::Result<()>;
256 async fn metadata(&self, path: &Path) -> io::Result<FsMetadata>;
257 async fn symlink_metadata(&self, path: &Path) -> io::Result<FsMetadata>;
258 async fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>>;
259 async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf>;
260 async fn create_dir(&self, path: &Path) -> io::Result<()>;
261 async fn create_dir_all(&self, path: &Path) -> io::Result<()>;
262 async fn remove_dir(&self, path: &Path) -> io::Result<()>;
263 async fn remove_dir_all(&self, path: &Path) -> io::Result<()>;
264 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
265 async fn set_readonly(&self, path: &Path, readonly: bool) -> io::Result<()>;
266
267 async fn read_many(&self, paths: &[PathBuf]) -> io::Result<Vec<ReadManyEntry>> {
268 let mut entries = Vec::with_capacity(paths.len());
269 for path in paths {
270 let bytes = self.read(path).await.ok();
271 entries.push(ReadManyEntry::new(path.clone(), bytes));
272 }
273 Ok(entries)
274 }
275
276 async fn data_manifest_descriptor(
277 &self,
278 _request: &DataManifestRequest,
279 ) -> io::Result<DataManifestDescriptor> {
280 Err(io::Error::new(
281 ErrorKind::Unsupported,
282 "data manifest descriptor is unsupported by this provider",
283 ))
284 }
285
286 async fn data_chunk_upload_targets(
287 &self,
288 _request: &DataChunkUploadRequest,
289 ) -> io::Result<Vec<DataChunkUploadTarget>> {
290 Err(io::Error::new(
291 ErrorKind::Unsupported,
292 "data chunk upload targets are unsupported by this provider",
293 ))
294 }
295
296 async fn data_upload_chunk(
297 &self,
298 _target: &DataChunkUploadTarget,
299 _data: &[u8],
300 ) -> io::Result<()> {
301 Err(io::Error::new(
302 ErrorKind::Unsupported,
303 "data chunk upload is unsupported by this provider",
304 ))
305 }
306}
307
308pub struct File {
309 inner: Box<dyn FileHandle>,
310}
311
312impl File {
313 fn from_handle(handle: Box<dyn FileHandle>) -> Self {
314 Self { inner: handle }
315 }
316
317 pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
318 let mut opts = OpenOptions::new();
319 opts.read(true);
320 opts.open(path)
321 }
322
323 pub fn create(path: impl AsRef<Path>) -> io::Result<Self> {
324 let mut opts = OpenOptions::new();
325 opts.write(true).create(true).truncate(true);
326 opts.open(path)
327 }
328}
329
330impl fmt::Debug for File {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 f.debug_struct("File").finish_non_exhaustive()
333 }
334}
335
336impl Read for File {
337 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
338 self.inner.read(buf)
339 }
340}
341
342impl Write for File {
343 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
344 self.inner.write(buf)
345 }
346
347 fn flush(&mut self) -> io::Result<()> {
348 self.inner.flush()
349 }
350}
351
352impl Seek for File {
353 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
354 self.inner.seek(pos)
355 }
356}
357
358static PROVIDER: OnceCell<RwLock<Arc<dyn FsProvider>>> = OnceCell::new();
359#[cfg(target_arch = "wasm32")]
360static CURRENT_DIR: OnceCell<RwLock<PathBuf>> = OnceCell::new();
361
362fn provider_lock() -> &'static RwLock<Arc<dyn FsProvider>> {
363 PROVIDER.get_or_init(|| RwLock::new(default_provider()))
364}
365
366#[cfg(target_arch = "wasm32")]
367fn current_dir_lock() -> &'static RwLock<PathBuf> {
368 CURRENT_DIR.get_or_init(|| RwLock::new(PathBuf::from("/")))
369}
370
371fn with_provider<T>(f: impl FnOnce(&dyn FsProvider) -> T) -> T {
372 let guard = provider_lock()
373 .read()
374 .expect("filesystem provider lock poisoned");
375 f(&**guard)
376}
377
378fn resolve_path(path: &Path) -> PathBuf {
379 #[cfg(target_arch = "wasm32")]
380 {
381 if path.is_absolute() {
382 return path.to_path_buf();
383 }
384 if let Ok(base) = current_dir() {
385 return base.join(path);
386 }
387 return PathBuf::from("/").join(path);
388 }
389 #[cfg(not(target_arch = "wasm32"))]
390 {
391 path.to_path_buf()
392 }
393}
394
395pub fn set_provider(provider: Arc<dyn FsProvider>) {
396 let mut guard = provider_lock()
397 .write()
398 .expect("filesystem provider lock poisoned");
399 *guard = provider;
400}
401
402pub fn replace_provider(provider: Arc<dyn FsProvider>) -> ProviderGuard {
406 let mut guard = provider_lock()
407 .write()
408 .expect("filesystem provider lock poisoned");
409 let previous = guard.clone();
410 *guard = provider;
411 ProviderGuard { previous }
412}
413
414pub fn with_provider_override<R>(provider: Arc<dyn FsProvider>, f: impl FnOnce() -> R) -> R {
417 let guard = replace_provider(provider);
418 let result = f();
419 drop(guard);
420 result
421}
422
423pub fn current_provider() -> Arc<dyn FsProvider> {
425 provider_lock()
426 .read()
427 .expect("filesystem provider lock poisoned")
428 .clone()
429}
430
431pub fn current_dir() -> io::Result<PathBuf> {
432 #[cfg(target_arch = "wasm32")]
433 {
434 return Ok(current_dir_lock()
435 .read()
436 .expect("filesystem current dir lock poisoned")
437 .clone());
438 }
439 #[cfg(not(target_arch = "wasm32"))]
440 {
441 std::env::current_dir()
442 }
443}
444
445pub fn set_current_dir(path: impl AsRef<Path>) -> io::Result<()> {
446 #[cfg(target_arch = "wasm32")]
447 {
448 let mut target = PathBuf::from(path.as_ref());
449 if !target.is_absolute() {
450 let base = current_dir()?;
451 target = base.join(target);
452 }
453 let canonical =
454 futures::executor::block_on(canonicalize_async(&target)).unwrap_or(target.clone());
455 let metadata = futures::executor::block_on(metadata_async(&canonical))?;
456 if !metadata.is_dir() {
457 return Err(io::Error::new(
458 ErrorKind::NotFound,
459 format!("Not a directory: {}", canonical.display()),
460 ));
461 }
462 let mut guard = current_dir_lock()
463 .write()
464 .expect("filesystem current dir lock poisoned");
465 *guard = canonical;
466 return Ok(());
467 }
468 #[cfg(not(target_arch = "wasm32"))]
469 {
470 std::env::set_current_dir(path)
471 }
472}
473
474pub struct ProviderGuard {
475 previous: Arc<dyn FsProvider>,
476}
477
478impl Drop for ProviderGuard {
479 fn drop(&mut self) {
480 set_provider(self.previous.clone());
481 }
482}
483
484pub async fn read_many_async(paths: &[PathBuf]) -> io::Result<Vec<ReadManyEntry>> {
485 let resolved = paths
486 .iter()
487 .map(|path| resolve_path(path.as_path()))
488 .collect::<Vec<_>>();
489 let provider = current_provider();
490 provider.read_many(&resolved).await
491}
492
493pub async fn read_async(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
494 let resolved = resolve_path(path.as_ref());
495 let provider = current_provider();
496 provider.read(&resolved).await
497}
498
499pub async fn read_to_string_async(path: impl AsRef<Path>) -> io::Result<String> {
500 let bytes = read_async(path).await?;
501 String::from_utf8(bytes).map_err(|err| io::Error::new(ErrorKind::InvalidData, err.utf8_error()))
502}
503
504pub async fn write_async(path: impl AsRef<Path>, data: impl AsRef<[u8]>) -> io::Result<()> {
505 let resolved = resolve_path(path.as_ref());
506 let provider = current_provider();
507 provider.write(&resolved, data.as_ref()).await
508}
509
510pub async fn remove_file_async(path: impl AsRef<Path>) -> io::Result<()> {
511 let resolved = resolve_path(path.as_ref());
512 let provider = current_provider();
513 provider.remove_file(&resolved).await
514}
515
516pub async fn metadata_async(path: impl AsRef<Path>) -> io::Result<FsMetadata> {
517 let resolved = resolve_path(path.as_ref());
518 let provider = current_provider();
519 provider.metadata(&resolved).await
520}
521
522pub async fn symlink_metadata_async(path: impl AsRef<Path>) -> io::Result<FsMetadata> {
523 let resolved = resolve_path(path.as_ref());
524 let provider = current_provider();
525 provider.symlink_metadata(&resolved).await
526}
527
528pub async fn read_dir_async(path: impl AsRef<Path>) -> io::Result<Vec<DirEntry>> {
529 let resolved = resolve_path(path.as_ref());
530 let provider = current_provider();
531 provider.read_dir(&resolved).await
532}
533
534pub async fn canonicalize_async(path: impl AsRef<Path>) -> io::Result<PathBuf> {
535 let resolved = resolve_path(path.as_ref());
536 let provider = current_provider();
537 provider.canonicalize(&resolved).await
538}
539
540pub async fn create_dir_async(path: impl AsRef<Path>) -> io::Result<()> {
541 let resolved = resolve_path(path.as_ref());
542 let provider = current_provider();
543 provider.create_dir(&resolved).await
544}
545
546pub async fn create_dir_all_async(path: impl AsRef<Path>) -> io::Result<()> {
547 let resolved = resolve_path(path.as_ref());
548 let provider = current_provider();
549 provider.create_dir_all(&resolved).await
550}
551
552pub async fn remove_dir_async(path: impl AsRef<Path>) -> io::Result<()> {
553 let resolved = resolve_path(path.as_ref());
554 let provider = current_provider();
555 provider.remove_dir(&resolved).await
556}
557
558pub async fn remove_dir_all_async(path: impl AsRef<Path>) -> io::Result<()> {
559 let resolved = resolve_path(path.as_ref());
560 let provider = current_provider();
561 provider.remove_dir_all(&resolved).await
562}
563
564pub async fn rename_async(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
565 let resolved_from = resolve_path(from.as_ref());
566 let resolved_to = resolve_path(to.as_ref());
567 let provider = current_provider();
568 provider.rename(&resolved_from, &resolved_to).await
569}
570
571pub async fn set_readonly_async(path: impl AsRef<Path>, readonly: bool) -> io::Result<()> {
572 let resolved = resolve_path(path.as_ref());
573 let provider = current_provider();
574 provider.set_readonly(&resolved, readonly).await
575}
576
577pub async fn data_manifest_descriptor_async(
578 request: &DataManifestRequest,
579) -> io::Result<DataManifestDescriptor> {
580 let provider = current_provider();
581 provider.data_manifest_descriptor(request).await
582}
583
584pub async fn data_chunk_upload_targets_async(
585 request: &DataChunkUploadRequest,
586) -> io::Result<Vec<DataChunkUploadTarget>> {
587 let provider = current_provider();
588 provider.data_chunk_upload_targets(request).await
589}
590
591pub async fn data_upload_chunk_async(
592 target: &DataChunkUploadTarget,
593 data: &[u8],
594) -> io::Result<()> {
595 let provider = current_provider();
596 provider.data_upload_chunk(target, data).await
597}
598
599pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<u64> {
602 let mut reader = OpenOptions::new().read(true).open(from.as_ref())?;
603 let mut writer = OpenOptions::new()
604 .write(true)
605 .create(true)
606 .truncate(true)
607 .open(to.as_ref())?;
608 io::copy(&mut reader, &mut writer)
609}
610
611fn default_provider() -> Arc<dyn FsProvider> {
612 #[cfg(not(target_arch = "wasm32"))]
613 {
614 Arc::new(NativeFsProvider)
615 }
616 #[cfg(target_arch = "wasm32")]
617 {
618 Arc::new(PlaceholderFsProvider)
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625 use once_cell::sync::Lazy;
626 use std::io::{Read, Write};
627 use std::sync::Mutex;
628 use tempfile::tempdir;
629
630 static TEST_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
631
632 struct UnsupportedProvider;
633
634 #[async_trait(?Send)]
635 impl FsProvider for UnsupportedProvider {
636 fn open(&self, _path: &Path, _flags: &OpenFlags) -> io::Result<Box<dyn FileHandle>> {
637 Err(unsupported())
638 }
639
640 async fn read(&self, _path: &Path) -> io::Result<Vec<u8>> {
641 Err(unsupported())
642 }
643
644 async fn write(&self, _path: &Path, _data: &[u8]) -> io::Result<()> {
645 Err(unsupported())
646 }
647
648 async fn remove_file(&self, _path: &Path) -> io::Result<()> {
649 Err(unsupported())
650 }
651
652 async fn metadata(&self, _path: &Path) -> io::Result<FsMetadata> {
653 Err(unsupported())
654 }
655
656 async fn symlink_metadata(&self, _path: &Path) -> io::Result<FsMetadata> {
657 Err(unsupported())
658 }
659
660 async fn read_dir(&self, _path: &Path) -> io::Result<Vec<DirEntry>> {
661 Err(unsupported())
662 }
663
664 async fn canonicalize(&self, _path: &Path) -> io::Result<PathBuf> {
665 Err(unsupported())
666 }
667
668 async fn create_dir(&self, _path: &Path) -> io::Result<()> {
669 Err(unsupported())
670 }
671
672 async fn create_dir_all(&self, _path: &Path) -> io::Result<()> {
673 Err(unsupported())
674 }
675
676 async fn remove_dir(&self, _path: &Path) -> io::Result<()> {
677 Err(unsupported())
678 }
679
680 async fn remove_dir_all(&self, _path: &Path) -> io::Result<()> {
681 Err(unsupported())
682 }
683
684 async fn rename(&self, _from: &Path, _to: &Path) -> io::Result<()> {
685 Err(unsupported())
686 }
687
688 async fn set_readonly(&self, _path: &Path, _readonly: bool) -> io::Result<()> {
689 Err(unsupported())
690 }
691
692 async fn data_manifest_descriptor(
693 &self,
694 _request: &DataManifestRequest,
695 ) -> io::Result<DataManifestDescriptor> {
696 Err(unsupported())
697 }
698
699 async fn data_chunk_upload_targets(
700 &self,
701 _request: &DataChunkUploadRequest,
702 ) -> io::Result<Vec<DataChunkUploadTarget>> {
703 Err(unsupported())
704 }
705
706 async fn data_upload_chunk(
707 &self,
708 _target: &DataChunkUploadTarget,
709 _data: &[u8],
710 ) -> io::Result<()> {
711 Err(unsupported())
712 }
713 }
714
715 fn unsupported() -> io::Error {
716 io::Error::new(ErrorKind::Unsupported, "unsupported in test provider")
717 }
718
719 #[test]
720 fn copy_file_round_trip() {
721 let _guard = TEST_LOCK.lock().unwrap();
722 let dir = tempdir().expect("tempdir");
723 let src = dir.path().join("src.bin");
724 let dst = dir.path().join("dst.bin");
725 {
726 let mut file = std::fs::File::create(&src).expect("create src");
727 file.write_all(b"hello filesystem").expect("write src");
728 }
729
730 copy_file(&src, &dst).expect("copy");
731 let mut dst_file = File::open(&dst).expect("open dst");
732 let mut contents = Vec::new();
733 dst_file
734 .read_to_end(&mut contents)
735 .expect("read destination");
736 assert_eq!(contents, b"hello filesystem");
737 }
738
739 #[test]
740 fn set_readonly_flips_metadata_flag() {
741 let _guard = TEST_LOCK.lock().unwrap();
742 let dir = tempdir().expect("tempdir");
743 let path = dir.path().join("flag.txt");
744 futures::executor::block_on(write_async(&path, b"flag")).expect("write");
745
746 futures::executor::block_on(set_readonly_async(&path, true)).expect("set readonly");
747 let meta = futures::executor::block_on(metadata_async(&path)).expect("metadata");
748 assert!(meta.is_readonly());
749
750 futures::executor::block_on(set_readonly_async(&path, false)).expect("unset readonly");
751 let meta = futures::executor::block_on(metadata_async(&path)).expect("metadata");
752 assert!(!meta.is_readonly());
753 }
754
755 #[test]
756 fn replace_provider_restores_previous() {
757 let _guard = TEST_LOCK.lock().unwrap();
758 let original = current_provider();
759 let custom: Arc<dyn FsProvider> = Arc::new(UnsupportedProvider);
760 {
761 let _guard = replace_provider(custom.clone());
762 let active = current_provider();
763 assert!(Arc::ptr_eq(&active, &custom));
764 }
765 let final_provider = current_provider();
766 assert!(Arc::ptr_eq(&final_provider, &original));
767 }
768
769 #[test]
770 fn with_provider_restores_even_on_panic() {
771 let _guard = TEST_LOCK.lock().unwrap();
772 let original = current_provider();
773 let custom: Arc<dyn FsProvider> = Arc::new(UnsupportedProvider);
774 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
775 with_provider_override(custom.clone(), || {
776 let active = current_provider();
777 assert!(Arc::ptr_eq(&active, &custom));
778 panic!("boom");
779 })
780 }));
781 assert!(result.is_err());
782 let final_provider = current_provider();
783 assert!(Arc::ptr_eq(&final_provider, &original));
784 }
785}