use std::fmt::Debug;
use async_trait::async_trait;
use crate::core::{Digest, Timestamp};
#[cfg(feature = "opendal")]
mod opendal_store;
#[cfg(feature = "opendal")]
pub use opendal_store::OpenDalBlobs;
mod memory;
pub use memory::MemoryBlobs;
#[derive(Debug, thiserror::Error)]
pub enum BlobError {
#[error("blob storage: {0}")]
Backend(String),
#[error("no blob at {0}")]
NotFound(String),
#[error("blob at {expected} hashes to {actual} — the stored bytes were altered")]
Corrupt { expected: String, actual: String },
#[error("blob at {digest} was expired at {at}: {reason}")]
Expired {
digest: String,
at: i64,
reason: String,
},
}
#[async_trait]
pub trait BlobStore: Send + Sync + Debug {
fn tenant(&self) -> &str {
crate::core::TenantId::DEFAULT
}
async fn put(&self, bytes: &[u8]) -> Result<Digest, BlobError>;
async fn put_at(&self, digest: Digest, bytes: &[u8]) -> Result<(), BlobError>;
async fn get_raw(&self, digest: Digest) -> Result<Vec<u8>, BlobError>;
async fn get(&self, digest: Digest) -> Result<Vec<u8>, BlobError>;
async fn expire(&self, digest: Digest, at: Timestamp, reason: &str) -> Result<(), BlobError>;
async fn has(&self, digest: Digest) -> Result<bool, BlobError>;
}
pub async fn erase_case(
blobs: &dyn BlobStore,
cases: &dyn crate::case::CaseStore,
#[cfg(feature = "keyring")] keyring: Option<&dyn crate::keyring::KeyRing>,
#[cfg(feature = "keyring")] tenant: &crate::core::TenantId,
case: crate::core::CaseId,
at: crate::core::Timestamp,
reason: &str,
) -> Result<usize, BlobError> {
let digests = cases
.blobs_of(case)
.await
.map_err(|e| BlobError::Backend(e.to_string()))?;
let mut n = 0;
for digest in digests {
blobs.expire(digest, at, reason).await?;
n += 1;
}
#[cfg(feature = "keyring")]
if let Some(keys) = keyring {
keys.destroy(
&crate::keyring::scope(tenant, &case.to_string()),
at,
reason,
)
.await
.map_err(|e| BlobError::Backend(e.to_string()))?;
}
Ok(n)
}
pub(crate) fn verify(digest: Digest, bytes: Vec<u8>) -> Result<Vec<u8>, BlobError> {
let actual = Digest::of(&bytes);
if actual == digest {
Ok(bytes)
} else {
Err(BlobError::Corrupt {
expected: digest.to_hex(),
actual: actual.to_hex(),
})
}
}