gitdataai/lib/storage/lib.rs

203 lines
5.3 KiB
Rust

pub mod error;
pub mod local;
pub mod s3;
use std::time::Duration;
use async_trait::async_trait;
pub use aws_sdk_s3::primitives::ByteStream;
use aws_sdk_s3::primitives::ByteStreamError;
pub use error::{StorageError, StorageResult};
pub use local::{LocalStorage, LocalStorageConfig};
pub use s3::{S3Storage, S3StorageConfig};
#[derive(Clone, Debug)]
pub enum AppStorageConfig {
Local(LocalStorageConfig),
S3(S3StorageConfig),
}
#[derive(Clone)]
pub enum AppStorage {
Local(LocalStorage),
S3(S3Storage),
}
#[derive(Clone, Debug, Default)]
pub struct PutObjectOptions {
pub content_type: Option<String>,
pub content_length: Option<i64>,
pub cache_control: Option<String>,
}
#[derive(Clone, Debug)]
pub struct StoredObject {
pub key: String,
pub url: String,
pub e_tag: Option<String>,
pub version_id: Option<String>,
}
#[derive(Debug)]
pub struct StorageObjectStream {
pub body: ByteStream,
pub content_length: Option<i64>,
pub content_type: Option<String>,
pub e_tag: Option<String>,
}
#[derive(Clone, Debug)]
pub struct StorageObject {
pub bytes: Vec<u8>,
pub content_length: Option<i64>,
pub content_type: Option<String>,
pub e_tag: Option<String>,
}
#[async_trait]
pub trait ObjectStorage: Send + Sync {
async fn put_stream(
&self,
key: &str,
body: ByteStream,
options: PutObjectOptions,
) -> StorageResult<StoredObject>;
async fn put_bytes(
&self,
key: &str,
bytes: Vec<u8>,
options: PutObjectOptions,
) -> StorageResult<StoredObject>;
async fn get_stream(&self, key: &str)
-> StorageResult<StorageObjectStream>;
async fn get_bytes(&self, key: &str) -> StorageResult<StorageObject>;
async fn delete(&self, key: &str) -> StorageResult<()>;
fn public_url(&self, key: &str) -> StorageResult<Option<String>>;
async fn presigned_get_url(
&self,
key: &str,
expires_in: Duration,
) -> StorageResult<String>;
}
impl AppStorage {
pub async fn init(config: AppStorageConfig) -> StorageResult<Self> {
match config {
AppStorageConfig::Local(config) => {
Ok(Self::Local(LocalStorage::connect(config).await?))
}
AppStorageConfig::S3(config) => {
Ok(Self::S3(S3Storage::connect(config).await?))
}
}
}
}
#[async_trait]
impl ObjectStorage for AppStorage {
async fn put_stream(
&self,
key: &str,
body: ByteStream,
options: PutObjectOptions,
) -> StorageResult<StoredObject> {
match self {
Self::Local(storage) => {
storage.put_stream(key, body, options).await
}
Self::S3(storage) => storage.put_stream(key, body, options).await,
}
}
async fn put_bytes(
&self,
key: &str,
bytes: Vec<u8>,
options: PutObjectOptions,
) -> StorageResult<StoredObject> {
match self {
Self::Local(storage) => {
storage.put_bytes(key, bytes, options).await
}
Self::S3(storage) => storage.put_bytes(key, bytes, options).await,
}
}
async fn get_stream(
&self,
key: &str,
) -> StorageResult<StorageObjectStream> {
match self {
Self::Local(storage) => storage.get_stream(key).await,
Self::S3(storage) => storage.get_stream(key).await,
}
}
async fn get_bytes(&self, key: &str) -> StorageResult<StorageObject> {
match self {
Self::Local(storage) => storage.get_bytes(key).await,
Self::S3(storage) => storage.get_bytes(key).await,
}
}
async fn delete(&self, key: &str) -> StorageResult<()> {
match self {
Self::Local(storage) => storage.delete(key).await,
Self::S3(storage) => storage.delete(key).await,
}
}
fn public_url(&self, key: &str) -> StorageResult<Option<String>> {
match self {
Self::Local(storage) => storage.public_url(key),
Self::S3(storage) => storage.public_url(key),
}
}
async fn presigned_get_url(
&self,
key: &str,
expires_in: Duration,
) -> StorageResult<String> {
match self {
Self::Local(storage) => {
storage.presigned_get_url(key, expires_in).await
}
Self::S3(storage) => {
storage.presigned_get_url(key, expires_in).await
}
}
}
}
pub async fn collect_byte_stream(
body: ByteStream,
) -> Result<Vec<u8>, ByteStreamError> {
body.collect().await.map(|data| data.to_vec())
}
impl TryFrom<&config::AppConfig> for AppStorageConfig {
type Error = StorageError;
fn try_from(config: &config::AppConfig) -> Result<Self, Self::Error> {
let backend = config
.storage_backend()
.map_err(|error| StorageError::Config(error.to_string()))?;
match backend.as_str() {
"local" | "fs" | "filesystem" => {
Ok(Self::Local(LocalStorageConfig::try_from(config)?))
}
"s3" => Ok(Self::S3(S3StorageConfig::try_from(config)?)),
backend => Err(StorageError::Config(format!(
"unsupported storage backend: {backend}"
))),
}
}
}