1use async_trait::async_trait;
2use crate::{
3 BlobCtx, BlobId, BlobResult, PartReceipt, UploadId, UploadSession, ByteStream
4};
5
6#[async_trait]
8pub trait UploadCoordinator: Send + Sync {
9 async fn begin(
11 &self,
12 ctx: BlobCtx,
13 intent: UploadIntent,
14 ) -> BlobResult<UploadSession>;
15
16 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 async fn set_total_parts(
27 &self,
28 ctx: BlobCtx,
29 upload_id: &UploadId,
30 total_parts: u32,
31 ) -> BlobResult<UploadSession>;
32
33 async fn complete(
35 &self,
36 ctx: BlobCtx,
37 upload_id: &UploadId,
38 ) -> BlobResult<crate::BlobReceipt>;
39
40 async fn abort(
42 &self,
43 ctx: BlobCtx,
44 upload_id: &UploadId,
45 ) -> BlobResult<()>;
46
47 async fn get_session(
49 &self,
50 ctx: BlobCtx,
51 upload_id: &UploadId,
52 ) -> BlobResult<UploadSession>;
53}
54
55#[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#[derive(Debug, Clone)]
70pub enum Chunking {
71 Parts {
73 part_size: u64,
74 total_parts: Option<u32>,
75 },
76 Single,
78}
79
80#[async_trait]
82pub trait UploadSessionStore: Send + Sync {
83 async fn create(&self, session: UploadSession) -> BlobResult<UploadSession>;
85
86 async fn get(&self, upload_id: &UploadId) -> BlobResult<UploadSession>;
88
89 async fn update(&self, session: UploadSession) -> BlobResult<UploadSession>;
91
92 async fn delete(&self, upload_id: &UploadId) -> BlobResult<()>;
94
95 async fn record_part(
97 &self,
98 upload_id: &UploadId,
99 part: PartReceipt,
100 ) -> BlobResult<()>;
101
102 async fn mark_completed(
104 &self,
105 upload_id: &UploadId,
106 completed_at: i64,
107 ) -> BlobResult<()>;
108
109 async fn mark_failed(
111 &self,
112 upload_id: &UploadId,
113 failed_at: i64,
114 reason: String,
115 ) -> BlobResult<()>;
116
117 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}