opendal_core/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::sync::Arc;
19
20use crate::layers::*;
21use crate::raw::*;
22use crate::types::IntoOperatorUri;
23use crate::*;
24
25/// # Operator build API
26///
27/// Operator should be built via [`OperatorBuilder`]. We recommend to use [`Operator::new`] to get started:
28///
29/// ```
30/// # use anyhow::Result;
31/// use opendal_core::services::Memory;
32/// use opendal_core::Operator;
33/// async fn test() -> Result<()> {
34///     // Create memory backend builder.
35///     let builder = Memory::default();
36///
37///     // Build an `Operator` to start operating the storage.
38///     let op: Operator = Operator::new(builder)?.finish();
39///
40///     Ok(())
41/// }
42/// ```
43impl Operator {
44    /// Create a new operator with input builder.
45    ///
46    /// OpenDAL will call `builder.build()` internally, so we don't need
47    /// to import `opendal::Builder` trait.
48    ///
49    /// # Examples
50    ///
51    /// Read more backend init examples in [examples](https://github.com/apache/opendal/tree/main/examples).
52    ///
53    /// ```
54    /// # use anyhow::Result;
55    /// use opendal_core::services::Memory;
56    /// use opendal_core::Operator;
57    /// async fn test() -> Result<()> {
58    ///     // Create memory backend builder.
59    ///     let builder = Memory::default();
60    ///
61    ///     // Build an `Operator` to start operating the storage.
62    ///     let op: Operator = Operator::new(builder)?.finish();
63    ///
64    ///     Ok(())
65    /// }
66    /// ```
67    #[allow(clippy::new_ret_no_self)]
68    pub fn new<B: Builder>(ab: B) -> Result<OperatorBuilder<impl Access>> {
69        let acc = ab.build()?;
70        Ok(OperatorBuilder::new(acc))
71    }
72
73    /// Create a new operator from given config.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// # use anyhow::Result;
79    /// use std::collections::HashMap;
80    ///
81    /// use opendal_core::services::MemoryConfig;
82    /// use opendal_core::Operator;
83    /// async fn test() -> Result<()> {
84    ///     let cfg = MemoryConfig::default();
85    ///
86    ///     // Build an `Operator` to start operating the storage.
87    ///     let op: Operator = Operator::from_config(cfg)?.finish();
88    ///
89    ///     Ok(())
90    /// }
91    /// ```
92    pub fn from_config<C: Configurator>(cfg: C) -> Result<OperatorBuilder<impl Access>> {
93        let builder = cfg.into_builder();
94        let acc = builder.build()?;
95        Ok(OperatorBuilder::new(acc))
96    }
97
98    /// Create a new operator from given iterator in static dispatch.
99    ///
100    /// # Notes
101    ///
102    /// `from_iter` generates a `OperatorBuilder` which allows adding layer in zero-cost way.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// # use anyhow::Result;
108    /// use std::collections::HashMap;
109    ///
110    /// use opendal_core::services::Memory;
111    /// use opendal_core::Operator;
112    /// async fn test() -> Result<()> {
113    ///     let map = HashMap::new();
114    ///
115    ///     // Build an `Operator` to start operating the storage.
116    ///     let op: Operator = Operator::from_iter::<Memory>(map)?.finish();
117    ///
118    ///     Ok(())
119    /// }
120    /// ```
121    #[allow(clippy::should_implement_trait)]
122    pub fn from_iter<B: Builder>(
123        iter: impl IntoIterator<Item = (String, String)>,
124    ) -> Result<OperatorBuilder<impl Access>> {
125        let builder = B::Config::from_iter(iter)?.into_builder();
126        let acc = builder.build()?;
127        Ok(OperatorBuilder::new(acc))
128    }
129
130    /// Create a new operator by parsing configuration from a URI.
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// # use anyhow::Result;
136    /// use opendal_core::Operator;
137    ///
138    /// # fn example() -> Result<()> {
139    /// let op = Operator::from_uri("memory://localhost/")?;
140    /// # let _ = op;
141    /// # Ok(())
142    /// # }
143    /// ```
144    pub fn from_uri(uri: impl IntoOperatorUri) -> Result<Operator> {
145        OperatorRegistry::get().load(uri)
146    }
147
148    /// Create a new operator via given scheme and iterator of config value in dynamic dispatch.
149    ///
150    /// # Notes
151    ///
152    /// `via_iter` generates a `Operator` which allows building operator without generic type.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// # use anyhow::Result;
158    /// use std::collections::HashMap;
159    ///
160    /// use opendal_core::Operator;
161    /// use opendal_core::services;
162    ///
163    /// async fn test() -> Result<()> {
164    ///     let map: Vec<(String, String)> = vec![];
165    ///
166    ///     // Build an `Operator` to start operating the storage.
167    ///     let op: Operator = Operator::via_iter(services::MEMORY_SCHEME, map)?;
168    ///
169    ///     Ok(())
170    /// }
171    /// ```
172    pub fn via_iter(
173        scheme: impl AsRef<str>,
174        iter: impl IntoIterator<Item = (String, String)>,
175    ) -> Result<Operator> {
176        Operator::from_uri((scheme.as_ref(), iter))
177    }
178
179    /// Create a new layer with dynamic dispatch.
180    ///
181    /// Please note that `Layer` can modify internal contexts such as `HttpClient`
182    /// and `Runtime` for the operator. Therefore, it is recommended to add layers
183    /// before interacting with the storage. Adding or duplicating layers after
184    /// accessing the storage may result in unexpected behavior.
185    ///
186    /// # Notes
187    ///
188    /// `OperatorBuilder::layer()` is using static dispatch which is zero
189    /// cost. `Operator::layer()` is using dynamic dispatch which has a
190    /// bit runtime overhead with an extra vtable lookup and unable to
191    /// inline.
192    ///
193    /// It's always recommended to use `OperatorBuilder::layer()` instead.
194    ///
195    /// # Examples
196    ///
197    /// ```no_run
198    /// # use std::sync::Arc;
199    /// #
200    /// # use opendal_core::Result;
201    /// # use opendal_core::layers::HttpClientLayer;
202    /// # use opendal_core::raw::HttpClient;
203    /// # use opendal_core::services::Memory;
204    /// # use opendal_core::Operator;
205    /// #
206    /// # async fn test() -> Result<()> {
207    /// let client = HttpClient::new()?;
208    /// let op = Operator::new(Memory::default())?.finish();
209    /// let op = op.layer(HttpClientLayer::new(client));
210    /// // All operations will go through the new_layer
211    /// let _ = op.read("test_file").await?;
212    /// # Ok(())
213    /// # }
214    /// ```
215    #[must_use]
216    pub fn layer<L: Layer<Accessor>>(self, layer: L) -> Self {
217        Self::from_inner(Arc::new(
218            TypeEraseLayer.layer(layer.layer(self.into_inner())),
219        ))
220    }
221}
222
223/// OperatorBuilder is a typed builder to build an Operator.
224///
225/// # Notes
226///
227/// OpenDAL uses static dispatch internally and only performs dynamic
228/// dispatch at the outmost type erase layer. OperatorBuilder is the only
229/// public API provided by OpenDAL come with generic parameters.
230///
231/// It's required to call `finish` after the operator built.
232///
233/// # Examples
234///
235/// For users who want to support many services, we can build a helper function like the following:
236///
237/// ```
238/// use std::collections::HashMap;
239///
240/// use opendal_core::services;
241/// use opendal_core::Builder;
242/// use opendal_core::Operator;
243/// use opendal_core::Result;
244///
245/// fn init_service<B: Builder>(cfg: HashMap<String, String>) -> Result<Operator> {
246///     let op = Operator::from_iter::<B>(cfg)?
247///         // add layers
248///         // .layer(LoggingLayer::default())
249///         // .layer(RetryLayer::new())
250///         .finish();
251///
252///     Ok(op)
253/// }
254///
255/// async fn init(scheme: &str, cfg: HashMap<String, String>) -> Result<()> {
256///     let _ = match scheme {
257///         services::MEMORY_SCHEME => init_service::<services::Memory>(cfg)?,
258///         _ => todo!(),
259///     };
260///
261///     Ok(())
262/// }
263/// ```
264pub struct OperatorBuilder<A: Access> {
265    accessor: A,
266}
267
268impl<A: Access> OperatorBuilder<A> {
269    /// Create a new operator builder.
270    #[allow(clippy::new_ret_no_self)]
271    pub fn new(accessor: A) -> OperatorBuilder<impl Access> {
272        // Make sure error context layer has been attached.
273        OperatorBuilder { accessor }
274            .layer(ErrorContextLayer)
275            .layer(SimulateLayer::default())
276            .layer(CompleteLayer)
277            .layer(CorrectnessCheckLayer)
278    }
279
280    /// Create a new layer with static dispatch.
281    ///
282    /// # Notes
283    ///
284    /// `OperatorBuilder::layer()` is using static dispatch which is zero
285    /// cost. `Operator::layer()` is using dynamic dispatch which has a
286    /// bit runtime overhead with an extra vtable lookup and unable to
287    /// inline.
288    ///
289    /// It's always recommended to use `OperatorBuilder::layer()` instead.
290    ///
291    /// # Examples
292    ///
293    /// ```no_run
294    /// # use std::sync::Arc;
295    /// #
296    /// # use opendal_core::Result;
297    /// # use opendal_core::layers::HttpClientLayer;
298    /// # use opendal_core::raw::HttpClient;
299    /// # use opendal_core::services::Memory;
300    /// # use opendal_core::Operator;
301    /// #
302    /// # async fn test() -> Result<()> {
303    /// let client = HttpClient::new()?;
304    /// let op = Operator::new(Memory::default())?
305    ///     .layer(HttpClientLayer::new(client))
306    ///     .finish();
307    /// // All operations will go through the new_layer
308    /// let _ = op.read("test_file").await?;
309    /// # Ok(())
310    /// # }
311    /// ```
312    #[must_use]
313    pub fn layer<L: Layer<A>>(self, layer: L) -> OperatorBuilder<L::LayeredAccess> {
314        OperatorBuilder {
315            accessor: layer.layer(self.accessor),
316        }
317    }
318
319    /// Finish the building to construct an Operator.
320    pub fn finish(self) -> Operator {
321        let ob = self.layer(TypeEraseLayer);
322        Operator::from_inner(Arc::new(ob.accessor) as Accessor)
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use crate::services;
330
331    #[test]
332    fn via_iter_delegates_to_registry() {
333        let op = Operator::via_iter(services::MEMORY_SCHEME, [])
334            .expect("memory scheme should be registered");
335        let _ = op;
336    }
337}