use std::path::{
absolute,
PathBuf,
};
use anyhow::anyhow;
use async_trait::async_trait;
use bytes::Bytes;
#[cfg(feature = "mock")]
use mockall::automock;
use object_store::path::Path;
use object_store::{
DynObjectStore,
ObjectStoreScheme,
PutPayload,
};
use reqwest::Url;
use crate::errors::*;
#[cfg_attr(feature = "mock", automock)]
#[async_trait]
pub trait ObjectStoreWrapper {
fn scheme(&self) -> ObjectStoreScheme;
async fn put(&self, data: Bytes) -> EmptyResult;
async fn get(&self) -> anyhow::Result<Bytes>;
}
#[derive(Debug)]
pub struct SkObjectStore {
scheme: ObjectStoreScheme,
store: Box<DynObjectStore>,
path: Path,
}
impl SkObjectStore {
pub fn new(path_str: &str) -> anyhow::Result<SkObjectStore> {
let (scheme, path) = parse_path(path_str)?;
let store: Box<DynObjectStore> = match scheme {
ObjectStoreScheme::Local => Box::new(object_store::local::LocalFileSystem::new()),
ObjectStoreScheme::Memory => Box::new(object_store::memory::InMemory::new()),
ObjectStoreScheme::AmazonS3 => {
Box::new(object_store::aws::AmazonS3Builder::from_env().with_url(path_str).build()?)
},
ObjectStoreScheme::MicrosoftAzure => Box::new(
object_store::azure::MicrosoftAzureBuilder::from_env()
.with_url(path_str)
.build()?,
),
ObjectStoreScheme::GoogleCloudStorage => Box::new(
object_store::gcp::GoogleCloudStorageBuilder::from_env()
.with_url(path_str)
.build()?,
),
ObjectStoreScheme::Http => Box::new(object_store::http::HttpBuilder::new().with_url(path_str).build()?),
_ => unimplemented!(),
};
Ok(SkObjectStore { scheme, store, path })
}
}
#[async_trait]
impl ObjectStoreWrapper for SkObjectStore {
fn scheme(&self) -> ObjectStoreScheme {
self.scheme.clone()
}
async fn put(&self, data: Bytes) -> EmptyResult {
let payload = PutPayload::from_bytes(data);
self.store.put(&self.path, payload).await?;
Ok(())
}
async fn get(&self) -> anyhow::Result<Bytes> {
Ok(self.store.get(&self.path).await?.bytes().await?)
}
}
fn parse_path(path_str: &str) -> anyhow::Result<(ObjectStoreScheme, Path)> {
let url = match Url::parse(path_str) {
Err(url::ParseError::RelativeUrlWithoutBase) => {
let path = absolute(PathBuf::from(path_str))?;
Url::from_file_path(path).map_err(|e| anyhow!("could not create URL from file path: {e:?}"))?
},
res => res?,
};
Ok(ObjectStoreScheme::parse(&url)?)
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod test {
use sk_testutils::*;
use super::*;
#[rstest]
fn test_new_sk_object_store_invalid() {
let _ = SkObjectStore::new("oracle3://foo/bar").unwrap_err();
}
#[rstest]
fn test_new_sk_object_store() {
let store = SkObjectStore::new("s3://foo/bar").unwrap();
assert_eq!(store.scheme(), ObjectStoreScheme::AmazonS3);
}
#[rstest]
#[case::with_base("file:///tmp/foo")]
#[case::without_base("/tmp/foo")]
#[case::relative("foo")]
fn test_new_sk_object_store_local_path(#[case] path: &str) {
let store = SkObjectStore::new(path).unwrap();
assert_eq!(store.scheme(), ObjectStoreScheme::Local);
}
}