Skip to main content

dog_blob/
types.rs

1use bytes::Bytes;
2use futures_core::Stream;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::pin::Pin;
6use uuid::Uuid;
7
8/// Stream of bytes for blob content
9pub type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>>;
10
11/// Unique identifier for a blob
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct BlobId(pub String);
14
15impl BlobId {
16    /// Generate a new random blob ID
17    pub fn new() -> Self {
18        Self(Uuid::new_v4().to_string())
19    }
20
21    /// Create from existing string
22    pub fn from_string(id: String) -> Self {
23        Self(id)
24    }
25
26    /// Get the inner string
27    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/// Unique identifier for an upload session
45#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
46pub struct UploadId(pub String);
47
48impl UploadId {
49    /// Generate a new random upload ID
50    pub fn new() -> Self {
51        Self(format!("upl_{}", Uuid::new_v4().simple()))
52    }
53
54    /// Create from existing string
55    pub fn from_string(id: String) -> Self {
56        Self(id)
57    }
58
59    /// Get the inner string
60    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/// Context for blob operations (tenant, user, request info)
78#[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/// Request to store a blob
106#[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/// Byte range for partial content requests
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct ByteRange {
173    pub start: u64,
174    pub end: Option<u64>, // None means "to end of file"
175}
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/// Status of an upload session
206#[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/// Upload session state
215#[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/// Progress tracking for upload sessions
237#[derive(Debug, Clone, Default, Serialize, Deserialize)]
238pub struct UploadProgress {
239    pub parts: BTreeMap<u32, PartReceipt>,
240    pub received_bytes: u64,
241}
242
243/// Receipt for an uploaded part
244#[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/// Unique identifier for a chunked upload session
254#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
255pub struct ChunkSessionId(pub String);
256
257impl ChunkSessionId {
258    /// Create from existing string (e.g., Dropzone UUID)
259    pub fn from_string(id: String) -> Self {
260        Self(id)
261    }
262
263    /// Generate a new random chunk session ID
264    pub fn new() -> Self {
265        Self(format!("chunk_{}", Uuid::new_v4().simple()))
266    }
267
268    /// Get the inner string
269    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/// Result of uploading a chunk
281#[derive(Debug, Clone)]
282pub enum ChunkResult {
283    /// Chunk received, waiting for more chunks
284    Partial {
285        chunks_received: u32,
286        total_chunks: u32,
287    },
288    /// All chunks received, file assembled and uploaded
289    Complete {
290        receipt: crate::BlobReceipt,
291    },
292}
293
294/// State tracking for a chunked upload session
295#[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}