opendal/blocking/
operator.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use tokio::runtime::Handle;
19
20use crate::Operator as AsyncOperator;
21use crate::*;
22
23/// Use OpenDAL in blocking context.
24///
25/// # Notes
26///
27/// blocking::Operator is a wrapper around [`AsyncOperator`]. It calls async runtimes' `block_on` API to spawn blocking tasks.
28/// Please avoid using blocking::Operator in async context.
29///
30/// # Examples
31///
32/// ## Init in async context
33///
34/// blocking::Operator will use current async context's runtime to handle the async calls.
35///
36/// This is just for initialization. You must use `blocking::Operator` in blocking context.
37///
38/// ```rust,no_run
39/// # use opendal::services;
40/// # use opendal::blocking;
41/// # use opendal::Operator;
42/// # use opendal::Result;
43///
44/// #[tokio::main]
45/// async fn main() -> Result<()> {
46///     // Create fs backend builder.
47///     let mut builder = services::S3::default().bucket("test").region("us-east-1");
48///     let op = Operator::new(builder)?.finish();
49///
50///     // Build an `blocking::Operator` with blocking layer to start operating the storage.
51///     let _: blocking::Operator = blocking::Operator::new(op)?;
52///
53///     Ok(())
54/// }
55/// ```
56///
57/// ## In async context with blocking functions
58///
59/// If `blocking::Operator` is called in blocking function, please fetch a [`tokio::runtime::EnterGuard`]
60/// first. You can use [`Handle::try_current`] first to get the handle and then call [`Handle::enter`].
61/// This often happens in the case that async function calls blocking function.
62///
63/// ```rust,no_run
64/// # use opendal::services;
65/// # use opendal::blocking;
66/// # use opendal::Operator;
67/// # use opendal::Result;
68///
69/// #[tokio::main]
70/// async fn main() -> Result<()> {
71///     let _ = blocking_fn()?;
72///     Ok(())
73/// }
74///
75/// fn blocking_fn() -> Result<blocking::Operator> {
76///     // Create fs backend builder.
77///     let mut builder = services::S3::default().bucket("test").region("us-east-1");
78///     let op = Operator::new(builder)?.finish();
79///
80///     let handle = tokio::runtime::Handle::try_current().unwrap();
81///     let _guard = handle.enter();
82///     // Build an `blocking::Operator` to start operating the storage.
83///     let op: blocking::Operator = blocking::Operator::new(op)?;
84///     Ok(op)
85/// }
86/// ```
87///
88/// ## In blocking context
89///
90/// In a pure blocking context, we can create a runtime and use it to create the `blocking::Operator`.
91///
92/// > The following code uses a global statically created runtime as an example, please manage the
93/// > runtime on demand.
94///
95/// ```rust,no_run
96/// # use std::sync::LazyLock;
97/// # use opendal::services;
98/// # use opendal::blocking;
99/// # use opendal::Operator;
100/// # use opendal::Result;
101///
102/// static RUNTIME: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
103///     tokio::runtime::Builder::new_multi_thread()
104///         .enable_all()
105///         .build()
106///         .unwrap()
107/// });
108///
109/// fn main() -> Result<()> {
110///     // Create fs backend builder.
111///     let mut builder = services::S3::default().bucket("test").region("us-east-1");
112///     let op = Operator::new(builder)?.finish();
113///
114///     // Fetch the `EnterGuard` from global runtime.
115///     let _guard = RUNTIME.enter();
116///     // Build an `blocking::Operator` with blocking layer to start operating the storage.
117///     let _: blocking::Operator = blocking::Operator::new(op)?;
118///
119///     Ok(())
120/// }
121/// ```
122#[derive(Clone, Debug)]
123pub struct Operator {
124    handle: tokio::runtime::Handle,
125    op: AsyncOperator,
126}
127
128impl Operator {
129    /// Create a new `BlockingLayer` with the current runtime's handle
130    pub fn new(op: AsyncOperator) -> Result<Self> {
131        Ok(Self {
132            handle: Handle::try_current()
133                .map_err(|_| Error::new(ErrorKind::Unexpected, "failed to get current handle"))?,
134            op,
135        })
136    }
137
138    /// Create a blocking operator from URI based configuration.
139    pub fn from_uri(
140        uri: &str,
141        options: impl IntoIterator<Item = (String, String)>,
142    ) -> Result<Self> {
143        let op = AsyncOperator::from_uri(uri, options)?;
144        Self::new(op)
145    }
146
147    /// Get information of underlying accessor.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// # use std::sync::Arc;
153    /// use opendal::blocking;
154    /// # use anyhow::Result;
155    /// use opendal::blocking::Operator;
156    ///
157    /// # fn test(op: blocking::Operator) -> Result<()> {
158    /// let info = op.info();
159    /// # Ok(())
160    /// # }
161    /// ```
162    pub fn info(&self) -> OperatorInfo {
163        self.op.info()
164    }
165}
166
167/// # Operator blocking API.
168impl Operator {
169    /// Get given path's metadata.
170    ///
171    /// # Behavior
172    ///
173    /// ## Services that support `create_dir`
174    ///
175    /// `test` and `test/` may vary in some services such as S3. However, on a local file system,
176    /// they're identical. Therefore, the behavior of `stat("test")` and `stat("test/")` might differ
177    /// in certain edge cases. Always use `stat("test/")` when you need to access a directory if possible.
178    ///
179    /// Here are the behavior list:
180    ///
181    /// | Case                   | Path            | Result                                     |
182    /// |------------------------|-----------------|--------------------------------------------|
183    /// | stat existing dir      | `abc/`          | Metadata with dir mode                     |
184    /// | stat existing file     | `abc/def_file`  | Metadata with file mode                    |
185    /// | stat dir without `/`   | `abc/def_dir`   | Error `NotFound` or metadata with dir mode |
186    /// | stat file with `/`     | `abc/def_file/` | Error `NotFound`                           |
187    /// | stat not existing path | `xyz`           | Error `NotFound`                           |
188    ///
189    /// Refer to [RFC: List Prefix][crate::docs::rfcs::rfc_3243_list_prefix] for more details.
190    ///
191    /// ## Services that not support `create_dir`
192    ///
193    /// For services that not support `create_dir`, `stat("test/")` will return `NotFound` even
194    /// when `test/abc` exists since the service won't have the concept of dir. There is nothing
195    /// we can do about this.
196    ///
197    /// # Examples
198    ///
199    /// ## Check if file exists
200    ///
201    /// ```
202    /// # use anyhow::Result;
203    /// # use futures::io;
204    /// use opendal::blocking;
205    /// # use opendal::blocking::Operator;
206    /// use opendal::ErrorKind;
207    /// #
208    /// # fn test(op: blocking::Operator) -> Result<()> {
209    /// if let Err(e) = op.stat("test") {
210    ///     if e.kind() == ErrorKind::NotFound {
211    ///         println!("file not exist")
212    ///     }
213    /// }
214    /// # Ok(())
215    /// # }
216    /// ```
217    pub fn stat(&self, path: &str) -> Result<Metadata> {
218        self.stat_options(path, options::StatOptions::default())
219    }
220
221    /// Get given path's metadata with extra options.
222    ///
223    /// # Behavior
224    ///
225    /// ## Services that support `create_dir`
226    ///
227    /// `test` and `test/` may vary in some services such as S3. However, on a local file system,
228    /// they're identical. Therefore, the behavior of `stat("test")` and `stat("test/")` might differ
229    /// in certain edge cases. Always use `stat("test/")` when you need to access a directory if possible.
230    ///
231    /// Here are the behavior list:
232    ///
233    /// | Case                   | Path            | Result                                     |
234    /// |------------------------|-----------------|--------------------------------------------|
235    /// | stat existing dir      | `abc/`          | Metadata with dir mode                     |
236    /// | stat existing file     | `abc/def_file`  | Metadata with file mode                    |
237    /// | stat dir without `/`   | `abc/def_dir`   | Error `NotFound` or metadata with dir mode |
238    /// | stat file with `/`     | `abc/def_file/` | Error `NotFound`                           |
239    /// | stat not existing path | `xyz`           | Error `NotFound`                           |
240    ///
241    /// Refer to [RFC: List Prefix][crate::docs::rfcs::rfc_3243_list_prefix] for more details.
242    ///
243    /// ## Services that not support `create_dir`
244    ///
245    /// For services that not support `create_dir`, `stat("test/")` will return `NotFound` even
246    /// when `test/abc` exists since the service won't have the concept of dir. There is nothing
247    /// we can do about this.
248    pub fn stat_options(&self, path: &str, opts: options::StatOptions) -> Result<Metadata> {
249        self.handle.block_on(self.op.stat_options(path, opts))
250    }
251
252    /// Check if this path exists or not.
253    ///
254    /// # Example
255    ///
256    /// ```no_run
257    /// use anyhow::Result;
258    /// use opendal::blocking;
259    /// use opendal::blocking::Operator;
260    /// fn test(op: blocking::Operator) -> Result<()> {
261    ///     let _ = op.exists("test")?;
262    ///
263    ///     Ok(())
264    /// }
265    /// ```
266    pub fn exists(&self, path: &str) -> Result<bool> {
267        let r = self.stat(path);
268        match r {
269            Ok(_) => Ok(true),
270            Err(err) => match err.kind() {
271                ErrorKind::NotFound => Ok(false),
272                _ => Err(err),
273            },
274        }
275    }
276
277    /// Create a dir at given path.
278    ///
279    /// # Notes
280    ///
281    /// To indicate that a path is a directory, it is compulsory to include
282    /// a trailing / in the path. Failure to do so may result in
283    /// `NotADirectory` error being returned by OpenDAL.
284    ///
285    /// # Behavior
286    ///
287    /// - Create on existing dir will succeed.
288    /// - Create dir is always recursive, works like `mkdir -p`
289    ///
290    /// # Examples
291    ///
292    /// ```no_run
293    /// # use opendal::Result;
294    /// use opendal::blocking;
295    /// # use opendal::blocking::Operator;
296    /// # use futures::TryStreamExt;
297    /// # fn test(op: blocking::Operator) -> Result<()> {
298    /// op.create_dir("path/to/dir/")?;
299    /// # Ok(())
300    /// # }
301    /// ```
302    pub fn create_dir(&self, path: &str) -> Result<()> {
303        self.handle.block_on(self.op.create_dir(path))
304    }
305
306    /// Read the whole path into a bytes.
307    ///
308    /// This function will allocate a new bytes internally. For more precise memory control or
309    /// reading data lazily, please use [`blocking::Operator::reader`]
310    ///
311    /// # Examples
312    ///
313    /// ```no_run
314    /// # use opendal::Result;
315    /// use opendal::blocking;
316    /// # use opendal::blocking::Operator;
317    /// #
318    /// # fn test(op: blocking::Operator) -> Result<()> {
319    /// let bs = op.read("path/to/file")?;
320    /// # Ok(())
321    /// # }
322    /// ```
323    pub fn read(&self, path: &str) -> Result<Buffer> {
324        self.read_options(path, options::ReadOptions::default())
325    }
326
327    /// Read the whole path into a bytes with extra options.
328    ///
329    /// This function will allocate a new bytes internally. For more precise memory control or
330    /// reading data lazily, please use [`blocking::Operator::reader`]
331    pub fn read_options(&self, path: &str, opts: options::ReadOptions) -> Result<Buffer> {
332        self.handle.block_on(self.op.read_options(path, opts))
333    }
334
335    /// Create a new reader which can read the whole path.
336    ///
337    /// # Examples
338    ///
339    /// ```no_run
340    /// # use opendal::Result;
341    /// use opendal::blocking;
342    /// # use opendal::blocking::Operator;
343    /// # use futures::TryStreamExt;
344    /// # fn test(op: blocking::Operator) -> Result<()> {
345    /// let r = op.reader("path/to/file")?;
346    /// # Ok(())
347    /// # }
348    /// ```
349    pub fn reader(&self, path: &str) -> Result<blocking::Reader> {
350        self.reader_options(path, options::ReaderOptions::default())
351    }
352
353    /// Create a new reader with extra options
354    pub fn reader_options(
355        &self,
356        path: &str,
357        opts: options::ReaderOptions,
358    ) -> Result<blocking::Reader> {
359        let r = self.handle.block_on(self.op.reader_options(path, opts))?;
360        Ok(blocking::Reader::new(self.handle.clone(), r))
361    }
362
363    /// Write bytes into given path.
364    ///
365    /// # Notes
366    ///
367    /// - Write will make sure all bytes has been written, or an error will be returned.
368    ///
369    /// # Examples
370    ///
371    /// ```no_run
372    /// # use opendal::Result;
373    /// # use opendal::blocking::Operator;
374    /// # use futures::StreamExt;
375    /// # use futures::SinkExt;
376    /// use bytes::Bytes;
377    /// use opendal::blocking;
378    ///
379    /// # fn test(op: blocking::Operator) -> Result<()> {
380    /// op.write("path/to/file", vec![0; 4096])?;
381    /// # Ok(())
382    /// # }
383    /// ```
384    pub fn write(&self, path: &str, bs: impl Into<Buffer>) -> Result<Metadata> {
385        self.write_options(path, bs, options::WriteOptions::default())
386    }
387
388    /// Write data with options.
389    ///
390    /// # Notes
391    ///
392    /// - Write will make sure all bytes has been written, or an error will be returned.
393    pub fn write_options(
394        &self,
395        path: &str,
396        bs: impl Into<Buffer>,
397        opts: options::WriteOptions,
398    ) -> Result<Metadata> {
399        self.handle.block_on(self.op.write_options(path, bs, opts))
400    }
401
402    /// Write multiple bytes into given path.
403    ///
404    /// # Notes
405    ///
406    /// - Write will make sure all bytes has been written, or an error will be returned.
407    ///
408    /// # Examples
409    ///
410    /// ```no_run
411    /// # use opendal::Result;
412    /// # use opendal::blocking;
413    /// # use opendal::blocking::Operator;
414    /// # use futures::StreamExt;
415    /// # use futures::SinkExt;
416    /// use bytes::Bytes;
417    ///
418    /// # fn test(op: blocking::Operator) -> Result<()> {
419    /// let mut w = op.writer("path/to/file")?;
420    /// w.write(vec![0; 4096])?;
421    /// w.write(vec![1; 4096])?;
422    /// w.close()?;
423    /// # Ok(())
424    /// # }
425    /// ```
426    pub fn writer(&self, path: &str) -> Result<blocking::Writer> {
427        self.writer_options(path, options::WriteOptions::default())
428    }
429
430    /// Create a new writer with extra options
431    pub fn writer_options(
432        &self,
433        path: &str,
434        opts: options::WriteOptions,
435    ) -> Result<blocking::Writer> {
436        let w = self.handle.block_on(self.op.writer_options(path, opts))?;
437        Ok(blocking::Writer::new(self.handle.clone(), w))
438    }
439
440    /// Copy a file from `from` to `to`.
441    ///
442    /// # Notes
443    ///
444    /// - `from` and `to` must be a file.
445    /// - `to` will be overwritten if it exists.
446    /// - If `from` and `to` are the same, nothing will happen.
447    /// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// # use opendal::Result;
453    /// use opendal::blocking;
454    /// # use opendal::blocking::Operator;
455    ///
456    /// # fn test(op: blocking::Operator) -> Result<()> {
457    /// op.copy("path/to/file", "path/to/file2")?;
458    /// # Ok(())
459    /// # }
460    /// ```
461    pub fn copy(&self, from: &str, to: &str) -> Result<()> {
462        self.handle.block_on(self.op.copy(from, to))
463    }
464
465    /// Rename a file from `from` to `to`.
466    ///
467    /// # Notes
468    ///
469    /// - `from` and `to` must be a file.
470    /// - `to` will be overwritten if it exists.
471    /// - If `from` and `to` are the same, a `IsSameFile` error will occur.
472    ///
473    /// # Examples
474    ///
475    /// ```
476    /// # use opendal::Result;
477    /// use opendal::blocking;
478    /// # use opendal::blocking::Operator;
479    ///
480    /// # fn test(op: blocking::Operator) -> Result<()> {
481    /// op.rename("path/to/file", "path/to/file2")?;
482    /// # Ok(())
483    /// # }
484    /// ```
485    pub fn rename(&self, from: &str, to: &str) -> Result<()> {
486        self.handle.block_on(self.op.rename(from, to))
487    }
488
489    /// Delete given path.
490    ///
491    /// # Notes
492    ///
493    /// - Delete not existing error won't return errors.
494    ///
495    /// # Examples
496    ///
497    /// ```no_run
498    /// # use anyhow::Result;
499    /// # use futures::io;
500    /// use opendal::blocking;
501    /// # use opendal::blocking::Operator;
502    /// # fn test(op: blocking::Operator) -> Result<()> {
503    /// op.delete("path/to/file")?;
504    /// # Ok(())
505    /// # }
506    /// ```
507    pub fn delete(&self, path: &str) -> Result<()> {
508        self.delete_options(path, options::DeleteOptions::default())
509    }
510
511    /// Delete given path with options.
512    ///
513    /// # Notes
514    ///
515    /// - Delete not existing error won't return errors.
516    pub fn delete_options(&self, path: &str, opts: options::DeleteOptions) -> Result<()> {
517        self.handle.block_on(self.op.delete_options(path, opts))
518    }
519
520    /// Delete an infallible iterator of paths.
521    ///
522    /// Also see:
523    ///
524    /// - [`blocking::Operator::delete_try_iter`]: delete an fallible iterator of paths.
525    pub fn delete_iter<I, D>(&self, iter: I) -> Result<()>
526    where
527        I: IntoIterator<Item = D>,
528        D: IntoDeleteInput,
529    {
530        self.handle.block_on(self.op.delete_iter(iter))
531    }
532
533    /// Delete a fallible iterator of paths.
534    ///
535    /// Also see:
536    ///
537    /// - [`blocking::Operator::delete_iter`]: delete an infallible iterator of paths.
538    pub fn delete_try_iter<I, D>(&self, try_iter: I) -> Result<()>
539    where
540        I: IntoIterator<Item = Result<D>>,
541        D: IntoDeleteInput,
542    {
543        self.handle.block_on(self.op.delete_try_iter(try_iter))
544    }
545
546    /// Create a [`BlockingDeleter`] to continuously remove content from storage.
547    ///
548    /// It leverages batch deletion capabilities provided by storage services for efficient removal.
549    ///
550    /// Users can have more control over the deletion process by using [`BlockingDeleter`] directly.
551    pub fn deleter(&self) -> Result<blocking::Deleter> {
552        blocking::Deleter::create(
553            self.handle.clone(),
554            self.handle.block_on(self.op.deleter())?,
555        )
556    }
557
558    /// Remove the path and all nested dirs and files recursively.
559    ///
560    /// # Notes
561    ///
562    /// We don't support batch delete now.
563    ///
564    /// # Examples
565    ///
566    /// ```
567    /// # use anyhow::Result;
568    /// # use futures::io;
569    /// use opendal::blocking;
570    /// # use opendal::blocking::Operator;
571    /// # fn test(op: blocking::Operator) -> Result<()> {
572    /// op.remove_all("path/to/dir")?;
573    /// # Ok(())
574    /// # }
575    /// ```
576    pub fn remove_all(&self, path: &str) -> Result<()> {
577        self.handle.block_on(self.op.remove_all(path))
578    }
579
580    /// List entries that starts with given `path` in parent dir.
581    ///
582    /// # Notes
583    ///
584    /// ## Recursively List
585    ///
586    /// This function only read the children of the given directory. To read
587    /// all entries recursively, use `blocking::Operator::list_options("path", opts)`
588    /// instead.
589    ///
590    /// ## Streaming List
591    ///
592    /// This function will read all entries in the given directory. It could
593    /// take very long time and consume a lot of memory if the directory
594    /// contains a lot of entries.
595    ///
596    /// In order to avoid this, you can use [`blocking::Operator::lister`] to list entries in
597    /// a streaming way.
598    ///
599    /// # Examples
600    ///
601    /// ```no_run
602    /// # use anyhow::Result;
603    /// use opendal::blocking;
604    /// use opendal::blocking::Operator;
605    /// use opendal::EntryMode;
606    /// #  fn test(op: blocking::Operator) -> Result<()> {
607    /// let mut entries = op.list("path/to/dir/")?;
608    /// for entry in entries {
609    ///     match entry.metadata().mode() {
610    ///         EntryMode::FILE => {
611    ///             println!("Handling file")
612    ///         }
613    ///         EntryMode::DIR => {
614    ///             println!("Handling dir {}", entry.path())
615    ///         }
616    ///         EntryMode::Unknown => continue,
617    ///     }
618    /// }
619    /// # Ok(())
620    /// # }
621    /// ```
622    pub fn list(&self, path: &str) -> Result<Vec<Entry>> {
623        self.list_options(path, options::ListOptions::default())
624    }
625
626    /// List entries that starts with given `path` in parent dir. with options.
627    ///
628    /// # Notes
629    ///
630    /// ## Streaming List
631    ///
632    /// This function will read all entries in the given directory. It could
633    /// take very long time and consume a lot of memory if the directory
634    /// contains a lot of entries.
635    ///
636    /// In order to avoid this, you can use [`blocking::Operator::lister`] to list entries in
637    /// a streaming way.
638    pub fn list_options(&self, path: &str, opts: options::ListOptions) -> Result<Vec<Entry>> {
639        self.handle.block_on(self.op.list_options(path, opts))
640    }
641
642    /// List entries that starts with given `path` in parent dir.
643    ///
644    /// This function will create a new [`BlockingLister`] to list entries. Users can stop listing
645    /// via dropping this [`Lister`].
646    ///
647    /// # Notes
648    ///
649    /// ## Recursively List
650    ///
651    /// This function only read the children of the given directory. To read
652    /// all entries recursively, use [`blocking::Operator::lister_with`] and `delimiter("")`
653    /// instead.
654    ///
655    /// # Examples
656    ///
657    /// ```no_run
658    /// # use anyhow::Result;
659    /// # use futures::io;
660    /// use futures::TryStreamExt;
661    /// use opendal::blocking;
662    /// use opendal::blocking::Operator;
663    /// use opendal::EntryMode;
664    /// # fn test(op: blocking::Operator) -> Result<()> {
665    /// let mut ds = op.lister("path/to/dir/")?;
666    /// for de in ds {
667    ///     let de = de?;
668    ///     match de.metadata().mode() {
669    ///         EntryMode::FILE => {
670    ///             println!("Handling file")
671    ///         }
672    ///         EntryMode::DIR => {
673    ///             println!("Handling dir like start a new list via meta.path()")
674    ///         }
675    ///         EntryMode::Unknown => continue,
676    ///     }
677    /// }
678    /// # Ok(())
679    /// # }
680    /// ```
681    pub fn lister(&self, path: &str) -> Result<blocking::Lister> {
682        self.lister_options(path, options::ListOptions::default())
683    }
684
685    /// List entries within a given directory as an iterator with options.
686    ///
687    /// This function will create a new handle to list entries.
688    ///
689    /// An error will be returned if given path doesn't end with `/`.
690    pub fn lister_options(
691        &self,
692        path: &str,
693        opts: options::ListOptions,
694    ) -> Result<blocking::Lister> {
695        let l = self.handle.block_on(self.op.lister_options(path, opts))?;
696        Ok(blocking::Lister::new(self.handle.clone(), l))
697    }
698
699    /// Check if this operator can work correctly.
700    ///
701    /// We will send a `list` request to path and return any errors we met.
702    ///
703    /// ```
704    /// # use std::sync::Arc;
705    /// # use anyhow::Result;
706    /// use opendal::blocking;
707    /// use opendal::blocking::Operator;
708    /// use opendal::ErrorKind;
709    ///
710    /// # fn test(op: blocking::Operator) -> Result<()> {
711    /// op.check()?;
712    /// # Ok(())
713    /// # }
714    /// ```
715    pub fn check(&self) -> Result<()> {
716        let mut ds = self.lister("/")?;
717
718        match ds.next() {
719            Some(Err(e)) if e.kind() != ErrorKind::NotFound => Err(e),
720            _ => Ok(()),
721        }
722    }
723}
724
725impl From<Operator> for AsyncOperator {
726    fn from(val: Operator) -> Self {
727        val.op
728    }
729}