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}