opendal/types/operator/
builder.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 std::collections::HashMap;
19use std::sync::Arc;
20
21use crate::layers::*;
22use crate::raw::*;
23use crate::types::IntoOperatorUri;
24use crate::*;
25
26/// # Operator build API
27///
28/// Operator should be built via [`OperatorBuilder`]. We recommend to use [`Operator::new`] to get started:
29///
30/// ```
31/// # use anyhow::Result;
32/// use opendal::services::Fs;
33/// use opendal::Operator;
34/// async fn test() -> Result<()> {
35///     // Create fs backend builder.
36///     let builder = Fs::default().root("/tmp");
37///
38///     // Build an `Operator` to start operating the storage.
39///     let op: Operator = Operator::new(builder)?.finish();
40///
41///     Ok(())
42/// }
43/// ```
44impl Operator {
45    /// Create a new operator with input builder.
46    ///
47    /// OpenDAL will call `builder.build()` internally, so we don't need
48    /// to import `opendal::Builder` trait.
49    ///
50    /// # Examples
51    ///
52    /// Read more backend init examples in [examples](https://github.com/apache/opendal/tree/main/examples).
53    ///
54    /// ```
55    /// # use anyhow::Result;
56    /// use opendal::services::Fs;
57    /// use opendal::Operator;
58    /// async fn test() -> Result<()> {
59    ///     // Create fs backend builder.
60    ///     let builder = Fs::default().root("/tmp");
61    ///
62    ///     // Build an `Operator` to start operating the storage.
63    ///     let op: Operator = Operator::new(builder)?.finish();
64    ///
65    ///     Ok(())
66    /// }
67    /// ```
68    #[allow(clippy::new_ret_no_self)]
69    pub fn new<B: Builder>(ab: B) -> Result<OperatorBuilder<impl Access>> {
70        let acc = ab.build()?;
71        Ok(OperatorBuilder::new(acc))
72    }
73
74    /// Create a new operator from given config.
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// # use anyhow::Result;
80    /// use std::collections::HashMap;
81    ///
82    /// use opendal::services::MemoryConfig;
83    /// use opendal::Operator;
84    /// async fn test() -> Result<()> {
85    ///     let cfg = MemoryConfig::default();
86    ///
87    ///     // Build an `Operator` to start operating the storage.
88    ///     let op: Operator = Operator::from_config(cfg)?.finish();
89    ///
90    ///     Ok(())
91    /// }
92    /// ```
93    pub fn from_config<C: Configurator>(cfg: C) -> Result<OperatorBuilder<impl Access>> {
94        let builder = cfg.into_builder();
95        let acc = builder.build()?;
96        Ok(OperatorBuilder::new(acc))
97    }
98
99    /// Create a new operator from given iterator in static dispatch.
100    ///
101    /// # Notes
102    ///
103    /// `from_iter` generates a `OperatorBuilder` which allows adding layer in zero-cost way.
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// # use anyhow::Result;
109    /// use std::collections::HashMap;
110    ///
111    /// use opendal::services::Fs;
112    /// use opendal::Operator;
113    /// async fn test() -> Result<()> {
114    ///     let map = HashMap::from([
115    ///         // Set the root for fs, all operations will happen under this root.
116    ///         //
117    ///         // NOTE: the root must be absolute path.
118    ///         ("root".to_string(), "/tmp".to_string()),
119    ///     ]);
120    ///
121    ///     // Build an `Operator` to start operating the storage.
122    ///     let op: Operator = Operator::from_iter::<Fs>(map)?.finish();
123    ///
124    ///     Ok(())
125    /// }
126    /// ```
127    #[allow(clippy::should_implement_trait)]
128    pub fn from_iter<B: Builder>(
129        iter: impl IntoIterator<Item = (String, String)>,
130    ) -> Result<OperatorBuilder<impl Access>> {
131        let builder = B::Config::from_iter(iter)?.into_builder();
132        let acc = builder.build()?;
133        Ok(OperatorBuilder::new(acc))
134    }
135
136    /// Create a new operator by parsing configuration from a URI.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// # use anyhow::Result;
142    /// use opendal::Operator;
143    ///
144    /// # fn example() -> Result<()> {
145    /// let op = Operator::from_uri("memory://localhost/")?;
146    /// # let _ = op;
147    /// # Ok(())
148    /// # }
149    /// ```
150    pub fn from_uri(uri: impl IntoOperatorUri) -> Result<Operator> {
151        crate::DEFAULT_OPERATOR_REGISTRY.load(uri)
152    }
153
154    /// Create a new operator via given scheme and iterator of config value in dynamic dispatch.
155    ///
156    /// # Notes
157    ///
158    /// `via_iter` generates a `Operator` which allows building operator without generic type.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// # use anyhow::Result;
164    /// use std::collections::HashMap;
165    ///
166    /// use opendal::Operator;
167    /// use opendal::Scheme;
168    /// async fn test() -> Result<()> {
169    ///     let map = [
170    ///         // Set the root for fs, all operations will happen under this root.
171    ///         //
172    ///         // NOTE: the root must be absolute path.
173    ///         ("root".to_string(), "/tmp".to_string()),
174    ///     ];
175    ///
176    ///     // Build an `Operator` to start operating the storage.
177    ///     let op: Operator = Operator::via_iter(Scheme::Fs, map)?;
178    ///
179    ///     Ok(())
180    /// }
181    /// ```
182    #[allow(unused_variables, unreachable_code)]
183    pub fn via_iter(
184        scheme: Scheme,
185        iter: impl IntoIterator<Item = (String, String)>,
186    ) -> Result<Operator> {
187        let op = match scheme {
188            #[cfg(feature = "services-aliyun-drive")]
189            Scheme::AliyunDrive => Self::from_iter::<services::AliyunDrive>(iter)?.finish(),
190            #[cfg(feature = "services-alluxio")]
191            Scheme::Alluxio => Self::from_iter::<services::Alluxio>(iter)?.finish(),
192            #[cfg(feature = "services-cloudflare-kv")]
193            Scheme::CloudflareKv => Self::from_iter::<services::CloudflareKv>(iter)?.finish(),
194            #[cfg(feature = "services-compfs")]
195            Scheme::Compfs => Self::from_iter::<services::Compfs>(iter)?.finish(),
196            #[cfg(feature = "services-upyun")]
197            Scheme::Upyun => Self::from_iter::<services::Upyun>(iter)?.finish(),
198            #[cfg(feature = "services-koofr")]
199            Scheme::Koofr => Self::from_iter::<services::Koofr>(iter)?.finish(),
200            #[cfg(feature = "services-yandex-disk")]
201            Scheme::YandexDisk => Self::from_iter::<services::YandexDisk>(iter)?.finish(),
202            #[cfg(feature = "services-pcloud")]
203            Scheme::Pcloud => Self::from_iter::<services::Pcloud>(iter)?.finish(),
204            #[cfg(feature = "services-azblob")]
205            Scheme::Azblob => Self::from_iter::<services::Azblob>(iter)?.finish(),
206            #[cfg(feature = "services-azdls")]
207            Scheme::Azdls => Self::from_iter::<services::Azdls>(iter)?.finish(),
208            #[cfg(feature = "services-azfile")]
209            Scheme::Azfile => Self::from_iter::<services::Azfile>(iter)?.finish(),
210            #[cfg(feature = "services-b2")]
211            Scheme::B2 => Self::from_iter::<services::B2>(iter)?.finish(),
212            #[cfg(feature = "services-cacache")]
213            Scheme::Cacache => Self::from_iter::<services::Cacache>(iter)?.finish(),
214            #[cfg(feature = "services-cos")]
215            Scheme::Cos => Self::from_iter::<services::Cos>(iter)?.finish(),
216            #[cfg(feature = "services-d1")]
217            Scheme::D1 => Self::from_iter::<services::D1>(iter)?.finish(),
218            #[cfg(feature = "services-dashmap")]
219            Scheme::Dashmap => Self::from_iter::<services::Dashmap>(iter)?.finish(),
220            #[cfg(feature = "services-dropbox")]
221            Scheme::Dropbox => Self::from_iter::<services::Dropbox>(iter)?.finish(),
222            #[cfg(feature = "services-etcd")]
223            Scheme::Etcd => Self::from_iter::<services::Etcd>(iter)?.finish(),
224            #[cfg(feature = "services-foundationdb")]
225            Scheme::Foundationdb => Self::from_iter::<services::Foundationdb>(iter)?.finish(),
226            #[cfg(feature = "services-fs")]
227            Scheme::Fs => Self::from_iter::<services::Fs>(iter)?.finish(),
228            #[cfg(feature = "services-ftp")]
229            Scheme::Ftp => Self::from_iter::<services::Ftp>(iter)?.finish(),
230            #[cfg(feature = "services-gcs")]
231            Scheme::Gcs => Self::from_iter::<services::Gcs>(iter)?.finish(),
232            #[cfg(feature = "services-ghac")]
233            Scheme::Ghac => Self::from_iter::<services::Ghac>(iter)?.finish(),
234            #[cfg(feature = "services-gridfs")]
235            Scheme::Gridfs => Self::from_iter::<services::Gridfs>(iter)?.finish(),
236            #[cfg(feature = "services-github")]
237            Scheme::Github => Self::from_iter::<services::Github>(iter)?.finish(),
238            #[cfg(feature = "services-hdfs")]
239            Scheme::Hdfs => Self::from_iter::<services::Hdfs>(iter)?.finish(),
240            #[cfg(feature = "services-http")]
241            Scheme::Http => Self::from_iter::<services::Http>(iter)?.finish(),
242            #[cfg(feature = "services-huggingface")]
243            Scheme::Huggingface => Self::from_iter::<services::Huggingface>(iter)?.finish(),
244            #[cfg(feature = "services-ipfs")]
245            Scheme::Ipfs => Self::from_iter::<services::Ipfs>(iter)?.finish(),
246            #[cfg(feature = "services-ipmfs")]
247            Scheme::Ipmfs => Self::from_iter::<services::Ipmfs>(iter)?.finish(),
248            #[cfg(feature = "services-memcached")]
249            Scheme::Memcached => Self::from_iter::<services::Memcached>(iter)?.finish(),
250            #[cfg(feature = "services-memory")]
251            Scheme::Memory => Self::from_iter::<services::Memory>(iter)?.finish(),
252            #[cfg(feature = "services-mini-moka")]
253            Scheme::MiniMoka => Self::from_iter::<services::MiniMoka>(iter)?.finish(),
254            #[cfg(feature = "services-moka")]
255            Scheme::Moka => Self::from_iter::<services::Moka>(iter)?.finish(),
256            #[cfg(feature = "services-monoiofs")]
257            Scheme::Monoiofs => Self::from_iter::<services::Monoiofs>(iter)?.finish(),
258            #[cfg(feature = "services-mysql")]
259            Scheme::Mysql => Self::from_iter::<services::Mysql>(iter)?.finish(),
260            #[cfg(feature = "services-obs")]
261            Scheme::Obs => Self::from_iter::<services::Obs>(iter)?.finish(),
262            #[cfg(feature = "services-onedrive")]
263            Scheme::Onedrive => Self::from_iter::<services::Onedrive>(iter)?.finish(),
264            #[cfg(feature = "services-postgresql")]
265            Scheme::Postgresql => Self::from_iter::<services::Postgresql>(iter)?.finish(),
266            #[cfg(feature = "services-gdrive")]
267            Scheme::Gdrive => Self::from_iter::<services::Gdrive>(iter)?.finish(),
268            #[cfg(feature = "services-oss")]
269            Scheme::Oss => Self::from_iter::<services::Oss>(iter)?.finish(),
270            #[cfg(feature = "services-persy")]
271            Scheme::Persy => Self::from_iter::<services::Persy>(iter)?.finish(),
272            #[cfg(feature = "services-redis")]
273            Scheme::Redis => Self::from_iter::<services::Redis>(iter)?.finish(),
274            #[cfg(feature = "services-rocksdb")]
275            Scheme::Rocksdb => Self::from_iter::<services::Rocksdb>(iter)?.finish(),
276            #[cfg(feature = "services-s3")]
277            Scheme::S3 => Self::from_iter::<services::S3>(iter)?.finish(),
278            #[cfg(feature = "services-seafile")]
279            Scheme::Seafile => Self::from_iter::<services::Seafile>(iter)?.finish(),
280            #[cfg(feature = "services-sftp")]
281            Scheme::Sftp => Self::from_iter::<services::Sftp>(iter)?.finish(),
282            #[cfg(feature = "services-sled")]
283            Scheme::Sled => Self::from_iter::<services::Sled>(iter)?.finish(),
284            #[cfg(feature = "services-sqlite")]
285            Scheme::Sqlite => Self::from_iter::<services::Sqlite>(iter)?.finish(),
286            #[cfg(feature = "services-swift")]
287            Scheme::Swift => Self::from_iter::<services::Swift>(iter)?.finish(),
288            #[cfg(feature = "services-tikv")]
289            Scheme::Tikv => Self::from_iter::<services::Tikv>(iter)?.finish(),
290            #[cfg(feature = "services-vercel-artifacts")]
291            Scheme::VercelArtifacts => Self::from_iter::<services::VercelArtifacts>(iter)?.finish(),
292            #[cfg(feature = "services-vercel-blob")]
293            Scheme::VercelBlob => Self::from_iter::<services::VercelBlob>(iter)?.finish(),
294            #[cfg(feature = "services-webdav")]
295            Scheme::Webdav => Self::from_iter::<services::Webdav>(iter)?.finish(),
296            #[cfg(feature = "services-webhdfs")]
297            Scheme::Webhdfs => Self::from_iter::<services::Webhdfs>(iter)?.finish(),
298            #[cfg(feature = "services-redb")]
299            Scheme::Redb => Self::from_iter::<services::Redb>(iter)?.finish(),
300            #[cfg(feature = "services-mongodb")]
301            Scheme::Mongodb => Self::from_iter::<services::Mongodb>(iter)?.finish(),
302            #[cfg(feature = "services-hdfs-native")]
303            Scheme::HdfsNative => Self::from_iter::<services::HdfsNative>(iter)?.finish(),
304            #[cfg(feature = "services-lakefs")]
305            Scheme::Lakefs => Self::from_iter::<services::Lakefs>(iter)?.finish(),
306            v => {
307                return Err(Error::new(
308                    ErrorKind::Unsupported,
309                    "scheme is not enabled or supported",
310                )
311                .with_context("scheme", v));
312            }
313        };
314
315        Ok(op)
316    }
317
318    /// Create a new operator from given map.
319    ///
320    /// # Notes
321    ///
322    /// from_map is using static dispatch layers which is zero cost. via_map is
323    /// using dynamic dispatch layers which has a bit runtime overhead with an
324    /// extra vtable lookup and unable to inline. But from_map requires generic
325    /// type parameter which is not always easy to be used.
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// # use anyhow::Result;
331    /// use std::collections::HashMap;
332    ///
333    /// use opendal::services::Memory;
334    /// use opendal::Operator;
335    /// async fn test() -> Result<()> {
336    ///     let map = HashMap::new();
337    ///
338    ///     // Build an `Operator` to start operating the storage.
339    ///     let op: Operator = Operator::from_map::<Memory>(map)?.finish();
340    ///
341    ///     Ok(())
342    /// }
343    /// ```
344    #[deprecated = "use from_iter instead"]
345    pub fn from_map<B: Builder>(
346        map: HashMap<String, String>,
347    ) -> Result<OperatorBuilder<impl Access>> {
348        Self::from_iter::<B>(map)
349    }
350
351    /// Create a new operator from given scheme and map.
352    ///
353    /// # Notes
354    ///
355    /// from_map is using static dispatch layers which is zero cost. via_map is
356    /// using dynamic dispatch layers which has a bit runtime overhead with an
357    /// extra vtable lookup and unable to inline. But from_map requires generic
358    /// type parameter which is not always easy to be used.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// # use anyhow::Result;
364    /// use std::collections::HashMap;
365    ///
366    /// use opendal::Operator;
367    /// use opendal::Scheme;
368    /// async fn test() -> Result<()> {
369    ///     let map = HashMap::new();
370    ///
371    ///     // Build an `Operator` to start operating the storage.
372    ///     let op: Operator = Operator::via_map(Scheme::Memory, map)?;
373    ///
374    ///     Ok(())
375    /// }
376    /// ```
377    #[deprecated = "use via_iter instead"]
378    pub fn via_map(scheme: Scheme, map: HashMap<String, String>) -> Result<Operator> {
379        Self::via_iter(scheme, map)
380    }
381
382    /// Create a new layer with dynamic dispatch.
383    ///
384    /// Please note that `Layer` can modify internal contexts such as `HttpClient`
385    /// and `Runtime` for the operator. Therefore, it is recommended to add layers
386    /// before interacting with the storage. Adding or duplicating layers after
387    /// accessing the storage may result in unexpected behavior.
388    ///
389    /// # Notes
390    ///
391    /// `OperatorBuilder::layer()` is using static dispatch which is zero
392    /// cost. `Operator::layer()` is using dynamic dispatch which has a
393    /// bit runtime overhead with an extra vtable lookup and unable to
394    /// inline.
395    ///
396    /// It's always recommended to use `OperatorBuilder::layer()` instead.
397    ///
398    /// # Examples
399    ///
400    /// ```no_run
401    /// # use std::sync::Arc;
402    /// # use anyhow::Result;
403    /// use opendal::layers::LoggingLayer;
404    /// use opendal::services::Memory;
405    /// use opendal::Operator;
406    ///
407    /// # async fn test() -> Result<()> {
408    /// let op = Operator::new(Memory::default())?.finish();
409    /// let op = op.layer(LoggingLayer::default());
410    /// // All operations will go through the new_layer
411    /// let _ = op.read("test_file").await?;
412    /// # Ok(())
413    /// # }
414    /// ```
415    #[must_use]
416    pub fn layer<L: Layer<Accessor>>(self, layer: L) -> Self {
417        Self::from_inner(Arc::new(
418            TypeEraseLayer.layer(layer.layer(self.into_inner())),
419        ))
420    }
421}
422
423/// OperatorBuilder is a typed builder to build an Operator.
424///
425/// # Notes
426///
427/// OpenDAL uses static dispatch internally and only performs dynamic
428/// dispatch at the outmost type erase layer. OperatorBuilder is the only
429/// public API provided by OpenDAL come with generic parameters.
430///
431/// It's required to call `finish` after the operator built.
432///
433/// # Examples
434///
435/// For users who want to support many services, we can build a helper function like the following:
436///
437/// ```
438/// use std::collections::HashMap;
439///
440/// use opendal::layers::LoggingLayer;
441/// use opendal::layers::RetryLayer;
442/// use opendal::services;
443/// use opendal::Builder;
444/// use opendal::Operator;
445/// use opendal::Result;
446/// use opendal::Scheme;
447///
448/// fn init_service<B: Builder>(cfg: HashMap<String, String>) -> Result<Operator> {
449///     let op = Operator::from_map::<B>(cfg)?
450///         .layer(LoggingLayer::default())
451///         .layer(RetryLayer::new())
452///         .finish();
453///
454///     Ok(op)
455/// }
456///
457/// async fn init(scheme: Scheme, cfg: HashMap<String, String>) -> Result<()> {
458///     let _ = match scheme {
459///         Scheme::Memory => init_service::<services::Memory>(cfg)?,
460///         _ => todo!(),
461///     };
462///
463///     Ok(())
464/// }
465/// ```
466pub struct OperatorBuilder<A: Access> {
467    accessor: A,
468}
469
470impl<A: Access> OperatorBuilder<A> {
471    /// Create a new operator builder.
472    #[allow(clippy::new_ret_no_self)]
473    pub fn new(accessor: A) -> OperatorBuilder<impl Access> {
474        // Make sure error context layer has been attached.
475        OperatorBuilder { accessor }
476            .layer(ErrorContextLayer)
477            .layer(CompleteLayer)
478            .layer(CorrectnessCheckLayer)
479    }
480
481    /// Create a new layer with static dispatch.
482    ///
483    /// # Notes
484    ///
485    /// `OperatorBuilder::layer()` is using static dispatch which is zero
486    /// cost. `Operator::layer()` is using dynamic dispatch which has a
487    /// bit runtime overhead with an extra vtable lookup and unable to
488    /// inline.
489    ///
490    /// It's always recommended to use `OperatorBuilder::layer()` instead.
491    ///
492    /// # Examples
493    ///
494    /// ```no_run
495    /// # use std::sync::Arc;
496    /// # use anyhow::Result;
497    /// use opendal::layers::LoggingLayer;
498    /// use opendal::services::Memory;
499    /// use opendal::Operator;
500    ///
501    /// # async fn test() -> Result<()> {
502    /// let op = Operator::new(Memory::default())?
503    ///     .layer(LoggingLayer::default())
504    ///     .finish();
505    /// // All operations will go through the new_layer
506    /// let _ = op.read("test_file").await?;
507    /// # Ok(())
508    /// # }
509    /// ```
510    #[must_use]
511    pub fn layer<L: Layer<A>>(self, layer: L) -> OperatorBuilder<L::LayeredAccess> {
512        OperatorBuilder {
513            accessor: layer.layer(self.accessor),
514        }
515    }
516
517    /// Finish the building to construct an Operator.
518    pub fn finish(self) -> Operator {
519        let ob = self.layer(TypeEraseLayer);
520        Operator::from_inner(Arc::new(ob.accessor) as Accessor)
521    }
522}