#[cfg(feature = "storage-aws")]
use object_store::aws::AmazonS3;
#[cfg(feature = "storage-azure")]
use object_store::azure::MicrosoftAzure;
#[cfg(feature = "storage-gcp")]
use object_store::gcp::GoogleCloudStorage;
#[cfg(feature = "storage-http")]
use object_store::http::HttpStore;
#[cfg(all(feature = "storage", not(target_arch = "wasm32")))]
use object_store::local::LocalFileSystem;
use object_store::memory::InMemory;
mod builder;
#[cfg(feature = "storage-azure")]
pub use builder::AzureObjectStorageBuilder;
#[cfg(feature = "storage-gcp")]
pub use builder::GcpObjectStorageBuilder;
#[cfg(feature = "storage-http")]
pub use builder::HttpObjectStorageBuilder;
#[cfg(all(feature = "storage", not(target_arch = "wasm32")))]
pub use builder::LocalObjectStorageBuilder;
pub use builder::MemoryObjectStorageBuilder;
#[cfg(feature = "storage-aws")]
pub use builder::S3ObjectStorageBuilder;
#[derive(Debug)]
pub struct ObjectStorage<S> {
name: String,
store: S,
}
impl ObjectStorage<InMemory> {
#[must_use]
pub fn memory(name: impl Into<String>) -> MemoryObjectStorageBuilder {
MemoryObjectStorageBuilder::new(name)
}
}
#[cfg(all(feature = "storage", not(target_arch = "wasm32")))]
impl ObjectStorage<LocalFileSystem> {
#[must_use]
pub fn local(
name: impl Into<String>,
root: impl AsRef<std::path::Path>,
) -> LocalObjectStorageBuilder {
LocalObjectStorageBuilder::new(name, root)
}
}
#[cfg(feature = "storage-aws")]
impl ObjectStorage<AmazonS3> {
#[must_use]
pub fn s3(name: impl Into<String>) -> S3ObjectStorageBuilder {
S3ObjectStorageBuilder::new(name)
}
}
#[cfg(feature = "storage-gcp")]
impl ObjectStorage<GoogleCloudStorage> {
#[must_use]
pub fn gcs(name: impl Into<String>) -> GcpObjectStorageBuilder {
GcpObjectStorageBuilder::new(name)
}
}
#[cfg(feature = "storage-azure")]
impl ObjectStorage<MicrosoftAzure> {
#[must_use]
pub fn azure(name: impl Into<String>) -> AzureObjectStorageBuilder {
AzureObjectStorageBuilder::new(name)
}
}
#[cfg(feature = "storage-http")]
impl ObjectStorage<HttpStore> {
#[must_use]
pub fn http(name: impl Into<String>, url: impl Into<String>) -> HttpObjectStorageBuilder {
HttpObjectStorageBuilder::new(name, url)
}
}
impl<S> ObjectStorage<S> {
pub(crate) fn from_store(name: impl Into<String>, store: S) -> Self {
Self {
name: name.into(),
store,
}
}
#[must_use]
pub const fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub const fn store(&self) -> &S {
&self.store
}
}
#[cfg(test)]
mod tests {
use std::{env, fs, io::ErrorKind, process};
use object_store::{ObjectStoreExt as _, PutPayload, path::Path};
use super::ObjectStorage;
#[test]
fn memory_builder_builds_usable_object_storage() {
let runtime = runtime();
let storage = ObjectStorage::memory("objects").build();
let path = Path::from("documents/report.txt");
assert_eq!(storage.name(), "objects", "storage name must be preserved");
runtime.block_on(async {
match storage
.store()
.put(&path, PutPayload::from_static(b"hello storage"))
.await
{
Ok(_) => {}
Err(error) => panic!("memory object must be written: {error}"),
}
let bytes = match storage.store().get(&path).await {
Ok(result) => match result.bytes().await {
Ok(bytes) => bytes,
Err(error) => panic!("memory object bytes must be read: {error}"),
},
Err(error) => panic!("memory object must be read: {error}"),
};
assert_eq!(
bytes.as_ref(),
b"hello storage",
"read bytes must match written bytes"
);
});
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn local_builder_builds_usable_object_storage() {
let runtime = runtime();
let root = test_directory("local_builder_builds_usable_object_storage");
create_test_directory(&root);
let storage = match ObjectStorage::local("objects", &root).build() {
Ok(storage) => storage,
Err(error) => panic!("local object storage must build: {error}"),
};
let path = Path::from("documents/report.txt");
runtime.block_on(async {
match storage
.store()
.put(&path, PutPayload::from_static(b"hello local storage"))
.await
{
Ok(_) => {}
Err(error) => panic!("local object must be written: {error}"),
}
let bytes = match storage.store().get(&path).await {
Ok(result) => match result.bytes().await {
Ok(bytes) => bytes,
Err(error) => panic!("local object bytes must be read: {error}"),
},
Err(error) => panic!("local object must be read: {error}"),
};
assert_eq!(
bytes.as_ref(),
b"hello local storage",
"read bytes must match written local bytes"
);
});
remove_test_directory(&root);
}
fn runtime() -> tokio::runtime::Runtime {
match tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
{
Ok(runtime) => runtime,
Err(error) => panic!("test runtime must build: {error}"),
}
}
#[cfg(not(target_arch = "wasm32"))]
fn test_directory(name: &str) -> std::path::PathBuf {
let directory = env::temp_dir().join(format!("recomp-storage-{name}-{}", process::id()));
remove_test_directory(&directory);
directory
}
#[cfg(not(target_arch = "wasm32"))]
fn create_test_directory(directory: &std::path::Path) {
match fs::create_dir_all(directory) {
Ok(()) => {}
Err(error) => panic!("test storage directory must be created: {error}"),
}
}
#[cfg(not(target_arch = "wasm32"))]
fn remove_test_directory(directory: &std::path::Path) {
match fs::remove_dir_all(directory) {
Ok(()) => {}
Err(error) if error.kind() == ErrorKind::NotFound => {}
Err(error) => panic!("test storage directory cleanup failed: {error}"),
}
}
}