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}