cf_r2_sdk/
lib.rs

1//! # Unofficial Cloudflare R2 SDK
2//!
3//! This is the "Unofficial Cloudflare R2 SDK".
4//!
5//! It can upload, download, and delete binary data or files to Cloudflare R2.
6//!
7//! This crate is based on [cloudflare-r2-rs](https://crates.io/crates/cloudflare-r2-rs) (License: [Apache-2.0](https://choosealicense.com/licenses/apache-2.0/), Owner: [milen-denev](https://github.com/milen-denev)) and [r2sync](https://crates.io/crates/r2sync) (License: [MIT](https://github.com/Songmu/r2sync/blob/main/LICENSE), Owner: [Songmu](https://github.com/Songmu)).
8//!
9//! <div class="warning">
10//!
11//! date: 2025-01-18
12//!
13//! This crate is <strong>solved</strong> [this problem](https://www.cloudflarestatus.com/incidents/t5nrjmpxc1cj) by adding the following S3Client config.
14//!
15//! requestChecksumCalculation: "WHEN_REQUIRED",
16//! responseChecksumValidation: "WHEN_REQUIRED",
17//!
18//! Reference: [https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/)
19//!
20//! </div>
21//!
22//! # Struct
23//!
24//! [builder::Builder](https://docs.rs/cf-r2-sdk/latest/cf_r2_sdk/builder/struct.Builder.html)
25//!
26//! [operator::Operator](https://docs.rs/cf-r2-sdk/latest/cf_r2_sdk/operator/struct.Operator.html)
27//!
28//! # How to use
29//!
30//! ### 1. Create a aws_sdk_s3::Client object
31//!
32//! Set the "bucket name", "access key id", "secret access key", "endpoint url", and "region".
33//!
34//! Default value of region is "auto" (region is option field).
35//!
36//! ```
37//! use cf_r2_sdk::builder::Builder;
38//! use cf_r2_sdk::error::Error;
39//! use dotenvy::dotenv;
40//! use std::env;
41//!
42//! #[tokio::main(flavor = "current_thread")]
43//! async fn main() -> Result<(), Error> {
44//!     // load .env file
45//!     dotenv().expect(".env file not found.");
46//!     // insert a environment variable
47//!     let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
48//!     let endpoint_url: String =
49//!         env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
50//!     let access_key_id: String =
51//!         env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
52//!     let secret_access_key: String =
53//!         env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
54//!     let region: String = env::var("REGION").expect("REGION not found in .env file.");
55//!
56//!     let object: cf_r2_sdk::operator::Operator = Builder::new()
57//!         .set_bucket_name(bucket_name)
58//!         .set_access_key_id(access_key_id)
59//!         .set_secret_access_key(secret_access_key)
60//!         .set_endpoint(endpoint_url)
61//!         .set_region(region)
62//!         .create_client_result()?;
63//!     Ok(())
64//! }
65//! ```
66//!
67//! ### 2. Operate R2 object strage
68//!
69//! #### Upload binary data
70//!
71//!
72//! ```
73//! use cf_r2_sdk::builder::Builder;
74//! use cf_r2_sdk::error::Error;
75//! use dotenvy::dotenv;
76//! use std::env;
77//!
78//! #[tokio::main(flavor = "current_thread")]
79//! async fn main() -> Result<(), Error> {
80//!    // load .env file
81//!    dotenv().expect(".env file not found.");
82//!    // insert a environment variable
83//!    let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
84//!    let endpoint_url: String =
85//!        env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
86//!    let access_key_id: String =
87//!        env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
88//!    let secret_access_key: String =
89//!       env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
90//!    let region: String = env::var("REGION").expect("REGION not found in .env file.");
91//!
92//!    let object: cf_r2_sdk::operator::Operator = Builder::new()
93//!        .set_bucket_name(bucket_name)
94//!        .set_access_key_id(access_key_id)
95//!        .set_secret_access_key(secret_access_key)
96//!        .set_endpoint(endpoint_url)
97//!        .set_region(region)
98//!        .create_client_result()?;
99//!
100//!    // upload binary data
101//!    object
102//!        .upload_binary("sample.txt", "test/plain", b"Hello, World!", None)
103//!        .await?;
104//!    Ok(())
105//! }
106//! ```
107//!
108//! #### upload file
109//!
110//! ```
111//! use cf_r2_sdk::builder::Builder;
112//! use cf_r2_sdk::error::Error;
113//! use dotenvy::dotenv;
114//! use std::env;
115//!
116//! #[tokio::main(flavor = "current_thread")]
117//! async fn main() -> Result<(), Error> {
118//!    // load .env file
119//!    dotenv().expect(".env file not found.");
120//!    // insert a environment variable
121//!    let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
122//!    let endpoint_url: String =
123//!        env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
124//!    let access_key_id: String =
125//!        env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
126//!    let secret_access_key: String =
127//!       env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
128//!    let region: String = env::var("REGION").expect("REGION not found in .env file.");
129//!
130//!    let object: cf_r2_sdk::operator::Operator = Builder::new()
131//!        .set_bucket_name(bucket_name)
132//!        .set_access_key_id(access_key_id)
133//!        .set_secret_access_key(secret_access_key)
134//!        .set_endpoint(endpoint_url)
135//!        .set_region(region)
136//!        .create_client_result()?;
137//!
138//!    // upload file
139//!    object
140//!        .upload_file("sample.jpg", "image/jpeg", "data/sample.jpg", None)
141//!        .await?;
142//!    Ok(())
143//! }
144//! ```
145//!
146//! #### download binary data
147//!
148//! ```
149//! use cf_r2_sdk::builder::Builder;
150//! use cf_r2_sdk::error::Error;
151//! use dotenvy::dotenv;
152//! use std::env;
153//!
154//! #[tokio::main(flavor = "current_thread")]
155//! async fn main() -> Result<(), Error> {
156//!    // load .env file
157//!    dotenv().expect(".env file not found.");
158//!    // insert a environment variable
159//!    let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
160//!    let endpoint_url: String =
161//!        env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
162//!    let access_key_id: String =
163//!        env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
164//!    let secret_access_key: String =
165//!       env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
166//!    let region: String = env::var("REGION").expect("REGION not found in .env file.");
167//!
168//!    let object: cf_r2_sdk::operator::Operator = Builder::new()
169//!        .set_bucket_name(bucket_name)
170//!        .set_access_key_id(access_key_id)
171//!        .set_secret_access_key(secret_access_key)
172//!        .set_endpoint(endpoint_url)
173//!        .set_region(region)
174//!        .create_client_result()?;
175//!
176//!     object
177//!        .upload_binary("sample.txt", "test/plain", b"Hello, World!", None)
178//!        .await?;
179//!
180//!    // download binary data
181//!    let bin: Vec<u8> = object.download("sample.txt").await?;
182//!
183//!    println!("{:?}", bin);
184//!    Ok(())
185//! }
186//! ```
187//!
188//! #### delete file
189//!
190//! ```
191//! use cf_r2_sdk::builder::Builder;
192//! use cf_r2_sdk::error::Error;
193//! use dotenvy::dotenv;
194//! use std::env;
195//!
196//! #[tokio::main(flavor = "current_thread")]
197//! async fn main() -> Result<(), Error> {
198//!    // load .env file
199//!    dotenv().expect(".env file not found.");
200//!    // insert a environment variable
201//!    let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
202//!    let endpoint_url: String =
203//!        env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
204//!    let access_key_id: String =
205//!        env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
206//!    let secret_access_key: String =
207//!       env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
208//!    let region: String = env::var("REGION").expect("REGION not found in .env file.");
209//!
210//!    let object: cf_r2_sdk::operator::Operator = Builder::new()
211//!        .set_bucket_name(bucket_name)
212//!        .set_access_key_id(access_key_id)
213//!        .set_secret_access_key(secret_access_key)
214//!        .set_endpoint(endpoint_url)
215//!        .set_region(region)
216//!        .create_client_result()?;
217//!
218//!    object
219//!        .upload_binary("sample.txt", "test/plain", b"Hello, World!", None)
220//!        .await?;
221//!
222//!    // delete file
223//!    object
224//!        .delete("sample.txt")
225//!        .await?;
226//!
227//!    Ok(())
228//! }
229//! ```
230//!
231//! #### get file names vector
232//!
233//! ```
234//! use cf_r2_sdk::builder::Builder;
235//! use cf_r2_sdk::error::Error;
236//! use dotenvy::dotenv;
237//! use std::env;
238//!
239//! #[tokio::main(flavor = "current_thread")]
240//! async fn main() -> Result<(), Error> {
241//!    // load .env file
242//!    dotenv().expect(".env file not found.");
243//!    // insert a environment variable
244//!    let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
245//!    let endpoint_url: String =
246//!        env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
247//!    let access_key_id: String =
248//!        env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
249//!    let secret_access_key: String =
250//!       env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
251//!    let region: String = env::var("REGION").expect("REGION not found in .env file.");
252//!
253//!    let object: cf_r2_sdk::operator::Operator = Builder::new()
254//!        .set_bucket_name(bucket_name)
255//!        .set_access_key_id(access_key_id)
256//!        .set_secret_access_key(secret_access_key)
257//!        .set_endpoint(endpoint_url)
258//!        .set_region(region)
259//!        .create_client_result()?;
260//!
261//!    object
262//!        .upload_binary("sample.txt", "test/plain", b"Hello, World!", None)
263//!        .await?;
264//!
265//!    // get file names vector
266//!    let file_names: Vec<String> = object.list_objects().await?;
267//!
268//!    for file_name in file_names {
269//!       println!("{}", file_name);
270//!    }
271//!
272//!    Ok(())
273//! }
274//! ```
275
276pub mod builder;
277pub mod error;
278pub mod operator;
279
280#[cfg(test)]
281mod tests {
282    use crate::error::Error;
283
284    use super::*;
285    use builder::Builder;
286    use dotenvy::dotenv;
287    use std::env;
288    use tokio::{fs::File, io::AsyncReadExt};
289
290    #[tokio::test]
291    async fn local_test_1_upload_and_download_binary() -> Result<(), Error> {
292        // load .env file
293        dotenv().expect(".env file not found.");
294        // insert a environment variable
295        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
296        let endpoint_url: String =
297            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
298        let access_key_id: String =
299            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
300        let secret_access_key: String =
301            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
302        let region: String = env::var("REGION").expect("REGION not found in .env file.");
303
304        let object = Builder::new()
305            .set_bucket_name(bucket_name)
306            .set_access_key_id(access_key_id)
307            .set_secret_access_key(secret_access_key)
308            .set_endpoint(endpoint_url)
309            .set_region(region)
310            .create_client_result()?;
311
312        object
313            .upload_binary("test.txt", "text/plain", b"Hello, World!", None)
314            .await?;
315
316        let bin = object.download("test.txt").await;
317        match bin {
318            Ok(bin) => assert_eq!(bin, b"Hello, World!"),
319            Err(e) => panic!("Error: {:?}", e),
320        }
321        Ok(())
322    }
323
324    #[tokio::test]
325    async fn local_test_2_upload_and_download_file() -> Result<(), Error> {
326        // load .env file
327        dotenv().expect(".env file not found.");
328        // insert a environment variable
329        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
330        let endpoint_url: String =
331            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
332        let access_key_id: String =
333            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
334        let secret_access_key: String =
335            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
336        let region: String = env::var("REGION").expect("REGION not found in .env file.");
337
338        let object = Builder::new()
339            .set_bucket_name(bucket_name)
340            .set_access_key_id(access_key_id)
341            .set_secret_access_key(secret_access_key)
342            .set_endpoint(endpoint_url)
343            .set_region(region)
344            .create_client_result()?;
345
346        let file_path = "./data/sample.jpg";
347        object
348            .upload_file("sample.jpg", "image/jpeg", file_path, None)
349            .await?;
350
351        let bin = object.download("sample.jpg").await;
352
353        //read file from local
354        let mut file = File::open(file_path).await.expect("Failed to open file");
355        let mut buffer = Vec::new();
356        file.read_to_end(&mut buffer)
357            .await
358            .expect("Failed to close file");
359
360        match bin {
361            Ok(bin) => assert_eq!(bin, buffer),
362            Err(e) => panic!("Error: {:?}", e),
363        }
364        Ok(())
365    }
366
367    #[tokio::test]
368    async fn local_test_3_upload_and_delete() -> Result<(), Error> {
369        // load .env file
370        dotenv().expect(".env file not found.");
371        // insert a environment variable
372        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
373        let endpoint_url: String =
374            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
375        let access_key_id: String =
376            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
377        let secret_access_key: String =
378            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
379        let region: String = env::var("REGION").expect("REGION not found in .env file.");
380
381        let object = Builder::new()
382            .set_bucket_name(bucket_name)
383            .set_access_key_id(access_key_id)
384            .set_secret_access_key(secret_access_key)
385            .set_endpoint(endpoint_url)
386            .set_region(region)
387            .create_client_result()?;
388
389        object
390            .upload_binary("text.txt", "text/plain", b"Hello, World!", None)
391            .await?;
392
393        object.delete("text.txt").await?;
394
395        let bin: Result<Vec<u8>, error::OperationError> = object.download("text.txt").await;
396        if bin.is_ok() {
397            panic!("Error: {:?}", bin)
398        }
399        Ok(())
400    }
401
402    #[tokio::test]
403    async fn local_test_4_list_objects() -> Result<(), Error> {
404        // load .env file
405        dotenv().expect(".env file not found.");
406        // insert a environment variable
407        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
408        let endpoint_url: String =
409            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
410        let access_key_id: String =
411            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
412        let secret_access_key: String =
413            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
414        let region: String = env::var("REGION").expect("REGION not found in .env file.");
415
416        let object = Builder::new()
417            .set_bucket_name(bucket_name)
418            .set_access_key_id(access_key_id)
419            .set_secret_access_key(secret_access_key)
420            .set_endpoint(endpoint_url)
421            .set_region(region)
422            .create_client_result()?;
423
424        let _data = object.list_objects().await?;
425
426        Ok(())
427    }
428
429    //* Actions Test *//
430    #[tokio::test]
431    #[ignore]
432    async fn actions_test_1_upload_and_download_binary() -> Result<(), Error> {
433        // insert a environment variable
434        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
435        let endpoint_url: String =
436            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
437        let access_key_id: String =
438            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
439        let secret_access_key: String =
440            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
441        let region: String = env::var("REGION").expect("REGION not found in .env file.");
442
443        let object = Builder::new()
444            .set_bucket_name(bucket_name)
445            .set_access_key_id(access_key_id)
446            .set_secret_access_key(secret_access_key)
447            .set_endpoint(endpoint_url)
448            .set_region(region)
449            .create_client_result()?;
450
451        object
452            .upload_binary("test.txt", "text/plain", b"Hello, World!", None)
453            .await?;
454
455        let bin = object.download("test.txt").await;
456        match bin {
457            Ok(bin) => assert_eq!(bin, b"Hello, World!"),
458            Err(e) => panic!("Error: {:?}", e),
459        }
460        Ok(())
461    }
462
463    #[tokio::test]
464    #[ignore]
465    async fn actions_test_2_upload_and_download_file() -> Result<(), Error> {
466        // insert a environment variable
467        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
468        let endpoint_url: String =
469            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
470        let access_key_id: String =
471            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
472        let secret_access_key: String =
473            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
474        let region: String = env::var("REGION").expect("REGION not found in .env file.");
475
476        let object = Builder::new()
477            .set_bucket_name(bucket_name)
478            .set_access_key_id(access_key_id)
479            .set_secret_access_key(secret_access_key)
480            .set_endpoint(endpoint_url)
481            .set_region(region)
482            .create_client_result()?;
483
484        let file_path = "./data/sample.jpg";
485        object
486            .upload_file("sample.jpg", "image/jpeg", file_path, None)
487            .await?;
488
489        let bin = object.download("sample.jpg").await;
490
491        //read file from local
492        let mut file = File::open(file_path).await.expect("Failed to open file");
493        let mut buffer = Vec::new();
494        file.read_to_end(&mut buffer)
495            .await
496            .expect("Failed to close file");
497
498        match bin {
499            Ok(bin) => assert_eq!(bin, buffer),
500            Err(e) => panic!("Error: {:?}", e),
501        }
502        Ok(())
503    }
504
505    #[tokio::test]
506    #[ignore]
507    async fn actions_test_3_upload_and_delete() -> Result<(), Error> {
508        // insert a environment variable
509        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
510        let endpoint_url: String =
511            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
512        let access_key_id: String =
513            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
514        let secret_access_key: String =
515            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
516        let region: String = env::var("REGION").expect("REGION not found in .env file.");
517
518        let object = Builder::new()
519            .set_bucket_name(bucket_name)
520            .set_access_key_id(access_key_id)
521            .set_secret_access_key(secret_access_key)
522            .set_endpoint(endpoint_url)
523            .set_region(region)
524            .create_client_result()?;
525
526        object
527            .upload_binary("text.txt", "text/plain", b"Hello, World!", None)
528            .await?;
529
530        object.delete("text.txt").await?;
531
532        let bin: Result<Vec<u8>, error::OperationError> = object.download("text.txt").await;
533        if bin.is_ok() {
534            panic!("Error: {:?}", bin)
535        }
536        Ok(())
537    }
538
539    #[tokio::test]
540    #[ignore]
541    async fn actions_test_4_list_objects() -> Result<(), Error> {
542        // insert a environment variable
543        let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
544        let endpoint_url: String =
545            env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
546        let access_key_id: String =
547            env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
548        let secret_access_key: String =
549            env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
550        let region: String = env::var("REGION").expect("REGION not found in .env file.");
551
552        let object = Builder::new()
553            .set_bucket_name(bucket_name)
554            .set_access_key_id(access_key_id)
555            .set_secret_access_key(secret_access_key)
556            .set_endpoint(endpoint_url)
557            .set_region(region)
558            .create_client_result()?;
559
560        let _data = object.list_objects().await?;
561
562        Ok(())
563    }
564}