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