Skip to main content

dog_blob/
upload.rs

1use async_trait::async_trait;
2use crate::{
3    BlobCtx, BlobId, BlobResult, PartReceipt, UploadId, UploadSession, ByteStream
4};
5
6/// Coordinates multipart and resumable uploads
7#[async_trait]
8pub trait UploadCoordinator: Send + Sync {
9    /// Begin a new upload session
10    async fn begin(
11        &self,
12        ctx: BlobCtx,
13        intent: UploadIntent,
14    ) -> BlobResult<UploadSession>;
15
16    /// Accept a part upload
17    async fn accept_part(
18        &self,
19        ctx: BlobCtx,
20        upload_id: &UploadId,
21        part_number: u32,
22        body: ByteStream,
23    ) -> BlobResult<PartReceipt>;
24
25    /// Set total parts (optional, can be done later)
26    async fn set_total_parts(
27        &self,
28        ctx: BlobCtx,
29        upload_id: &UploadId,
30        total_parts: u32,
31    ) -> BlobResult<UploadSession>;
32
33    /// Complete the upload and return final blob receipt
34    async fn complete(
35        &self,
36        ctx: BlobCtx,
37        upload_id: &UploadId,
38    ) -> BlobResult<crate::BlobReceipt>;
39
40    /// Abort the upload and cleanup
41    async fn abort(
42        &self,
43        ctx: BlobCtx,
44        upload_id: &UploadId,
45    ) -> BlobResult<()>;
46
47    /// Get upload session status
48    async fn get_session(
49        &self,
50        ctx: BlobCtx,
51        upload_id: &UploadId,
52    ) -> BlobResult<UploadSession>;
53}
54
55/// Intent to upload a blob
56#[derive(Debug, Clone)]
57pub struct UploadIntent {
58    pub id: BlobId,
59    pub key: String,
60    pub content_type: String,
61    pub filename: Option<String>,
62    pub size_hint: Option<u64>,
63    pub attributes: serde_json::Value,
64    pub chunking: Chunking,
65    pub idempotency_key: Option<String>,
66}
67
68/// How the upload should be chunked
69#[derive(Debug, Clone)]
70pub enum Chunking {
71    /// Upload in parts
72    Parts {
73        part_size: u64,
74        total_parts: Option<u32>,
75    },
76    /// Single upload (no parts)
77    Single,
78}
79
80/// Storage for upload session state
81#[async_trait]
82pub trait UploadSessionStore: Send + Sync {
83    /// Create a new upload session
84    async fn create(&self, session: UploadSession) -> BlobResult<UploadSession>;
85
86    /// Get an upload session
87    async fn get(&self, upload_id: &UploadId) -> BlobResult<UploadSession>;
88
89    /// Update an upload session
90    async fn update(&self, session: UploadSession) -> BlobResult<UploadSession>;
91
92    /// Delete an upload session
93    async fn delete(&self, upload_id: &UploadId) -> BlobResult<()>;
94
95    /// Record a part upload
96    async fn record_part(
97        &self,
98        upload_id: &UploadId,
99        part: PartReceipt,
100    ) -> BlobResult<()>;
101
102    /// Mark session as completed
103    async fn mark_completed(
104        &self,
105        upload_id: &UploadId,
106        completed_at: i64,
107    ) -> BlobResult<()>;
108
109    /// Mark session as failed
110    async fn mark_failed(
111        &self,
112        upload_id: &UploadId,
113        failed_at: i64,
114        reason: String,
115    ) -> BlobResult<()>;
116
117    /// Mark session as aborted
118    async fn mark_aborted(
119        &self,
120        upload_id: &UploadId,
121        aborted_at: i64,
122    ) -> BlobResult<()>;
123}
124
125impl UploadIntent {
126    pub fn new(id: BlobId, key: String) -> Self {
127        Self {
128            id,
129            key,
130            content_type: "application/octet-stream".to_string(),
131            filename: None,
132            size_hint: None,
133            attributes: serde_json::Value::Null,
134            chunking: Chunking::Single,
135            idempotency_key: None,
136        }
137    }
138
139    pub fn with_content_type<S: Into<String>>(mut self, content_type: S) -> Self {
140        self.content_type = content_type.into();
141        self
142    }
143
144    pub fn with_filename<S: Into<String>>(mut self, filename: S) -> Self {
145        self.filename = Some(filename.into());
146        self
147    }
148
149    pub fn with_size_hint(mut self, size: u64) -> Self {
150        self.size_hint = Some(size);
151        self
152    }
153
154    pub fn with_attributes(mut self, attributes: serde_json::Value) -> Self {
155        self.attributes = attributes;
156        self
157    }
158
159    pub fn with_parts(mut self, part_size: u64, total_parts: Option<u32>) -> Self {
160        self.chunking = Chunking::Parts { part_size, total_parts };
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}