blob: 4f91366123ccb5e2eab228a9184a5b604fcbbf2f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::collections::HashMap;
use std::sync::Arc;
use crate::layers::*;
use crate::raw::*;
use crate::*;
/// # Operator build API
///
/// Operator should be built via [`OperatorBuilder`]. We recommend to use [`Operator::new`] to get started:
///
/// ```
/// # use anyhow::Result;
/// use opendal::services::Fs;
/// use opendal::Operator;
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// // Create fs backend builder.
/// let mut builder = Fs::default();
/// // Set the root for fs, all operations will happen under this root.
/// //
/// // NOTE: the root must be absolute path.
/// builder.root("/tmp");
///
/// // Build an `Operator` to start operating the storage.
/// let op: Operator = Operator::new(builder)?.finish();
///
/// Ok(())
/// }
/// ```
impl Operator {
/// Create a new operator with input builder.
///
/// OpenDAL will call `builder.build()` internally, so we don't need
/// to import `opendal::Builder` trait.
///
/// # Examples
///
/// Read more backend init examples in [examples](https://github.com/apache/incubator-opendal/tree/main/examples).
///
/// ```
/// # use anyhow::Result;
/// use opendal::services::Fs;
/// use opendal::Operator;
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// // Create fs backend builder.
/// let mut builder = Fs::default();
/// // Set the root for fs, all operations will happen under this root.
/// //
/// // NOTE: the root must be absolute path.
/// builder.root("/tmp");
///
/// // Build an `Operator` to start operating the storage.
/// let op: Operator = Operator::new(builder)?.finish();
///
/// Ok(())
/// }
/// ```
#[allow(clippy::new_ret_no_self)]
pub fn new<B: Builder>(mut ab: B) -> Result<OperatorBuilder<impl Accessor>> {
let acc = ab.build()?;
Ok(OperatorBuilder::new(acc))
}
/// Create a new operator from given map.
///
/// # Notes
///
/// from_map is using static dispatch layers which is zero cost. via_map is
/// using dynamic dispatch layers which has a bit runtime overhead with an
/// extra vtable lookup and unable to inline. But from_map requires generic
/// type parameter which is not always easy to be used.
///
/// # Examples
///
/// ```
/// # use anyhow::Result;
/// use std::collections::HashMap;
///
/// use opendal::services::Fs;
/// use opendal::Operator;
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let map = HashMap::from([
/// // Set the root for fs, all operations will happen under this root.
/// //
/// // NOTE: the root must be absolute path.
/// ("root".to_string(), "/tmp".to_string()),
/// ]);
///
/// // Build an `Operator` to start operating the storage.
/// let op: Operator = Operator::from_map::<Fs>(map)?.finish();
///
/// Ok(())
/// }
/// ```
pub fn from_map<B: Builder>(
map: HashMap<String, String>,
) -> Result<OperatorBuilder<impl Accessor>> {
let acc = B::from_map(map).build()?;
Ok(OperatorBuilder::new(acc))
}
/// Create a new operator from given scheme and map.
///
/// # Notes
///
/// from_map is using static dispatch layers which is zero cost. via_map is
/// using dynamic dispatch layers which has a bit runtime overhead with an
/// extra vtable lookup and unable to inline. But from_map requires generic
/// type parameter which is not always easy to be used.
///
/// # Examples
///
/// ```
/// # use anyhow::Result;
/// use std::collections::HashMap;
///
/// use opendal::Operator;
/// use opendal::Scheme;
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let map = HashMap::from([
/// // Set the root for fs, all operations will happen under this root.
/// //
/// // NOTE: the root must be absolute path.
/// ("root".to_string(), "/tmp".to_string()),
/// ]);
///
/// // Build an `Operator` to start operating the storage.
/// let op: Operator = Operator::via_map(Scheme::Fs, map)?;
///
/// Ok(())
/// }
/// ```
pub fn via_map(scheme: Scheme, map: HashMap<String, String>) -> Result<Operator> {
let op = match scheme {
#[cfg(feature = "services-azblob")]
Scheme::Azblob => Self::from_map::<services::Azblob>(map)?.finish(),
#[cfg(feature = "services-azdfs")]
Scheme::Azdfs => Self::from_map::<services::Azdfs>(map)?.finish(),
#[cfg(feature = "services-cos")]
Scheme::Cos => Self::from_map::<services::Cos>(map)?.finish(),
#[cfg(feature = "services-dashmap")]
Scheme::Dashmap => Self::from_map::<services::Dashmap>(map)?.finish(),
#[cfg(feature = "services-fs")]
Scheme::Fs => Self::from_map::<services::Fs>(map)?.finish(),
#[cfg(feature = "services-ftp")]
Scheme::Ftp => Self::from_map::<services::Ftp>(map)?.finish(),
#[cfg(feature = "services-gcs")]
Scheme::Gcs => Self::from_map::<services::Gcs>(map)?.finish(),
#[cfg(feature = "services-ghac")]
Scheme::Ghac => Self::from_map::<services::Ghac>(map)?.finish(),
#[cfg(feature = "services-hdfs")]
Scheme::Hdfs => Self::from_map::<services::Hdfs>(map)?.finish(),
#[cfg(feature = "services-http")]
Scheme::Http => Self::from_map::<services::Http>(map)?.finish(),
#[cfg(feature = "services-ipfs")]
Scheme::Ipfs => Self::from_map::<services::Ipfs>(map)?.finish(),
#[cfg(feature = "services-ipmfs")]
Scheme::Ipmfs => Self::from_map::<services::Ipmfs>(map)?.finish(),
#[cfg(feature = "services-memcached")]
Scheme::Memcached => Self::from_map::<services::Memcached>(map)?.finish(),
#[cfg(feature = "services-memory")]
Scheme::Memory => Self::from_map::<services::Memory>(map)?.finish(),
#[cfg(feature = "services-moka")]
Scheme::Moka => Self::from_map::<services::Moka>(map)?.finish(),
#[cfg(feature = "services-obs")]
Scheme::Obs => Self::from_map::<services::Obs>(map)?.finish(),
#[cfg(feature = "services-onedrive")]
Scheme::Onedrive => Self::from_map::<services::Onedrive>(map)?.finish(),
#[cfg(feature = "services-gdrive")]
Scheme::Gdrive => Self::from_map::<services::Gdrive>(map)?.finish(),
#[cfg(feature = "services-oss")]
Scheme::Oss => Self::from_map::<services::Oss>(map)?.finish(),
#[cfg(feature = "services-redis")]
Scheme::Redis => Self::from_map::<services::Redis>(map)?.finish(),
#[cfg(feature = "services-rocksdb")]
Scheme::Rocksdb => Self::from_map::<services::Rocksdb>(map)?.finish(),
#[cfg(feature = "services-s3")]
Scheme::S3 => Self::from_map::<services::S3>(map)?.finish(),
#[cfg(feature = "services-sftp")]
Scheme::Sftp => Self::from_map::<services::Sftp>(map)?.finish(),
#[cfg(feature = "services-sled")]
Scheme::Sled => Self::from_map::<services::Sled>(map)?.finish(),
#[cfg(feature = "services-supabase")]
Scheme::Supabase => Self::from_map::<services::Supabase>(map)?.finish(),
#[cfg(feature = "services-vercel-artifacts")]
Scheme::VercelArtifacts => Self::from_map::<services::VercelArtifacts>(map)?.finish(),
#[cfg(feature = "services-wasabi")]
Scheme::Wasabi => Self::from_map::<services::Wasabi>(map)?.finish(),
#[cfg(feature = "services-webdav")]
Scheme::Webdav => Self::from_map::<services::Webdav>(map)?.finish(),
#[cfg(feature = "services-webhdfs")]
Scheme::Webhdfs => Self::from_map::<services::Webhdfs>(map)?.finish(),
v => {
return Err(Error::new(
ErrorKind::Unsupported,
"scheme is not enabled or supported",
)
.with_context("scheme", v))
}
};
Ok(op)
}
/// Create a new layer with dynamic dispatch.
///
/// # Notes
///
/// `OperatorBuilder::layer()` is using static dispatch which is zero
/// cost. `Operator::layer()` is using dynamic dispatch which has a
/// bit runtime overhead with an extra vtable lookup and unable to
/// inline.
///
/// It's always recommended to use `OperatorBuilder::layer()` instead.
///
/// # Examples
///
/// ```no_run
/// # use std::sync::Arc;
/// # use anyhow::Result;
/// use opendal::layers::LoggingLayer;
/// use opendal::services::Fs;
/// use opendal::Operator;
///
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// let op = Operator::new(Fs::default())?.finish();
/// let op = op.layer(LoggingLayer::default());
/// // All operations will go through the new_layer
/// let _ = op.read("test_file").await?;
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn layer<L: Layer<FusedAccessor>>(self, layer: L) -> Self {
Self::from_inner(Arc::new(
TypeEraseLayer.layer(layer.layer(self.into_inner())),
))
}
}
/// OperatorBuilder is a typed builder to build an Operator.
///
/// # Notes
///
/// OpenDAL uses static dispatch internally and only performs dynamic
/// dispatch at the outmost type erase layer. OperatorBuilder is the only
/// public API provided by OpenDAL come with generic parameters.
///
/// It's required to call `finish` after the operator built.
///
/// # Examples
///
/// For users who want to support many services, we can build a helper function like the following:
///
/// ```
/// use std::collections::HashMap;
///
/// use opendal::layers::LoggingLayer;
/// use opendal::layers::RetryLayer;
/// use opendal::services;
/// use opendal::Builder;
/// use opendal::Operator;
/// use opendal::Result;
/// use opendal::Scheme;
///
/// fn init_service<B: Builder>(cfg: HashMap<String, String>) -> Result<Operator> {
/// let op = Operator::from_map::<B>(cfg)?
/// .layer(LoggingLayer::default())
/// .layer(RetryLayer::new())
/// .finish();
///
/// Ok(op)
/// }
///
/// async fn init(scheme: Scheme, cfg: HashMap<String, String>) -> Result<()> {
/// let _ = match scheme {
/// Scheme::S3 => init_service::<services::S3>(cfg)?,
/// Scheme::Fs => init_service::<services::Fs>(cfg)?,
/// _ => todo!(),
/// };
///
/// Ok(())
/// }
/// ```
pub struct OperatorBuilder<A: Accessor> {
accessor: A,
}
impl<A: Accessor> OperatorBuilder<A> {
/// Create a new operator builder.
#[allow(clippy::new_ret_no_self)]
pub fn new(accessor: A) -> OperatorBuilder<impl Accessor> {
// Make sure error context layer has been attached.
OperatorBuilder { accessor }
.layer(ErrorContextLayer)
.layer(CompleteLayer)
}
/// Create a new layer with static dispatch.
///
/// # Notes
///
/// `OperatorBuilder::layer()` is using static dispatch which is zero
/// cost. `Operator::layer()` is using dynamic dispatch which has a
/// bit runtime overhead with an extra vtable lookup and unable to
/// inline.
///
/// It's always recommended to use `OperatorBuilder::layer()` instead.
///
/// # Examples
///
/// ```no_run
/// # use std::sync::Arc;
/// # use anyhow::Result;
/// use opendal::layers::LoggingLayer;
/// use opendal::services::Fs;
/// use opendal::Operator;
///
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// let op = Operator::new(Fs::default())?
/// .layer(LoggingLayer::default())
/// .finish();
/// // All operations will go through the new_layer
/// let _ = op.read("test_file").await?;
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn layer<L: Layer<A>>(self, layer: L) -> OperatorBuilder<L::LayeredAccessor> {
OperatorBuilder {
accessor: layer.layer(self.accessor),
}
}
/// Finish the building to construct an Operator.
pub fn finish(self) -> Operator {
let ob = self.layer(TypeEraseLayer);
Operator::from_inner(Arc::new(ob.accessor) as FusedAccessor)
}
}