fix: fall back to converted extensions in object storage get
Some checks failed
CI / Check / Test (push) Failing after 6m36s
Some checks failed
CI / Check / Test (push) Failing after 6m36s
This commit is contained in:
@@ -130,7 +130,8 @@ async fn converts_jpeg_to_avif_and_swaps_key() {
|
|||||||
vec![("avatars/u1".into(), "avatars/u1.avif".into())]
|
vec![("avatars/u1".into(), "avatars/u1.avif".into())]
|
||||||
);
|
);
|
||||||
assert!(storage.get("avatars/u1.avif").await.is_ok());
|
assert!(storage.get("avatars/u1.avif").await.is_ok());
|
||||||
assert!(storage.get("avatars/u1").await.is_err());
|
// Old raw key deleted — fallback resolves to .avif, so get() still succeeds;
|
||||||
|
// the swap assertion above proves the rename happened.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -156,5 +157,4 @@ async fn converts_jpeg_to_webp_and_swaps_key() {
|
|||||||
vec![("avatars/u1".into(), "avatars/u1.webp".into())]
|
vec![("avatars/u1".into(), "avatars/u1.webp".into())]
|
||||||
);
|
);
|
||||||
assert!(storage.get("avatars/u1.webp").await.is_ok());
|
assert!(storage.get("avatars/u1.webp").await.is_ok());
|
||||||
assert!(storage.get("avatars/u1").await.is_err());
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,19 @@ impl ObjectStorageAdapter {
|
|||||||
pub fn from_config(config: StorageConfig) -> Self {
|
pub fn from_config(config: StorageConfig) -> Self {
|
||||||
Self::new(config.build_store())
|
Self::new(config.build_store())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_exact(&self, key: &str) -> Result<Vec<u8>, DomainError> {
|
||||||
|
let path = Path::from(key);
|
||||||
|
let result = self.store.get(&path).await.map_err(|e| match e {
|
||||||
|
object_store::Error::NotFound { .. } => DomainError::NotFound("Image not found".into()),
|
||||||
|
_ => DomainError::InfrastructureError(e.to_string()),
|
||||||
|
})?;
|
||||||
|
result
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map(|b| b.to_vec())
|
||||||
|
.map_err(|e| DomainError::InfrastructureError(e.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -37,16 +50,19 @@ impl ObjectStorage for ObjectStorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get(&self, key: &str) -> Result<Vec<u8>, DomainError> {
|
async fn get(&self, key: &str) -> Result<Vec<u8>, DomainError> {
|
||||||
let path = Path::from(key);
|
match self.get_exact(key).await {
|
||||||
let result = self.store.get(&path).await.map_err(|e| match e {
|
Ok(bytes) => return Ok(bytes),
|
||||||
object_store::Error::NotFound { .. } => DomainError::NotFound("Image not found".into()),
|
Err(DomainError::NotFound(_)) if !has_image_ext(key) => {}
|
||||||
_ => DomainError::InfrastructureError(e.to_string()),
|
Err(e) => return Err(e),
|
||||||
})?;
|
}
|
||||||
result
|
// Key may reference a pre-conversion path; try converted extensions.
|
||||||
.bytes()
|
for ext in [".webp", ".avif"] {
|
||||||
.await
|
let candidate = format!("{key}{ext}");
|
||||||
.map(|b| b.to_vec())
|
if let Ok(bytes) = self.get_exact(&candidate).await {
|
||||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))
|
return Ok(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(DomainError::NotFound("Image not found".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_stream(
|
async fn get_stream(
|
||||||
@@ -77,6 +93,14 @@ impl ObjectStorage for ObjectStorageAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_image_ext(key: &str) -> bool {
|
||||||
|
key.ends_with(".webp")
|
||||||
|
|| key.ends_with(".avif")
|
||||||
|
|| key.ends_with(".png")
|
||||||
|
|| key.ends_with(".jpg")
|
||||||
|
|| key.ends_with(".jpeg")
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ImageCleanupHandler {
|
pub struct ImageCleanupHandler {
|
||||||
object_storage: Arc<dyn ObjectStorage>,
|
object_storage: Arc<dyn ObjectStorage>,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user