use aws_sdk_s3::Client;
use aws_smithy_types::error::metadata::ProvideErrorMetadata;
use crate::error::{BlobStorageError, Result};
pub(crate) mod multipart;
pub(crate) mod single;
pub(crate) mod store;
pub const DEFAULT_MULTIPART_PART_SIZE: u64 = 50 * 1024 * 1024;
pub const MIN_MULTIPART_PART_SIZE: u64 = 5 * 1024 * 1024;
pub struct S3BlobStore {
pub(crate) client: Client,
pub(crate) bucket: String,
pub(crate) part_size: u64,
}
impl S3BlobStore {
pub fn new(client: Client, bucket: impl Into<String>) -> Self {
Self {
client,
bucket: bucket.into(),
part_size: DEFAULT_MULTIPART_PART_SIZE,
}
}
pub fn with_multipart_part_size(mut self, size: u64) -> Self {
self.part_size = size.max(MIN_MULTIPART_PART_SIZE);
self
}
pub fn multipart_part_size(&self) -> u64 {
self.part_size
}
pub(crate) async fn get_object_output(
&self,
key: &str,
) -> Result<aws_sdk_s3::operation::get_object::GetObjectOutput> {
self.client
.get_object()
.bucket(&self.bucket)
.key(key)
.send()
.await
.map_err(|e| {
if self.is_misconfigured(&e) {
BlobStorageError::BackendMisconfigured(format!(
"S3 bucket '{}' does not exist or is not accessible",
self.bucket
))
} else if self.is_not_found(&e) {
BlobStorageError::NotFound(key.to_string())
} else {
BlobStorageError::Storage {
message: format!("S3 get failed for key '{key}'"),
source: Some(Box::new(e)),
}
}
})
}
pub(crate) fn is_not_found<E>(&self, err: &aws_sdk_s3::error::SdkError<E>) -> bool
where
E: std::error::Error + ProvideErrorMetadata + 'static,
{
err.as_service_error()
.and_then(|e| e.code())
.is_some_and(|code| matches!(code, "NoSuchKey" | "NotFound"))
}
pub(crate) fn is_misconfigured<E>(&self, err: &aws_sdk_s3::error::SdkError<E>) -> bool
where
E: std::error::Error + ProvideErrorMetadata + 'static,
{
err.as_service_error()
.and_then(|e| e.code())
.is_some_and(|code| code == "NoSuchBucket")
}
}