1use crate::core::Instant;
2use crate::digest::{DigestSha2, DigestSha3};
3#[cfg(feature = "serde")]
4use crate::serde_buffer::{buffer_to_hex, hex_to_buffer};
5use async_trait::async_trait;
6use auto_impl::auto_impl;
7use etwin_core::types::AnyError;
8use etwin_core::{declare_new_string, declare_new_uuid};
9#[cfg(feature = "serde")]
10use etwin_serde_tools::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet};
12use std::ops::Range;
13
14declare_new_uuid! {
15 pub struct BlobId(Uuid);
16 pub type ParseError = BlobIdParseError;
17 const SQL_NAME = "blob_id";
18}
19
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(feature = "serde", serde(tag = "type", rename = "Blob"))]
22#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct BlobIdRef {
24 pub id: BlobId,
25}
26
27impl BlobIdRef {
28 pub const fn new(id: BlobId) -> Self {
29 Self { id }
30 }
31}
32
33impl From<BlobId> for BlobIdRef {
34 fn from(id: BlobId) -> Self {
35 Self::new(id)
36 }
37}
38
39declare_new_uuid! {
40 pub struct UploadSessionId(Uuid);
41 pub type ParseError = UploadSessionIdParseError;
42 const SQL_NAME = "upload_session_id";
43}
44
45declare_new_string! {
46 pub struct MediaType(String);
47 pub type ParseError = MediaTypeParseError;
48 const PATTERN = r"^[0-9a-z.-]{1,100}/[0-9a-z.-]{1,100}$";
49 const SQL_NAME = "media_type";
50}
51
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
53#[cfg_attr(feature = "serde", serde(tag = "type", rename = "Blob"))]
54#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub struct Blob {
56 pub id: BlobId,
57 pub media_type: MediaType,
58 pub byte_size: u32,
59 pub digest: BlobDigest,
60}
61
62impl Blob {
63 pub const fn as_ref(&self) -> BlobIdRef {
64 BlobIdRef::new(self.id)
65 }
66}
67
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub struct BlobDigest {
71 pub sha2_256: DigestSha2,
72 pub sha3_256: DigestSha3,
73}
74
75impl BlobDigest {
76 pub fn digest(data: &[u8]) -> Self {
77 Self {
78 sha2_256: DigestSha2::digest(data),
79 sha3_256: DigestSha3::digest(data),
80 }
81 }
82}
83
84#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
85#[derive(Clone, Debug, PartialEq, Eq, Hash)]
86pub struct UploadSession {
87 pub id: UploadSessionId,
88 pub expires_at: Instant,
89 pub remaining_range: Range<u32>,
90 pub blob: Option<Blob>,
91}
92
93#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
94#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct CreateBlobOptions {
96 pub media_type: MediaType,
97 #[cfg_attr(
98 feature = "serde",
99 serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")
100 )]
101 pub data: Vec<u8>,
102}
103
104#[derive(Debug, thiserror::Error)]
105pub enum CreateBlobError {
106 #[error("Blob size exceeds maximum allowed size")]
107 MaxSize,
108 #[error(transparent)]
109 Other(#[from] AnyError),
110}
111
112impl CreateBlobError {
113 pub fn other<E>(e: E) -> Self
114 where
115 E: ::std::error::Error + Send + Sync + 'static,
116 {
117 CreateBlobError::Other(Box::new(e))
118 }
119}
120
121#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
122#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
123pub struct GetBlobOptions {
124 pub id: BlobId,
125}
126
127#[derive(Debug, thiserror::Error)]
128pub enum GetBlobError {
129 #[error("Failed to find blob: {:?}", .0)]
130 NotFound(BlobId),
131 #[error(transparent)]
132 Other(#[from] AnyError),
133}
134
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[derive(Clone, Debug, PartialEq, Eq)]
137pub struct GetBlobsOptions {
138 pub id: HashSet<BlobId>,
139 pub now: Instant,
140 pub time: Option<Instant>,
141}
142
143#[derive(Debug, thiserror::Error)]
144pub enum GetBlobsError {
145 #[error(transparent)]
146 Other(#[from] AnyError),
147}
148
149#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
150#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
151pub struct GetBlobDataOptions {
152 pub id: BlobId,
153}
154
155#[derive(Debug, thiserror::Error)]
156pub enum GetBlobDataError {
157 #[error("Failed to find blob: {:?}", .0)]
158 NotFound(BlobId),
159 #[error(transparent)]
160 Other(#[from] AnyError),
161}
162
163#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
164#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
165pub struct CreateUploadSessionOptions {
166 pub media_type: MediaType,
167 pub byte_size: u32,
168}
169
170#[derive(Debug, thiserror::Error)]
171pub enum CreateUploadSessionError {
172 #[error("Upload session blob size exceeds maximum")]
173 MaxSize,
174 #[error(transparent)]
175 Other(#[from] AnyError),
176}
177
178impl CreateUploadSessionError {
179 pub fn other<E>(e: E) -> Self
180 where
181 E: ::std::error::Error + Send + Sync + 'static,
182 {
183 Self::Other(Box::new(e))
184 }
185}
186
187#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
188#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
189pub struct UploadOptions {
190 pub upload_session_id: UploadSessionId,
191 pub offset: u32,
192 #[cfg_attr(
193 feature = "serde",
194 serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")
195 )]
196 pub data: Vec<u8>,
197}
198
199#[derive(Debug, thiserror::Error)]
200pub enum UploadError {
201 #[error("Failed to find upload session for id: {:?}", .0)]
202 NotFound(UploadSessionId),
203 #[error("Upload session expired: {:?}", .0)]
204 Expired(UploadSessionId),
205 #[error("Upload session expected data from offset {} but received from offset {}", .expected, .actual)]
206 BadOffset { actual: u32, expected: u32 },
207 #[error("Upload session tried to write past the reserved size")]
208 Overflow,
209 #[error("Upload session tried to upload an empty chunk")]
210 EmptyInputData,
211 #[error(transparent)]
212 Other(#[from] AnyError),
213}
214
215impl UploadError {
216 pub fn other<E>(e: E) -> Self
217 where
218 E: ::std::error::Error + Send + Sync + 'static,
219 {
220 Self::Other(Box::new(e))
221 }
222}
223
224#[async_trait]
225#[auto_impl(&, Arc)]
226pub trait BlobStore: Send + Sync {
227 fn has_immutable_blobs(&self) -> bool;
228
229 async fn create_blob(&self, options: &CreateBlobOptions) -> Result<Blob, CreateBlobError>;
230
231 async fn get_blob(&self, options: &GetBlobOptions) -> Result<Blob, GetBlobError>;
232
233 async fn get_blobs(
234 &self,
235 options: &GetBlobsOptions,
236 ) -> Result<HashMap<BlobId, Result<Blob, GetBlobError>>, GetBlobsError>;
237
238 async fn get_blob_data(&self, options: &GetBlobDataOptions) -> Result<Vec<u8>, GetBlobDataError>;
239
240 async fn create_upload_session(
241 &self,
242 options: &CreateUploadSessionOptions,
243 ) -> Result<UploadSession, CreateUploadSessionError>;
244
245 async fn upload(&self, options: &UploadOptions) -> Result<UploadSession, UploadError>;
246}