opendal/services/vercel_blob/
core.rs1use bytes::Buf;
19use bytes::Bytes;
20use http::header;
21use http::request;
22use http::Request;
23use http::Response;
24use http::StatusCode;
25use serde::Deserialize;
26use serde::Serialize;
27use serde_json::json;
28use std::fmt::Debug;
29use std::fmt::Formatter;
30use std::sync::Arc;
31
32use self::constants::*;
33use super::error::parse_error;
34use crate::raw::*;
35use crate::*;
36
37pub(super) mod constants {
38 pub const X_VERCEL_BLOB_CONTENT_TYPE: &str = "x-content-type";
41 pub const X_VERCEL_BLOB_ADD_RANDOM_SUFFIX: &str = "x-add-random-suffix";
45 pub const X_VERCEL_BLOB_MPU_ACTION: &str = "x-mpu-action";
52 pub const X_VERCEL_BLOB_MPU_KEY: &str = "x-mpu-key";
53 pub const X_VERCEL_BLOB_MPU_PART_NUMBER: &str = "x-mpu-part-number";
54 pub const X_VERCEL_BLOB_MPU_UPLOAD_ID: &str = "x-mpu-upload-id";
55}
56
57#[derive(Clone)]
58pub struct VercelBlobCore {
59 pub info: Arc<AccessorInfo>,
60 pub root: String,
62 pub token: String,
64}
65
66impl Debug for VercelBlobCore {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("Backend")
69 .field("root", &self.root)
70 .finish_non_exhaustive()
71 }
72}
73
74impl VercelBlobCore {
75 #[inline]
76 pub async fn send(&self, req: Request<Buffer>) -> Result<Response<Buffer>> {
77 self.info.http_client().send(req).await
78 }
79
80 pub fn sign(&self, req: request::Builder) -> request::Builder {
81 req.header(header::AUTHORIZATION, format!("Bearer {}", self.token))
82 }
83}
84
85impl VercelBlobCore {
86 pub async fn download(
87 &self,
88 path: &str,
89 range: BytesRange,
90 _: &OpRead,
91 ) -> Result<Response<HttpBody>> {
92 let p = build_abs_path(&self.root, path);
93 let resp = self.list(&p, Some(1)).await?;
96
97 let url = resolve_blob(resp.blobs, p);
99
100 if url.is_empty() {
101 return Err(Error::new(ErrorKind::NotFound, "Blob not found"));
102 }
103
104 let mut req = Request::get(url);
105
106 if !range.is_full() {
107 req = req.header(header::RANGE, range.to_header());
108 }
109
110 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
112
113 self.info.http_client().fetch(req).await
114 }
115
116 pub fn get_put_request(
117 &self,
118 path: &str,
119 size: Option<u64>,
120 args: &OpWrite,
121 body: Buffer,
122 ) -> Result<Request<Buffer>> {
123 let p = build_abs_path(&self.root, path);
124
125 let url = format!(
126 "https://blob.vercel-storage.com/{}",
127 percent_encode_path(&p)
128 );
129
130 let mut req = Request::put(&url);
131
132 req = req.header(X_VERCEL_BLOB_ADD_RANDOM_SUFFIX, "0");
133
134 if let Some(size) = size {
135 req = req.header(header::CONTENT_LENGTH, size.to_string())
136 }
137
138 if let Some(mime) = args.content_type() {
139 req = req.header(X_VERCEL_BLOB_CONTENT_TYPE, mime)
140 }
141
142 let req = self.sign(req);
143
144 let req = req.body(body).map_err(new_request_build_error)?;
146
147 Ok(req)
148 }
149
150 pub async fn head(&self, path: &str) -> Result<Response<Buffer>> {
151 let p = build_abs_path(&self.root, path);
152
153 let resp = self.list(&p, Some(1)).await?;
154
155 let url = resolve_blob(resp.blobs, p);
156
157 if url.is_empty() {
158 return Err(Error::new(ErrorKind::NotFound, "Blob not found"));
159 }
160
161 let req = Request::get(format!(
162 "https://blob.vercel-storage.com?url={}",
163 percent_encode_path(&url)
164 ));
165
166 let req = self.sign(req);
167
168 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
170
171 self.send(req).await
172 }
173
174 pub async fn copy(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
175 let from = build_abs_path(&self.root, from);
176
177 let resp = self.list(&from, Some(1)).await?;
178
179 let from_url = resolve_blob(resp.blobs, from);
180
181 if from_url.is_empty() {
182 return Err(Error::new(ErrorKind::NotFound, "Blob not found"));
183 }
184
185 let to = build_abs_path(&self.root, to);
186
187 let to_url = format!(
188 "https://blob.vercel-storage.com/{}?fromUrl={}",
189 percent_encode_path(&to),
190 percent_encode_path(&from_url),
191 );
192
193 let req = Request::put(&to_url);
194
195 let req = self.sign(req);
196
197 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
199
200 self.send(req).await
201 }
202
203 pub async fn list(&self, prefix: &str, limit: Option<usize>) -> Result<ListResponse> {
204 let prefix = if prefix == "/" { "" } else { prefix };
205
206 let mut url = format!(
207 "https://blob.vercel-storage.com?prefix={}",
208 percent_encode_path(prefix)
209 );
210
211 if let Some(limit) = limit {
212 url.push_str(&format!("&limit={}", limit))
213 }
214
215 let req = Request::get(&url);
216
217 let req = self.sign(req);
218
219 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
221
222 let resp = self.send(req).await?;
223
224 let status = resp.status();
225
226 if status != StatusCode::OK {
227 return Err(parse_error(resp));
228 }
229
230 let body = resp.into_body();
231
232 let resp: ListResponse =
233 serde_json::from_reader(body.reader()).map_err(new_json_deserialize_error)?;
234
235 Ok(resp)
236 }
237
238 pub async fn initiate_multipart_upload(
239 &self,
240 path: &str,
241 args: &OpWrite,
242 ) -> Result<Response<Buffer>> {
243 let p = build_abs_path(&self.root, path);
244
245 let url = format!(
246 "https://blob.vercel-storage.com/mpu/{}",
247 percent_encode_path(&p)
248 );
249
250 let req = Request::post(&url);
251
252 let mut req = self.sign(req);
253
254 req = req.header(X_VERCEL_BLOB_MPU_ACTION, "create");
255 req = req.header(X_VERCEL_BLOB_ADD_RANDOM_SUFFIX, "0");
256
257 if let Some(mime) = args.content_type() {
258 req = req.header(X_VERCEL_BLOB_CONTENT_TYPE, mime);
259 };
260
261 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
263
264 self.send(req).await
265 }
266
267 pub async fn upload_part(
268 &self,
269 path: &str,
270 upload_id: &str,
271 part_number: usize,
272 size: u64,
273 body: Buffer,
274 ) -> Result<Response<Buffer>> {
275 let p = build_abs_path(&self.root, path);
276
277 let url = format!(
278 "https://blob.vercel-storage.com/mpu/{}",
279 percent_encode_path(&p)
280 );
281
282 let mut req = Request::post(&url);
283
284 req = req.header(header::CONTENT_LENGTH, size);
285 req = req.header(X_VERCEL_BLOB_MPU_ACTION, "upload");
286 req = req.header(X_VERCEL_BLOB_MPU_KEY, p);
287 req = req.header(X_VERCEL_BLOB_MPU_UPLOAD_ID, upload_id);
288 req = req.header(X_VERCEL_BLOB_MPU_PART_NUMBER, part_number);
289
290 let req = self.sign(req);
291
292 let req = req.body(body).map_err(new_request_build_error)?;
294
295 self.send(req).await
296 }
297
298 pub async fn complete_multipart_upload(
299 &self,
300 path: &str,
301 upload_id: &str,
302 parts: Vec<Part>,
303 ) -> Result<Response<Buffer>> {
304 let p = build_abs_path(&self.root, path);
305
306 let url = format!(
307 "https://blob.vercel-storage.com/mpu/{}",
308 percent_encode_path(&p)
309 );
310
311 let mut req = Request::post(&url);
312
313 req = req.header(X_VERCEL_BLOB_MPU_ACTION, "complete");
314 req = req.header(X_VERCEL_BLOB_MPU_KEY, p);
315 req = req.header(X_VERCEL_BLOB_MPU_UPLOAD_ID, upload_id);
316
317 let req = self.sign(req);
318
319 let parts_json = json!(parts);
320
321 let req = req
322 .header(header::CONTENT_TYPE, "application/json")
323 .body(Buffer::from(Bytes::from(parts_json.to_string())))
324 .map_err(new_request_build_error)?;
325
326 self.send(req).await
327 }
328}
329
330pub fn parse_blob(blob: &Blob) -> Result<Metadata> {
331 let mode = if blob.pathname.ends_with('/') {
332 EntryMode::DIR
333 } else {
334 EntryMode::FILE
335 };
336 let mut md = Metadata::new(mode);
337 if let Some(content_type) = blob.content_type.clone() {
338 md.set_content_type(&content_type);
339 }
340 md.set_content_length(blob.size);
341 md.set_last_modified(parse_datetime_from_rfc3339(&blob.uploaded_at)?);
342 md.set_content_disposition(&blob.content_disposition);
343 Ok(md)
344}
345
346fn resolve_blob(blobs: Vec<Blob>, path: String) -> String {
347 for blob in blobs {
348 if blob.pathname == path {
349 return blob.url;
350 }
351 }
352 "".to_string()
353}
354
355#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct ListResponse {
358 pub cursor: Option<String>,
359 pub has_more: bool,
360 pub blobs: Vec<Blob>,
361}
362
363#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
364#[serde(rename_all = "camelCase")]
365pub struct Blob {
366 pub url: String,
367 pub pathname: String,
368 pub size: u64,
369 pub uploaded_at: String,
370 pub content_disposition: String,
371 pub content_type: Option<String>,
372}
373
374#[derive(Default, Debug, Clone, PartialEq, Serialize)]
375#[serde(rename_all = "camelCase")]
376pub struct Part {
377 pub part_number: usize,
378 pub etag: String,
379}
380
381#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
382#[serde(rename_all = "camelCase")]
383pub struct InitiateMultipartUploadResponse {
384 pub upload_id: String,
385 pub key: String,
386}
387
388#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct UploadPartResponse {
391 pub etag: String,
392}