use std::time::Duration;
use fraiseql_error::{FileError, FraiseQLError, Result};
use super::backend::validate_key;
use crate::{
backend::{StorageBackend, types::ListResult},
config::BucketConfig,
};
pub struct BucketService {
backend: StorageBackend,
config: BucketConfig,
}
impl BucketService {
#[must_use]
pub const fn new(backend: StorageBackend, config: BucketConfig) -> Self {
Self { backend, config }
}
#[must_use]
pub const fn config(&self) -> &BucketConfig {
&self.config
}
pub async fn upload(&self, key: &str, data: &[u8], content_type: &str) -> Result<String> {
validate_key(key)?;
if let Some(max_bytes) = self.config.max_object_bytes {
#[allow(clippy::cast_possible_truncation)]
let actual = data.len() as u64;
if actual > max_bytes {
return Err(FraiseQLError::File(FileError::SizeLimitExceeded {
message: format!("Upload exceeds maximum object size of {max_bytes} bytes"),
limit: Some(max_bytes),
actual: Some(actual),
}));
}
}
if let Some(ref allowed) = self.config.allowed_mime_types {
let is_allowed = allowed.iter().any(|m| m == content_type || m == "*/*");
if !is_allowed {
return Err(FraiseQLError::File(FileError::MimeTypeNotAllowed {
message: format!(
"Content type '{content_type}' is not allowed for this bucket"
),
mime: Some(content_type.to_string()),
}));
}
}
self.backend.upload(key, data, content_type).await
}
pub async fn download(&self, key: &str) -> Result<Vec<u8>> {
self.backend.download(key).await
}
pub async fn delete(&self, key: &str) -> Result<()> {
self.backend.delete(key).await
}
pub async fn exists(&self, key: &str) -> Result<bool> {
self.backend.exists(key).await
}
pub async fn list(
&self,
prefix: &str,
cursor: Option<&str>,
limit: usize,
) -> Result<ListResult> {
self.backend.list(prefix, cursor, limit).await
}
pub async fn presigned_url(&self, key: &str, expiry: Duration) -> Result<String> {
self.backend.presigned_url(key, expiry).await
}
}
#[cfg(test)]
mod tests;