From ea057c1db37a9ad007787536c2c24b90677e3a69 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 20 May 2025 12:50:27 +0200 Subject: [PATCH 01/15] docs: add crates.io and visitors badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 34ea671..8a76eba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # bunny-api-tokio [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Crates.io](https://img.shields.io/crates/v/bunny-api-tokio.svg)](https://crates.io/crates/bunny-api-tokio) +[![Visitors](https://visitor-badge.laobi.icu/badge?page_id=gorb.bunny-api-tokio)](https://git.gorb.app/gorb/bunny-api-tokio) A Rust library providing **asynchronous access to the Bunny CDN API** using Tokio. From 0c6c5174b51be396aa6df3668c41a0d7116ca8c3 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 20 May 2025 13:18:29 +0200 Subject: [PATCH 02/15] feat: add useful derives to structs/enums --- src/edge_storage.rs | 4 +++- src/lib.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/edge_storage.rs b/src/edge_storage.rs index 6dac926..da14eb1 100644 --- a/src/edge_storage.rs +++ b/src/edge_storage.rs @@ -11,6 +11,7 @@ use serde::Deserialize; use url::Url; /// Endpoints for Edge Storage API +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Endpoint { /// Uses https://storage.bunnycdn.com as endpoint Frankfurt, @@ -54,7 +55,7 @@ impl TryInto for Endpoint { } /// File information returned by list -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] pub struct ListFile { /// ?? @@ -90,6 +91,7 @@ pub struct ListFile { } /// Edge Storage API for bunny +#[derive(Debug, Clone)] pub struct Storage { pub(crate) url: Url, pub(crate) reqwest: Arc, diff --git a/src/lib.rs b/src/lib.rs index e545ba9..bd1e383 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod edge_storage; pub mod error; /// API Client for bunny +#[derive(Debug, Clone)] pub struct Client { /// Used to interact with the Edge Storage API pub storage: edge_storage::Storage, From 9e52206eb40588b890129914f805a8576268fe62 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 20 May 2025 13:19:02 +0200 Subject: [PATCH 03/15] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68e81fa..c03a006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,7 +64,7 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bunny-api-tokio" -version = "0.2.0" +version = "0.2.1" dependencies = [ "bytes", "log", diff --git a/Cargo.toml b/Cargo.toml index df35fc2..8f43368 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bunny-api-tokio" -version = "0.2.0" +version = "0.2.1" edition = "2024" authors = ["Radical "] license = "MIT" From 52df8dae74e053589854555a1adecec1068823b4 Mon Sep 17 00:00:00 2001 From: radical Date: Fri, 23 May 2025 18:17:39 +0000 Subject: [PATCH 04/15] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8a76eba..c47f0c3 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ A Rust library providing **asynchronous access to the Bunny CDN API** using Tokio. +## Issues/PRs + +Issues and PRs can be submitted on the [GitHub mirror](https://github.com/gorb-app/bunny-api-tokio) + ## Features - **Async-first**: Built with Tokio for non-blocking API calls. - **Edge Storage API**: Supports Bunny's edge storage operations. From e55dd32305a03507393b0f01a1afa1ea3d1b0dd5 Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 24 May 2025 02:58:55 +0200 Subject: [PATCH 05/15] fix: Use own API Key on edge_storage --- src/edge_storage.rs | 33 +++++++++++++++++++-------------- src/lib.rs | 10 +++++++--- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/edge_storage.rs b/src/edge_storage.rs index da14eb1..e64b0af 100644 --- a/src/edge_storage.rs +++ b/src/edge_storage.rs @@ -2,11 +2,9 @@ //! //! Contains enums, structs and functions for the Bunny Edge Storage API -use std::sync::Arc; - use crate::Error; use bytes::Bytes; -use reqwest::Client; +use reqwest::{header::{HeaderMap, HeaderValue}, Client}; use serde::Deserialize; use url::Url; @@ -94,7 +92,7 @@ pub struct ListFile { #[derive(Debug, Clone)] pub struct Storage { pub(crate) url: Url, - pub(crate) reqwest: Arc, + pub(crate) reqwest: Client, } impl<'a> Storage { @@ -105,18 +103,25 @@ impl<'a> Storage { /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { + /// // API key here can be left as "" if you never plan on using anything from the bunny.net api /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); + /// // Requires own API key to use + /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// /// Ok(()) /// } /// ``` - pub fn init>( + pub async fn init, T1: AsRef>( &mut self, + api_key: T, endpoint: Endpoint, - storage_zone: T, + storage_zone: T1, ) -> Result<(), Error> { + let mut headers = HeaderMap::new(); + headers.append("AccessKey", HeaderValue::from_str(api_key.as_ref())?); + + self.reqwest = Client::builder().default_headers(headers).build()?; let endpoint: Url = endpoint.try_into()?; let storage_zone = String::from("/") + storage_zone.as_ref() + "/"; @@ -134,9 +139,9 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); + /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// - /// let file_bytes = fs::read("path/to/file.png").await?; + /// let file_bytes = fs::read("path/to/file.png").await.unwrap(); /// /// // Will put a file in STORAGE_ZONE/images/file.png /// client.storage.upload("/images/file.png", file_bytes).await?; @@ -173,13 +178,13 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); + /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// /// // Will download the file STORAGE_ZONE/images/file.png /// let contents = client.storage.download("/images/file.png").await?; /// - /// let mut file = fs::File::create("file.png").await?; - /// file.write_all(contents).await?; + /// let mut file = fs::File::create("file.png").await.unwrap(); + /// file.write_all(contents).await.unwrap(); /// /// Ok(()) /// } @@ -210,7 +215,7 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); + /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// /// // Will delete the file STORAGE_ZONE/images/file.png /// client.storage.delete("/images/file.png").await?; @@ -243,7 +248,7 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); + /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// /// // Will list the files in STORAGE_ZONE/images/ /// let files = client.storage.list("/images/").await?; diff --git a/src/lib.rs b/src/lib.rs index bd1e383..dfe3a24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,6 @@ use reqwest::{ Client as RClient, header::{HeaderMap, HeaderValue}, }; -use std::sync::Arc; use url::Url; pub mod edge_storage; @@ -33,6 +32,7 @@ pub mod error; /// API Client for bunny #[derive(Debug, Clone)] pub struct Client { + reqwest: RClient, /// Used to interact with the Edge Storage API pub storage: edge_storage::Storage, } @@ -45,6 +45,7 @@ impl Client { /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { + /// // Bunny.net api key /// let mut client = Client::new("api_key").await?; /// /// Ok(()) @@ -53,13 +54,16 @@ impl Client { pub async fn new>(api_key: T) -> Result { let mut headers = HeaderMap::new(); headers.append("AccessKey", HeaderValue::from_str(api_key.as_ref())?); + headers.append("accept", HeaderValue::from_str("application/json")?); - let reqwest = Arc::new(RClient::builder().default_headers(headers).build()?); + let reqwest = RClient::builder().default_headers(headers).build()?; + let storage_reqwest = RClient::new(); Ok(Self { + reqwest, storage: edge_storage::Storage { url: Url::parse("https://storage.bunnycdn.com").unwrap(), - reqwest, + reqwest: storage_reqwest, }, }) } From a9e05c31564c2facaf9b79f391c5407f6ca0ccff Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 24 May 2025 03:04:03 +0200 Subject: [PATCH 06/15] feat: add endpoints from bunny.net API Adds get_country_list(), list_api_keys(), region_list() and purge_url() from the API. These names are probably gonna change to be more streamlined, they are currently derived from the API reference page titles. --- src/bunny/mod.rs | 218 +++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 4 + src/lib.rs | 1 + 3 files changed, 223 insertions(+) create mode 100644 src/bunny/mod.rs diff --git a/src/bunny/mod.rs b/src/bunny/mod.rs new file mode 100644 index 0000000..2b7e78e --- /dev/null +++ b/src/bunny/mod.rs @@ -0,0 +1,218 @@ +//! Contains structs, enums and implementations for the main bunny.net API + +use serde::Deserialize; +use url::Url; + +use crate::{Client, error::Error}; + +/// Country struct returned by get_countries() function +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct Country { + /// Country name + pub name: String, + /// Country ISO code + pub iso_code: String, + /// Country is part of the EU + #[serde(rename = "IsEU")] + pub is_eu: bool, + /// Tax rate in percentage + pub tax_rate: f32, + /// Tax prefix + pub tax_prefix: String, + /// URL to country flag + pub flag_url: Url, + /// ?? + pub pop_list: Vec, +} + +/// API Key struct returned by list_api_keys() +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct ApiKey { + /// API Key ID + pub id: i32, + /// API Key + pub key: String, + /// ?? + pub roles: Vec, +} + +/// Pagination struct used by Bunny.net API +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct Pagination { + /// Vector of type T + pub items: Vec, + /// Current page number + pub current_page: i32, + /// Total amount of type T + pub total_items: i32, + /// Has more items + pub has_more_items: bool, +} + +/// Region struct returned by region_list() +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct Region { + /// Region ID + pub id: i32, + /// Name of the region + pub name: String, + /// Price per gigabyte in region + pub price_per_gigabyte: f32, + /// Region 2 letter code + pub region_code: String, + /// Continent 2 letter code + pub continent_code: String, + /// Country 2 letter code + pub country_code: String, + /// Region latitude + pub latitude: f32, + /// Region longitude + pub longitude: f32, + /// ?? + pub allow_latency_routing: bool, +} + +impl Client { + // TODO: Following functions could probably use better naming, the names are currently derived from the titles on the API reference + + /// Returns a list of countries and tax rates + /// + /// ``` + /// use bunny_api_tokio::{Client, error::Error}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// // Bunny.net api key + /// let mut client = Client::new("api_key").await?; + /// + /// let countries = client.get_countries().await?; + /// + /// println!("{:#?}", countries); + /// Ok(()) + /// } + /// ``` + pub async fn get_country_list(&self) -> Result, Error> { + let response = self + .reqwest + .get("https://api.bunny.net/country") + .header("accept", "application/json") + .send() + .await?; + + if response.status().as_u16() == 401 { + return Err(Error::Authentication(response.text().await?)); + } else if response.status().as_u16() == 500 { + return Err(Error::InternalServerError(response.text().await?)); + } + + Ok(response.json().await?) + } + + /// Returns a list of API Keys + /// + /// ``` + /// use bunny_api_tokio::{Client, error::Error}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// // Bunny.net api key + /// let mut client = Client::new("api_key").await?; + /// + /// let api_keys = client.list_api_keys(1, 1000).await?; + /// + /// println!("{:#?}", api_keys); + /// Ok(()) + /// } + /// ``` + pub async fn list_api_keys( + &self, + page: i32, + per_page: i32, + ) -> Result, Error> { + let response = self + .reqwest + .get("https://api.bunny.net/apikey") + .query(&[("page", page), ("perPage", per_page)]) + .send() + .await?; + + if response.status().as_u16() == 401 { + return Err(Error::Authentication(response.text().await?)); + } else if response.status().as_u16() == 500 { + return Err(Error::InternalServerError(response.text().await?)); + } + + Ok(response.json().await?) + } + + /// Returns a list of Regions + /// + /// ``` + /// use bunny_api_tokio::{Client, error::Error}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// // Bunny.net api key + /// let mut client = Client::new("api_key").await?; + /// + /// let regions = client.region_list().await?; + /// + /// println!("{:#?}", regions); + /// Ok(()) + /// } + /// ``` + pub async fn region_list(&self) -> Result, Error> { + let response = self + .reqwest + .get("https://api.bunny.net/region") + .send() + .await?; + + if response.status().as_u16() == 401 { + return Err(Error::Authentication(response.text().await?)); + } else if response.status().as_u16() == 500 { + return Err(Error::InternalServerError(response.text().await?)); + } + + Ok(response.json().await?) + } + + /// Purges a URL from the cache + /// + /// ``` + /// use bunny_api_tokio::{Client, error::Error}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// // Bunny.net api key + /// let mut client = Client::new("api_key").await?; + /// + /// client.purge_url("https://url_to_purge.com", false).await?; + /// + /// Ok(()) + /// } + /// ``` + pub async fn purge_url(&self, url: Url, asynchronous: bool) -> Result<(), Error> { + let response = self + .reqwest + .post("https://api.bunny.net/purge") + .query(&[ + ("url", url.to_string()), + ("async", asynchronous.to_string()), + ]) + .send() + .await?; + + if response.status().as_u16() == 401 { + return Err(Error::Authentication(response.text().await?)); + } else if response.status().as_u16() == 500 { + return Err(Error::InternalServerError(response.text().await?)); + } + + Ok(response.json().await?) + } +} diff --git a/src/error.rs b/src/error.rs index 4b012bd..d5f7d57 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,4 +26,8 @@ pub enum Error { /// Not found error #[error("not found: {0}")] NotFound(String), + + /// Internal server error + #[error("internal server error: {0}")] + InternalServerError(String), } diff --git a/src/lib.rs b/src/lib.rs index dfe3a24..7f2242f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ use reqwest::{ }; use url::Url; +pub mod bunny; pub mod edge_storage; pub mod error; From d66a3d085069506efda4c85aa2880982a8cae23b Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 24 May 2025 03:04:25 +0200 Subject: [PATCH 07/15] build: bump version --- Cargo.lock | 3 ++- Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03a006..2cb65e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,7 +64,7 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bunny-api-tokio" -version = "0.2.1" +version = "0.3.0" dependencies = [ "bytes", "log", @@ -1184,6 +1184,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8f43368..cb7e8b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bunny-api-tokio" -version = "0.2.1" +version = "0.3.0" edition = "2024" authors = ["Radical "] license = "MIT" @@ -22,4 +22,4 @@ reqwest = { version = "0.12.15", features = ["json"] } serde = { version = "1.0.219", features = ["derive"] } thiserror = "2.0.12" tokio = "1.45.0" -url = "2.5.4" +url = { version = "2.5.4", features = ["serde"] } From 6832215aaaca5bda4da1d01580aadeaae216fa08 Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 24 May 2025 03:34:03 +0200 Subject: [PATCH 08/15] build: add dev-dependencies for doctest Most tests will still fail due to the API key but this will make checking of the code formatting, etc. easier. --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 2 ++ 2 files changed, 14 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2cb65e8..14812c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1075,9 +1075,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index cb7e8b9..e293b03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ keywords = [ "tokio", ] +[dev-dependencies] +tokio = { version = "1.45.0", features = ["fs", "rt", "rt-multi-thread", "macros"] } [dependencies] bytes = "1.10.1" From 53e5238485efe3a293aa1932c54e914bb1b1ca40 Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 24 May 2025 03:34:07 +0200 Subject: [PATCH 09/15] fix: use correct syntax in doctests --- src/bunny/mod.rs | 4 ++-- src/edge_storage.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bunny/mod.rs b/src/bunny/mod.rs index 2b7e78e..7d2c333 100644 --- a/src/bunny/mod.rs +++ b/src/bunny/mod.rs @@ -89,7 +89,7 @@ impl Client { /// // Bunny.net api key /// let mut client = Client::new("api_key").await?; /// - /// let countries = client.get_countries().await?; + /// let countries = client.get_country_list().await?; /// /// println!("{:#?}", countries); /// Ok(()) @@ -191,7 +191,7 @@ impl Client { /// // Bunny.net api key /// let mut client = Client::new("api_key").await?; /// - /// client.purge_url("https://url_to_purge.com", false).await?; + /// client.purge_url("https://url_to_purge.com".parse()?, false).await?; /// /// Ok(()) /// } diff --git a/src/edge_storage.rs b/src/edge_storage.rs index e64b0af..93a70a9 100644 --- a/src/edge_storage.rs +++ b/src/edge_storage.rs @@ -144,7 +144,7 @@ impl<'a> Storage { /// let file_bytes = fs::read("path/to/file.png").await.unwrap(); /// /// // Will put a file in STORAGE_ZONE/images/file.png - /// client.storage.upload("/images/file.png", file_bytes).await?; + /// client.storage.upload("/images/file.png", file_bytes.into()).await?; /// /// Ok(()) /// } @@ -184,7 +184,7 @@ impl<'a> Storage { /// let contents = client.storage.download("/images/file.png").await?; /// /// let mut file = fs::File::create("file.png").await.unwrap(); - /// file.write_all(contents).await.unwrap(); + /// file.write_all(&contents).await.unwrap(); /// /// Ok(()) /// } @@ -253,7 +253,7 @@ impl<'a> Storage { /// // Will list the files in STORAGE_ZONE/images/ /// let files = client.storage.list("/images/").await?; /// - /// println!("{:#?}", files) + /// println!("{:#?}", files); /// /// Ok(()) /// } From 914db27a98189514d58225fdc1d2fbe061605fe7 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 25 Jun 2025 13:58:37 +0200 Subject: [PATCH 10/15] feat: use features to split up api into its respective parts Edge Storage now has its own client EdgeStorageClient Bunny.net has its own client BunnyClient --- .vscode/settings.json | 3 + Cargo.lock | 165 +++++++------------ Cargo.toml | 17 +- src/bunny/api_key.rs | 13 ++ src/bunny/country.rs | 23 +++ src/bunny/mod.rs | 110 +++++-------- src/bunny/pagination.rs | 15 ++ src/bunny/region.rs | 25 +++ src/edge_storage/endpoint.rs | 46 ++++++ src/edge_storage/list_file.rs | 37 +++++ src/{edge_storage.rs => edge_storage/mod.rs} | 119 +++---------- src/lib.rs | 55 +------ 12 files changed, 304 insertions(+), 324 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/bunny/api_key.rs create mode 100644 src/bunny/country.rs create mode 100644 src/bunny/pagination.rs create mode 100644 src/bunny/region.rs create mode 100644 src/edge_storage/endpoint.rs create mode 100644 src/edge_storage/list_file.rs rename src/{edge_storage.rs => edge_storage/mod.rs} (59%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6a9403f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.features": ["bunnynet", "edge_storage"], "_":["stream", "edge_scripting", "bunny_shield"], +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 14812c9..9203d89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -64,7 +64,7 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bunny-api-tokio" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bytes", "log", @@ -372,22 +372,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -513,6 +519,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itoa" version = "1.0.15" @@ -720,15 +736,14 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", - "futures-util", "h2", "http", "http-body", @@ -737,29 +752,26 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", ] [[package]] @@ -808,15 +820,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -1065,9 +1068,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -1138,6 +1141,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -1318,19 +1339,19 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ + "windows-link", "windows-result", "windows-strings", - "windows-targets 0.53.0", ] [[package]] @@ -1344,9 +1365,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -1357,7 +1378,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1366,7 +1387,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1375,30 +1396,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -1407,96 +1412,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index e293b03..435b3c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bunny-api-tokio" -version = "0.3.0" +version = "0.4.0" edition = "2024" authors = ["Radical "] license = "MIT" @@ -14,14 +14,23 @@ keywords = [ "tokio", ] +[features] +default = ["bunnynet"] +bunnynet = [] +edge_storage = [] +# Kept here for future use +#stream = [] +#edge_scripting = [] +#bunny_shield = [] + [dev-dependencies] -tokio = { version = "1.45.0", features = ["fs", "rt", "rt-multi-thread", "macros"] } +tokio = { version = "1.45.1", features = ["fs", "rt", "rt-multi-thread", "macros"] } [dependencies] bytes = "1.10.1" log = "0.4.27" -reqwest = { version = "0.12.15", features = ["json"] } +reqwest = { version = "0.12.20", features = ["json"] } serde = { version = "1.0.219", features = ["derive"] } thiserror = "2.0.12" -tokio = "1.45.0" +tokio = "1.45.1" url = { version = "2.5.4", features = ["serde"] } diff --git a/src/bunny/api_key.rs b/src/bunny/api_key.rs new file mode 100644 index 0000000..3865e2b --- /dev/null +++ b/src/bunny/api_key.rs @@ -0,0 +1,13 @@ +use serde::Deserialize; + +/// API Key struct returned by list_api_keys() +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct ApiKey { + /// API Key ID + pub id: i32, + /// API Key + pub key: String, + /// ?? + pub roles: Vec, +} diff --git a/src/bunny/country.rs b/src/bunny/country.rs new file mode 100644 index 0000000..7a41c8b --- /dev/null +++ b/src/bunny/country.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; +use url::Url; + +/// Country struct returned by get_countries() function +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct Country { + /// Country name + pub name: String, + /// Country ISO code + pub iso_code: String, + /// Country is part of the EU + #[serde(rename = "IsEU")] + pub is_eu: bool, + /// Tax rate in percentage + pub tax_rate: f32, + /// Tax prefix + pub tax_prefix: String, + /// URL to country flag + pub flag_url: Url, + /// ?? + pub pop_list: Vec, +} diff --git a/src/bunny/mod.rs b/src/bunny/mod.rs index 7d2c333..231e02a 100644 --- a/src/bunny/mod.rs +++ b/src/bunny/mod.rs @@ -1,82 +1,54 @@ //! Contains structs, enums and implementations for the main bunny.net API -use serde::Deserialize; use url::Url; -use crate::{Client, error::Error}; +use crate::error::Error; +use reqwest::{ + Client as RClient, + header::{HeaderMap, HeaderValue}, +}; -/// Country struct returned by get_countries() function -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct Country { - /// Country name - pub name: String, - /// Country ISO code - pub iso_code: String, - /// Country is part of the EU - #[serde(rename = "IsEU")] - pub is_eu: bool, - /// Tax rate in percentage - pub tax_rate: f32, - /// Tax prefix - pub tax_prefix: String, - /// URL to country flag - pub flag_url: Url, - /// ?? - pub pop_list: Vec, +mod api_key; +pub use api_key::ApiKey; +mod country; +pub use country::Country; +mod pagination; +pub use pagination::Pagination; +mod region; +pub use region::Region; + +/// API Client for bunny.net +#[derive(Debug, Clone)] +pub struct BunnyClient { + reqwest: RClient, } -/// API Key struct returned by list_api_keys() -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct ApiKey { - /// API Key ID - pub id: i32, - /// API Key - pub key: String, - /// ?? - pub roles: Vec, -} +impl BunnyClient { + /// Creates a new BunnyClient using the supplied `api_key` + /// + /// ``` + /// use bunny_api_tokio::{BunnyClient, error::Error}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// // Bunny.net api key + /// let mut client = BunnyClient::new("api_key").await?; + /// + /// Ok(()) + /// } + /// ``` + pub async fn new>(api_key: T) -> Result { + let mut headers = HeaderMap::new(); + headers.append("AccessKey", HeaderValue::from_str(api_key.as_ref())?); + headers.append("accept", HeaderValue::from_str("application/json")?); -/// Pagination struct used by Bunny.net API -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct Pagination { - /// Vector of type T - pub items: Vec, - /// Current page number - pub current_page: i32, - /// Total amount of type T - pub total_items: i32, - /// Has more items - pub has_more_items: bool, -} + let reqwest = RClient::builder().default_headers(headers).build()?; -/// Region struct returned by region_list() -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct Region { - /// Region ID - pub id: i32, - /// Name of the region - pub name: String, - /// Price per gigabyte in region - pub price_per_gigabyte: f32, - /// Region 2 letter code - pub region_code: String, - /// Continent 2 letter code - pub continent_code: String, - /// Country 2 letter code - pub country_code: String, - /// Region latitude - pub latitude: f32, - /// Region longitude - pub longitude: f32, - /// ?? - pub allow_latency_routing: bool, -} + Ok(Self { + reqwest, + }) + } -impl Client { // TODO: Following functions could probably use better naming, the names are currently derived from the titles on the API reference /// Returns a list of countries and tax rates diff --git a/src/bunny/pagination.rs b/src/bunny/pagination.rs new file mode 100644 index 0000000..9a3240e --- /dev/null +++ b/src/bunny/pagination.rs @@ -0,0 +1,15 @@ +use serde::Deserialize; + +/// Pagination struct used by Bunny.net API +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct Pagination { + /// Vector of type T + pub items: Vec, + /// Current page number + pub current_page: i32, + /// Total amount of type T + pub total_items: i32, + /// Has more items + pub has_more_items: bool, +} diff --git a/src/bunny/region.rs b/src/bunny/region.rs new file mode 100644 index 0000000..4e333b9 --- /dev/null +++ b/src/bunny/region.rs @@ -0,0 +1,25 @@ +use serde::Deserialize; + +/// Region struct returned by region_list() +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct Region { + /// Region ID + pub id: i32, + /// Name of the region + pub name: String, + /// Price per gigabyte in region + pub price_per_gigabyte: f32, + /// Region 2 letter code + pub region_code: String, + /// Continent 2 letter code + pub continent_code: String, + /// Country 2 letter code + pub country_code: String, + /// Region latitude + pub latitude: f32, + /// Region longitude + pub longitude: f32, + /// ?? + pub allow_latency_routing: bool, +} diff --git a/src/edge_storage/endpoint.rs b/src/edge_storage/endpoint.rs new file mode 100644 index 0000000..555c0cc --- /dev/null +++ b/src/edge_storage/endpoint.rs @@ -0,0 +1,46 @@ +use url::Url; +use crate::error::Error; + +/// Endpoints for Edge Storage API +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Endpoint { + /// Uses https://storage.bunnycdn.com as endpoint + Frankfurt, + /// Uses https://uk.storage.bunnycdn.com as endpoint + London, + /// Uses https://ny.storage.bunnycdn.com as endpoint + NewYork, + /// Uses https://la.storage.bunnycdn.com as endpoint + LosAngeles, + /// Uses https://sg.storage.bunnycdn.com as endpoint + Singapore, + /// Uses https://se.storage.bunnycdn.com as endpoint + Stockholm, + /// Uses https://br.storage.bunnycdn.com as endpoint + SaoPaulo, + /// Uses https://jh.storage.bunnycdn.com as endpoint + Johannesburg, + /// Uses https://syd.storage.bunnycdn.com as endpoint + Sydney, + /// Lets you input a custom endpoint, in case bunny adds a new one and this crate isnt up-to-date, has to be a valid URL with http(s) in front + Custom(String), +} + +impl TryInto for Endpoint { + type Error = Error; + + fn try_into(self) -> Result { + match self { + Endpoint::Frankfurt => Ok(Url::parse("https://storage.bunnycdn.com")?), + Endpoint::London => Ok(Url::parse("https://uk.storage.bunnycdn.com")?), + Endpoint::NewYork => Ok(Url::parse("https://ny.storage.bunnycdn.com")?), + Endpoint::LosAngeles => Ok(Url::parse("https://la.storage.bunnycdn.com")?), + Endpoint::Singapore => Ok(Url::parse("https://sg.storage.bunnycdn.com")?), + Endpoint::Stockholm => Ok(Url::parse("https://se.storage.bunnycdn.com")?), + Endpoint::SaoPaulo => Ok(Url::parse("https://br.storage.bunnycdn.com")?), + Endpoint::Johannesburg => Ok(Url::parse("https://jh.storage.bunnycdn.com")?), + Endpoint::Sydney => Ok(Url::parse("https://syd.storage.bunnycdn.com")?), + Endpoint::Custom(url) => Ok(Url::parse(&url)?), + } + } +} diff --git a/src/edge_storage/list_file.rs b/src/edge_storage/list_file.rs new file mode 100644 index 0000000..b35fd2f --- /dev/null +++ b/src/edge_storage/list_file.rs @@ -0,0 +1,37 @@ +use serde::Deserialize; + +/// File information returned by list +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct ListFile { + /// ?? + pub guid: String, + /// Name of the storage zone the object is in + pub storage_zone_name: String, + /// Path to object + pub path: String, + /// Object name + pub object_name: String, + /// Length of the object in bytes + pub length: u32, + /// When the object was last modified + pub last_changed: String, + /// ?? + pub server_id: u32, + /// ?? + pub array_number: u32, + /// If the object is a directory + pub is_directory: bool, + /// ?? + pub user_id: String, + /// Object content type + pub content_type: String, + /// When the object was created + pub date_created: String, + /// ID of the storage zone the object is in + pub storage_zone_id: u32, + /// File checksum on server + pub checksum: String, + /// Zones the object is replicated to + pub replicated_zones: String, +} diff --git a/src/edge_storage.rs b/src/edge_storage/mod.rs similarity index 59% rename from src/edge_storage.rs rename to src/edge_storage/mod.rs index 93a70a9..69e9131 100644 --- a/src/edge_storage.rs +++ b/src/edge_storage/mod.rs @@ -2,131 +2,52 @@ //! //! Contains enums, structs and functions for the Bunny Edge Storage API -use crate::Error; +use crate::error::Error; use bytes::Bytes; use reqwest::{header::{HeaderMap, HeaderValue}, Client}; -use serde::Deserialize; use url::Url; -/// Endpoints for Edge Storage API -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Endpoint { - /// Uses https://storage.bunnycdn.com as endpoint - Frankfurt, - /// Uses https://uk.storage.bunnycdn.com as endpoint - London, - /// Uses https://ny.storage.bunnycdn.com as endpoint - NewYork, - /// Uses https://la.storage.bunnycdn.com as endpoint - LosAngeles, - /// Uses https://sg.storage.bunnycdn.com as endpoint - Singapore, - /// Uses https://se.storage.bunnycdn.com as endpoint - Stockholm, - /// Uses https://br.storage.bunnycdn.com as endpoint - SaoPaulo, - /// Uses https://jh.storage.bunnycdn.com as endpoint - Johannesburg, - /// Uses https://syd.storage.bunnycdn.com as endpoint - Sydney, - /// Lets you input a custom endpoint, in case bunny adds a new one and this crate isnt up-to-date, has to be a valid URL with http(s) in front - Custom(String), -} - -impl TryInto for Endpoint { - type Error = Error; - - fn try_into(self) -> Result { - match self { - Endpoint::Frankfurt => Ok(Url::parse("https://storage.bunnycdn.com")?), - Endpoint::London => Ok(Url::parse("https://uk.storage.bunnycdn.com")?), - Endpoint::NewYork => Ok(Url::parse("https://ny.storage.bunnycdn.com")?), - Endpoint::LosAngeles => Ok(Url::parse("https://la.storage.bunnycdn.com")?), - Endpoint::Singapore => Ok(Url::parse("https://sg.storage.bunnycdn.com")?), - Endpoint::Stockholm => Ok(Url::parse("https://se.storage.bunnycdn.com")?), - Endpoint::SaoPaulo => Ok(Url::parse("https://br.storage.bunnycdn.com")?), - Endpoint::Johannesburg => Ok(Url::parse("https://jh.storage.bunnycdn.com")?), - Endpoint::Sydney => Ok(Url::parse("https://syd.storage.bunnycdn.com")?), - Endpoint::Custom(url) => Ok(Url::parse(&url)?), - } - } -} - -/// File information returned by list -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct ListFile { - /// ?? - pub guid: String, - /// Name of the storage zone the object is in - pub storage_zone_name: String, - /// Path to object - pub path: String, - /// Object name - pub object_name: String, - /// Length of the object in bytes - pub length: u32, - /// When the object was last modified - pub last_changed: String, - /// ?? - pub server_id: u32, - /// ?? - pub array_number: u32, - /// If the object is a directory - pub is_directory: bool, - /// ?? - pub user_id: String, - /// Object content type - pub content_type: String, - /// When the object was created - pub date_created: String, - /// ID of the storage zone the object is in - pub storage_zone_id: u32, - /// File checksum on server - pub checksum: String, - /// Zones the object is replicated to - pub replicated_zones: String, -} +mod endpoint; +pub use endpoint::Endpoint; +mod list_file; +pub use list_file::ListFile; /// Edge Storage API for bunny #[derive(Debug, Clone)] -pub struct Storage { +pub struct EdgeStorageClient { pub(crate) url: Url, pub(crate) reqwest: Client, } -impl<'a> Storage { - /// Sets endpoint and storage zone used by Edge Storage API +impl<'a> EdgeStorageClient { + /// Creates a new EdgeStorageClient using the supplied `api_key` /// /// ``` - /// use bunny_api_tokio::{Client, error::Error, edge_storage::Endpoint}; + /// use bunny_api_tokio::{EdgeStorageClient, error::Error, edge_storage::Endpoint}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { - /// // API key here can be left as "" if you never plan on using anything from the bunny.net api - /// let mut client = Client::new("api_key").await?; - /// - /// // Requires own API key to use - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// /// Ok(()) /// } /// ``` - pub async fn init, T1: AsRef>( - &mut self, - api_key: T, - endpoint: Endpoint, - storage_zone: T1, - ) -> Result<(), Error> { + pub async fn new, T1: AsRef>(api_key: T, endpoint: Endpoint, storage_zone: T1) -> Result { let mut headers = HeaderMap::new(); headers.append("AccessKey", HeaderValue::from_str(api_key.as_ref())?); + headers.append("accept", HeaderValue::from_str("application/json")?); + + let reqwest = Client::builder().default_headers(headers).build()?; - self.reqwest = Client::builder().default_headers(headers).build()?; let endpoint: Url = endpoint.try_into()?; let storage_zone = String::from("/") + storage_zone.as_ref() + "/"; - self.url = endpoint.join(&storage_zone)?; - Ok(()) + let url = endpoint.join(&storage_zone)?; + + Ok(Self { + url, + reqwest, + }) } /// Uploads a file to the Storage Zone diff --git a/src/lib.rs b/src/lib.rs index 7f2242f..6998c3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,53 +19,12 @@ //! ``` #![deny(missing_docs)] -use error::Error; -use reqwest::{ - Client as RClient, - header::{HeaderMap, HeaderValue}, -}; -use url::Url; - -pub mod bunny; +#[cfg(feature = "bunnynet")] +mod bunny; +#[cfg(feature = "bunnynet")] +pub use bunny::BunnyClient; +#[cfg(feature = "edge_storage")] pub mod edge_storage; +#[cfg(feature = "edge_storage")] +pub use edge_storage::EdgeStorageClient; pub mod error; - -/// API Client for bunny -#[derive(Debug, Clone)] -pub struct Client { - reqwest: RClient, - /// Used to interact with the Edge Storage API - pub storage: edge_storage::Storage, -} - -impl Client { - /// Creates a new Client using the supplied `api_key` - /// - /// ``` - /// use bunny_api_tokio::{Client, error::Error}; - /// - /// #[tokio::main] - /// async fn main() -> Result<(), Error> { - /// // Bunny.net api key - /// let mut client = Client::new("api_key").await?; - /// - /// Ok(()) - /// } - /// ``` - pub async fn new>(api_key: T) -> Result { - let mut headers = HeaderMap::new(); - headers.append("AccessKey", HeaderValue::from_str(api_key.as_ref())?); - headers.append("accept", HeaderValue::from_str("application/json")?); - - let reqwest = RClient::builder().default_headers(headers).build()?; - let storage_reqwest = RClient::new(); - - Ok(Self { - reqwest, - storage: edge_storage::Storage { - url: Url::parse("https://storage.bunnycdn.com").unwrap(), - reqwest: storage_reqwest, - }, - }) - } -} From ce50afa63548251d86a5384ab02ae433a1f8c8cb Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 25 Jun 2025 14:08:15 +0200 Subject: [PATCH 11/15] docs: update docs to reflect current code --- src/bunny/mod.rs | 20 ++++++++++---------- src/edge_storage/mod.rs | 40 ++++++++++++++++------------------------ src/lib.rs | 4 ++-- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/bunny/mod.rs b/src/bunny/mod.rs index 231e02a..3a373bc 100644 --- a/src/bunny/mod.rs +++ b/src/bunny/mod.rs @@ -1,4 +1,4 @@ -//! Contains structs, enums and implementations for the main bunny.net API +//! Contains structs, enums and implementations for the main Bunny.net API use url::Url; @@ -24,7 +24,7 @@ pub struct BunnyClient { } impl BunnyClient { - /// Creates a new BunnyClient using the supplied `api_key` + /// Creates a new Bunny.net API Client using the supplied `api_key` /// /// ``` /// use bunny_api_tokio::{BunnyClient, error::Error}; @@ -54,12 +54,12 @@ impl BunnyClient { /// Returns a list of countries and tax rates /// /// ``` - /// use bunny_api_tokio::{Client, error::Error}; + /// use bunny_api_tokio::{BunnyClient, error::Error}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// // Bunny.net api key - /// let mut client = Client::new("api_key").await?; + /// let mut client = BunnyClient::new("api_key").await?; /// /// let countries = client.get_country_list().await?; /// @@ -87,12 +87,12 @@ impl BunnyClient { /// Returns a list of API Keys /// /// ``` - /// use bunny_api_tokio::{Client, error::Error}; + /// use bunny_api_tokio::{BunnyClient, error::Error}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// // Bunny.net api key - /// let mut client = Client::new("api_key").await?; + /// let mut client = BunnyClient::new("api_key").await?; /// /// let api_keys = client.list_api_keys(1, 1000).await?; /// @@ -124,12 +124,12 @@ impl BunnyClient { /// Returns a list of Regions /// /// ``` - /// use bunny_api_tokio::{Client, error::Error}; + /// use bunny_api_tokio::{BunnyClient, error::Error}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// // Bunny.net api key - /// let mut client = Client::new("api_key").await?; + /// let mut client = BunnyClient::new("api_key").await?; /// /// let regions = client.region_list().await?; /// @@ -156,12 +156,12 @@ impl BunnyClient { /// Purges a URL from the cache /// /// ``` - /// use bunny_api_tokio::{Client, error::Error}; + /// use bunny_api_tokio::{BunnyClient, error::Error}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// // Bunny.net api key - /// let mut client = Client::new("api_key").await?; + /// let mut client = BunnyClient::new("api_key").await?; /// /// client.purge_url("https://url_to_purge.com".parse()?, false).await?; /// diff --git a/src/edge_storage/mod.rs b/src/edge_storage/mod.rs index 69e9131..adcd2c4 100644 --- a/src/edge_storage/mod.rs +++ b/src/edge_storage/mod.rs @@ -20,7 +20,7 @@ pub struct EdgeStorageClient { } impl<'a> EdgeStorageClient { - /// Creates a new EdgeStorageClient using the supplied `api_key` + /// Creates a new EdgeStorageClient /// /// ``` /// use bunny_api_tokio::{EdgeStorageClient, error::Error, edge_storage::Endpoint}; @@ -53,19 +53,17 @@ impl<'a> EdgeStorageClient { /// Uploads a file to the Storage Zone /// /// ``` - /// use bunny_api_tokio::{Client, error::Error, edge_storage::Endpoint}; + /// use bunny_api_tokio::{EdgeStorageClient, error::Error, edge_storage::Endpoint}; /// use tokio::fs; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { - /// let mut client = Client::new("api_key").await?; - /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; /// /// let file_bytes = fs::read("path/to/file.png").await.unwrap(); /// /// // Will put a file in STORAGE_ZONE/images/file.png - /// client.storage.upload("/images/file.png", file_bytes.into()).await?; + /// client.upload("/images/file.png", file_bytes.into()).await?; /// /// Ok(()) /// } @@ -91,18 +89,16 @@ impl<'a> EdgeStorageClient { /// Downloads a file from the Storage Zone /// /// ``` - /// use bunny_api_tokio::{Client, error::Error, edge_storage::Endpoint}; + /// use bunny_api_tokio::{EdgeStorageClient, error::Error, edge_storage::Endpoint}; /// use tokio::fs; /// use tokio::io::AsyncWriteExt; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { - /// let mut client = Client::new("api_key").await?; - /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; - /// + /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// /// // Will download the file STORAGE_ZONE/images/file.png - /// let contents = client.storage.download("/images/file.png").await?; + /// let contents = client.download("/images/file.png").await?; /// /// let mut file = fs::File::create("file.png").await.unwrap(); /// file.write_all(&contents).await.unwrap(); @@ -130,16 +126,14 @@ impl<'a> EdgeStorageClient { /// Deletes a file from the Storage Zone /// /// ``` - /// use bunny_api_tokio::{Client, error::Error, edge_storage::Endpoint}; + /// use bunny_api_tokio::{EdgeStorageClient, error::Error, edge_storage::Endpoint}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { - /// let mut client = Client::new("api_key").await?; - /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; - /// + /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// /// // Will delete the file STORAGE_ZONE/images/file.png - /// client.storage.delete("/images/file.png").await?; + /// client.delete("/images/file.png").await?; /// /// Ok(()) /// } @@ -163,16 +157,14 @@ impl<'a> EdgeStorageClient { /// Lists files on the Storage Zone /// /// ``` - /// use bunny_api_tokio::{Client, error::Error, edge_storage::Endpoint}; + /// use bunny_api_tokio::{EdgeStorageClient, error::Error, edge_storage::Endpoint}; /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { - /// let mut client = Client::new("api_key").await?; - /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; - /// + /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// /// // Will list the files in STORAGE_ZONE/images/ - /// let files = client.storage.list("/images/").await?; + /// let files = client.list("/images/").await?; /// /// println!("{:#?}", files); /// diff --git a/src/lib.rs b/src/lib.rs index 6998c3a..f20de92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,11 +8,11 @@ //! 2. Start coding //! //! ``` -//! use bunny_api_tokio::{Client, error::Error}; +//! use bunny_api_tokio::{BunnyClient, error::Error}; //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { -//! let mut client = Client::new("api_key").await?; +//! let mut client = BunnyClient::new("api_key").await?; //! //! Ok(()) //! } From 8412489373649f30359b045e98d4ef7fd39c8060 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 25 Jun 2025 14:08:27 +0200 Subject: [PATCH 12/15] make docs.rs build with all features --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 435b3c6..897928d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,6 @@ serde = { version = "1.0.219", features = ["derive"] } thiserror = "2.0.12" tokio = "1.45.1" url = { version = "2.5.4", features = ["serde"] } + +[package.metadata.docs.rs] +all-features = true From 57e76753f562ac408fb8d890bfd54121a1dc5e1c Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 25 Jun 2025 14:09:18 +0200 Subject: [PATCH 13/15] style: cargo fmt --- src/bunny/mod.rs | 4 +--- src/edge_storage/endpoint.rs | 2 +- src/edge_storage/mod.rs | 24 ++++++++++++++---------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/bunny/mod.rs b/src/bunny/mod.rs index 3a373bc..7146d57 100644 --- a/src/bunny/mod.rs +++ b/src/bunny/mod.rs @@ -44,9 +44,7 @@ impl BunnyClient { let reqwest = RClient::builder().default_headers(headers).build()?; - Ok(Self { - reqwest, - }) + Ok(Self { reqwest }) } // TODO: Following functions could probably use better naming, the names are currently derived from the titles on the API reference diff --git a/src/edge_storage/endpoint.rs b/src/edge_storage/endpoint.rs index 555c0cc..42b522b 100644 --- a/src/edge_storage/endpoint.rs +++ b/src/edge_storage/endpoint.rs @@ -1,5 +1,5 @@ -use url::Url; use crate::error::Error; +use url::Url; /// Endpoints for Edge Storage API #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/edge_storage/mod.rs b/src/edge_storage/mod.rs index adcd2c4..c3147ba 100644 --- a/src/edge_storage/mod.rs +++ b/src/edge_storage/mod.rs @@ -1,10 +1,13 @@ //! Edge Storage API -//! +//! //! Contains enums, structs and functions for the Bunny Edge Storage API use crate::error::Error; use bytes::Bytes; -use reqwest::{header::{HeaderMap, HeaderValue}, Client}; +use reqwest::{ + Client, + header::{HeaderMap, HeaderValue}, +}; use url::Url; mod endpoint; @@ -32,7 +35,11 @@ impl<'a> EdgeStorageClient { /// Ok(()) /// } /// ``` - pub async fn new, T1: AsRef>(api_key: T, endpoint: Endpoint, storage_zone: T1) -> Result { + pub async fn new, T1: AsRef>( + api_key: T, + endpoint: Endpoint, + storage_zone: T1, + ) -> Result { let mut headers = HeaderMap::new(); headers.append("AccessKey", HeaderValue::from_str(api_key.as_ref())?); headers.append("accept", HeaderValue::from_str("application/json")?); @@ -44,10 +51,7 @@ impl<'a> EdgeStorageClient { let url = endpoint.join(&storage_zone)?; - Ok(Self { - url, - reqwest, - }) + Ok(Self { url, reqwest }) } /// Uploads a file to the Storage Zone @@ -96,7 +100,7 @@ impl<'a> EdgeStorageClient { /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; - /// + /// /// // Will download the file STORAGE_ZONE/images/file.png /// let contents = client.download("/images/file.png").await?; /// @@ -131,7 +135,7 @@ impl<'a> EdgeStorageClient { /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; - /// + /// /// // Will delete the file STORAGE_ZONE/images/file.png /// client.delete("/images/file.png").await?; /// @@ -162,7 +166,7 @@ impl<'a> EdgeStorageClient { /// #[tokio::main] /// async fn main() -> Result<(), Error> { /// let mut client = EdgeStorageClient::new("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; - /// + /// /// // Will list the files in STORAGE_ZONE/images/ /// let files = client.list("/images/").await?; /// From c57ca25d782891262d1b6c2e4f9e7b9cc881b5e4 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 25 Jun 2025 14:16:07 +0200 Subject: [PATCH 14/15] fix: make error output more useful --- src/error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index d5f7d57..e94645e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,15 +4,15 @@ #[derive(thiserror::Error, Debug)] pub enum Error { /// Reqwest error - #[error("bleh")] + #[error(transparent)] Reqwest(#[from] reqwest::Error), /// Header contains non-ASCII characters - #[error("header contains non-ASCII characters")] + #[error(transparent)] InvalidHeader(#[from] reqwest::header::InvalidHeaderValue), /// URL Parse error - #[error("not a valid URL")] + #[error(transparent)] ParseError(#[from] url::ParseError), /// Authentication error From b5953c1fc6ea0b7e93af8f625645e68dd4a6157d Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 25 Jun 2025 14:24:21 +0200 Subject: [PATCH 15/15] fix: update version in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c47f0c3..9506383 100644 --- a/README.md +++ b/README.md @@ -19,5 +19,5 @@ Issues and PRs can be submitted on the [GitHub mirror](https://github.com/gorb-a Add to your `Cargo.toml`: ```toml [dependencies] -bunny-api-tokio = "0.2.0" +bunny-api-tokio = "0.4.0" ```