1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
10#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct BlobId(String);
13
14impl BlobId {
15 pub fn new(value: impl Into<String>) -> Self {
16 Self(value.into())
17 }
18
19 pub fn as_str(&self) -> &str {
20 &self.0
21 }
22}
23
24impl fmt::Display for BlobId {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.write_str(self.as_str())
27 }
28}
29
30impl From<String> for BlobId {
31 fn from(value: String) -> Self {
32 Self(value)
33 }
34}
35
36impl From<&str> for BlobId {
37 fn from(value: &str) -> Self {
38 Self(value.to_string())
39 }
40}
41
42#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
44#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
45pub struct BlobRef {
46 pub blob_id: BlobId,
47 pub media_type: String,
48}
49
50#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct BlobPayload {
54 pub blob_id: BlobId,
55 pub media_type: String,
56 pub data: String,
58}
59
60#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
61pub enum BlobStoreError {
62 #[error("blob not found: {0}")]
63 NotFound(BlobId),
64 #[error("blob store read failed: {0}")]
65 ReadFailed(String),
66 #[error("blob store write failed: {0}")]
67 WriteFailed(String),
68 #[error("blob store delete failed: {0}")]
69 DeleteFailed(String),
70 #[error("blob store unsupported: {0}")]
71 Unsupported(String),
72 #[error("blob store internal error: {0}")]
73 Internal(String),
74}
75
76#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
77#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
78pub trait BlobStore: Send + Sync {
79 async fn put_image(&self, media_type: &str, data: &str) -> Result<BlobRef, BlobStoreError>;
80
81 async fn get(&self, blob_id: &BlobId) -> Result<BlobPayload, BlobStoreError>;
82
83 async fn delete(&self, blob_id: &BlobId) -> Result<(), BlobStoreError>;
84
85 async fn exists(&self, blob_id: &BlobId) -> Result<bool, BlobStoreError> {
86 match self.get(blob_id).await {
87 Ok(_) => Ok(true),
88 Err(BlobStoreError::NotFound(_)) => Ok(false),
89 Err(err) => Err(err),
90 }
91 }
92
93 fn is_persistent(&self) -> bool;
95}