use crate::core::Instant;
use crate::digest::{DigestSha2, DigestSha3};
#[cfg(feature = "serde")]
use crate::serde_buffer::{buffer_to_hex, hex_to_buffer};
use async_trait::async_trait;
use auto_impl::auto_impl;
use etwin_core::types::AnyError;
use etwin_core::{declare_new_string, declare_new_uuid};
#[cfg(feature = "serde")]
use etwin_serde_tools::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::ops::Range;
declare_new_uuid! {
pub struct BlobId(Uuid);
pub type ParseError = BlobIdParseError;
const SQL_NAME = "blob_id";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "Blob"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlobIdRef {
pub id: BlobId,
}
impl BlobIdRef {
pub const fn new(id: BlobId) -> Self {
Self { id }
}
}
impl From<BlobId> for BlobIdRef {
fn from(id: BlobId) -> Self {
Self::new(id)
}
}
declare_new_uuid! {
pub struct UploadSessionId(Uuid);
pub type ParseError = UploadSessionIdParseError;
const SQL_NAME = "upload_session_id";
}
declare_new_string! {
pub struct MediaType(String);
pub type ParseError = MediaTypeParseError;
const PATTERN = r"^[0-9a-z.-]{1,100}/[0-9a-z.-]{1,100}$";
const SQL_NAME = "media_type";
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "Blob"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Blob {
pub id: BlobId,
pub media_type: MediaType,
pub byte_size: u32,
pub digest: BlobDigest,
}
impl Blob {
pub const fn as_ref(&self) -> BlobIdRef {
BlobIdRef::new(self.id)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlobDigest {
pub sha2_256: DigestSha2,
pub sha3_256: DigestSha3,
}
impl BlobDigest {
pub fn digest(data: &[u8]) -> Self {
Self {
sha2_256: DigestSha2::digest(data),
sha3_256: DigestSha3::digest(data),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct UploadSession {
pub id: UploadSessionId,
pub expires_at: Instant,
pub remaining_range: Range<u32>,
pub blob: Option<Blob>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CreateBlobOptions {
pub media_type: MediaType,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")
)]
pub data: Vec<u8>,
}
#[derive(Debug, thiserror::Error)]
pub enum CreateBlobError {
#[error("Blob size exceeds maximum allowed size")]
MaxSize,
#[error(transparent)]
Other(#[from] AnyError),
}
impl CreateBlobError {
pub fn other<E>(e: E) -> Self
where
E: ::std::error::Error + Send + Sync + 'static,
{
CreateBlobError::Other(Box::new(e))
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetBlobOptions {
pub id: BlobId,
}
#[derive(Debug, thiserror::Error)]
pub enum GetBlobError {
#[error("Failed to find blob: {:?}", .0)]
NotFound(BlobId),
#[error(transparent)]
Other(#[from] AnyError),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GetBlobsOptions {
pub id: HashSet<BlobId>,
pub now: Instant,
pub time: Option<Instant>,
}
#[derive(Debug, thiserror::Error)]
pub enum GetBlobsError {
#[error(transparent)]
Other(#[from] AnyError),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetBlobDataOptions {
pub id: BlobId,
}
#[derive(Debug, thiserror::Error)]
pub enum GetBlobDataError {
#[error("Failed to find blob: {:?}", .0)]
NotFound(BlobId),
#[error(transparent)]
Other(#[from] AnyError),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CreateUploadSessionOptions {
pub media_type: MediaType,
pub byte_size: u32,
}
#[derive(Debug, thiserror::Error)]
pub enum CreateUploadSessionError {
#[error("Upload session blob size exceeds maximum")]
MaxSize,
#[error(transparent)]
Other(#[from] AnyError),
}
impl CreateUploadSessionError {
pub fn other<E>(e: E) -> Self
where
E: ::std::error::Error + Send + Sync + 'static,
{
Self::Other(Box::new(e))
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UploadOptions {
pub upload_session_id: UploadSessionId,
pub offset: u32,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")
)]
pub data: Vec<u8>,
}
#[derive(Debug, thiserror::Error)]
pub enum UploadError {
#[error("Failed to find upload session for id: {:?}", .0)]
NotFound(UploadSessionId),
#[error("Upload session expired: {:?}", .0)]
Expired(UploadSessionId),
#[error("Upload session expected data from offset {} but received from offset {}", .expected, .actual)]
BadOffset { actual: u32, expected: u32 },
#[error("Upload session tried to write past the reserved size")]
Overflow,
#[error("Upload session tried to upload an empty chunk")]
EmptyInputData,
#[error(transparent)]
Other(#[from] AnyError),
}
impl UploadError {
pub fn other<E>(e: E) -> Self
where
E: ::std::error::Error + Send + Sync + 'static,
{
Self::Other(Box::new(e))
}
}
#[async_trait]
#[auto_impl(&, Arc)]
pub trait BlobStore: Send + Sync {
fn has_immutable_blobs(&self) -> bool;
async fn create_blob(&self, options: &CreateBlobOptions) -> Result<Blob, CreateBlobError>;
async fn get_blob(&self, options: &GetBlobOptions) -> Result<Blob, GetBlobError>;
async fn get_blobs(
&self,
options: &GetBlobsOptions,
) -> Result<HashMap<BlobId, Result<Blob, GetBlobError>>, GetBlobsError>;
async fn get_blob_data(&self, options: &GetBlobDataOptions) -> Result<Vec<u8>, GetBlobDataError>;
async fn create_upload_session(
&self,
options: &CreateUploadSessionOptions,
) -> Result<UploadSession, CreateUploadSessionError>;
async fn upload(&self, options: &UploadOptions) -> Result<UploadSession, UploadError>;
}