1use bytes::Bytes;
2use futures_core::Stream;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::pin::Pin;
6use uuid::Uuid;
7
8pub type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>>;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct BlobId(pub String);
14
15impl BlobId {
16 pub fn new() -> Self {
18 Self(Uuid::new_v4().to_string())
19 }
20
21 pub fn from_string(id: String) -> Self {
23 Self(id)
24 }
25
26 pub fn as_str(&self) -> &str {
28 &self.0
29 }
30}
31
32impl Default for BlobId {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl std::fmt::Display for BlobId {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "{}", self.0)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
46pub struct UploadId(pub String);
47
48impl UploadId {
49 pub fn new() -> Self {
51 Self(format!("upl_{}", Uuid::new_v4().simple()))
52 }
53
54 pub fn from_string(id: String) -> Self {
56 Self(id)
57 }
58
59 pub fn as_str(&self) -> &str {
61 &self.0
62 }
63}
64
65impl Default for UploadId {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl std::fmt::Display for UploadId {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(f, "{}", self.0)
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct BlobCtx {
80 pub tenant_id: String,
81 pub actor_id: Option<String>,
82 pub request_id: String,
83}
84
85impl BlobCtx {
86 pub fn new(tenant_id: String) -> Self {
87 Self {
88 tenant_id,
89 actor_id: None,
90 request_id: Uuid::new_v4().to_string(),
91 }
92 }
93
94 pub fn with_actor(mut self, actor_id: String) -> Self {
95 self.actor_id = Some(actor_id);
96 self
97 }
98
99 pub fn with_request_id(mut self, request_id: String) -> Self {
100 self.request_id = request_id;
101 self
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct BlobPut {
108 pub content_type: Option<String>,
109 pub filename: Option<String>,
110 pub size_hint: Option<u64>,
111 pub attributes: serde_json::Value,
112 pub key_hints: BTreeMap<String, String>,
113 pub idempotency_key: Option<String>,
114}
115
116impl Default for BlobPut {
117 fn default() -> Self {
118 Self {
119 content_type: None,
120 filename: None,
121 size_hint: None,
122 attributes: serde_json::Value::Null,
123 key_hints: BTreeMap::new(),
124 idempotency_key: None,
125 }
126 }
127}
128
129impl BlobPut {
130 pub fn new() -> Self {
131 Self::default()
132 }
133
134 pub fn with_content_type<S: Into<String>>(mut self, content_type: S) -> Self {
135 self.content_type = Some(content_type.into());
136 self
137 }
138
139 pub fn with_filename<S: Into<String>>(mut self, filename: S) -> Self {
140 self.filename = Some(filename.into());
141 self
142 }
143
144 pub fn with_size_hint(mut self, size: u64) -> Self {
145 self.size_hint = Some(size);
146 self
147 }
148
149 pub fn with_attribute<K: Into<String>, V: serde::Serialize>(mut self, key: K, value: V) -> Self {
150 if self.attributes.is_null() {
151 self.attributes = serde_json::Value::Object(serde_json::Map::new());
152 }
153 if let Some(obj) = self.attributes.as_object_mut() {
154 obj.insert(key.into(), serde_json::to_value(value).unwrap_or(serde_json::Value::Null));
155 }
156 self
157 }
158
159 pub fn with_key_hint<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
160 self.key_hints.insert(key.into(), value.into());
161 self
162 }
163
164 pub fn with_idempotency_key<S: Into<String>>(mut self, key: S) -> Self {
165 self.idempotency_key = Some(key.into());
166 self
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct ByteRange {
173 pub start: u64,
174 pub end: Option<u64>, }
176
177impl ByteRange {
178 pub fn new(start: u64, end: Option<u64>) -> Self {
179 Self { start, end }
180 }
181
182 pub fn from_start(start: u64) -> Self {
183 Self { start, end: None }
184 }
185
186 pub fn length(&self, total_size: u64) -> u64 {
187 match self.end {
188 Some(end) => end.saturating_sub(self.start) + 1,
189 None => total_size.saturating_sub(self.start),
190 }
191 }
192
193 pub fn is_valid(&self, total_size: u64) -> bool {
194 if self.start >= total_size {
195 return false;
196 }
197 if let Some(end) = self.end {
198 end >= self.start && end < total_size
199 } else {
200 true
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
207pub enum UploadStatus {
208 Active,
209 Completed { completed_at: i64 },
210 Aborted { aborted_at: i64 },
211 Failed { failed_at: i64, reason: String },
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct UploadSession {
217 pub upload_id: UploadId,
218 pub blob_id: BlobId,
219 pub tenant_id: String,
220 pub actor_id: Option<String>,
221
222 pub created_at: i64,
223 pub updated_at: i64,
224
225 pub total_parts: Option<u32>,
226 pub status: UploadStatus,
227
228 pub content_type: String,
229 pub filename: Option<String>,
230 pub size_hint: Option<u64>,
231 pub attributes: serde_json::Value,
232
233 pub progress: UploadProgress,
234}
235
236#[derive(Debug, Clone, Default, Serialize, Deserialize)]
238pub struct UploadProgress {
239 pub parts: BTreeMap<u32, PartReceipt>,
240 pub received_bytes: u64,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct PartReceipt {
246 pub part_number: u32,
247 pub size_bytes: u64,
248 pub etag: Option<String>,
249 pub checksum: Option<String>,
250 pub uploaded_at: i64,
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
255pub struct ChunkSessionId(pub String);
256
257impl ChunkSessionId {
258 pub fn from_string(id: String) -> Self {
260 Self(id)
261 }
262
263 pub fn new() -> Self {
265 Self(format!("chunk_{}", Uuid::new_v4().simple()))
266 }
267
268 pub fn as_str(&self) -> &str {
270 &self.0
271 }
272}
273
274impl std::fmt::Display for ChunkSessionId {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 write!(f, "{}", self.0)
277 }
278}
279
280#[derive(Debug, Clone)]
282pub enum ChunkResult {
283 Partial {
285 chunks_received: u32,
286 total_chunks: u32,
287 },
288 Complete {
290 receipt: crate::BlobReceipt,
291 },
292}
293
294#[derive(Debug, Clone)]
296pub struct ChunkSession {
297 pub session_id: ChunkSessionId,
298 pub blob_id: BlobId,
299 pub tenant_id: String,
300 pub total_chunks: u32,
301 pub received_chunks: std::collections::BTreeSet<u32>,
302 pub content_type: Option<String>,
303 pub filename: Option<String>,
304 pub temp_dir: String,
305 pub created_at: i64,
306}