diff --git a/Cargo.lock b/Cargo.lock index 14812c9..c03a006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,7 +64,7 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bunny-api-tokio" -version = "0.3.0" +version = "0.2.1" dependencies = [ "bytes", "log", @@ -1075,21 +1075,9 @@ 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" @@ -1196,7 +1184,6 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e293b03..8f43368 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bunny-api-tokio" -version = "0.3.0" +version = "0.2.1" edition = "2024" authors = ["Radical "] license = "MIT" @@ -14,8 +14,6 @@ keywords = [ "tokio", ] -[dev-dependencies] -tokio = { version = "1.45.0", features = ["fs", "rt", "rt-multi-thread", "macros"] } [dependencies] bytes = "1.10.1" @@ -24,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 = { version = "2.5.4", features = ["serde"] } +url = "2.5.4" diff --git a/README.md b/README.md index c47f0c3..8a76eba 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,6 @@ 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. diff --git a/src/bunny/mod.rs b/src/bunny/mod.rs deleted file mode 100644 index 7d2c333..0000000 --- a/src/bunny/mod.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! 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_country_list().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".parse()?, 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/edge_storage.rs b/src/edge_storage.rs index 93a70a9..da14eb1 100644 --- a/src/edge_storage.rs +++ b/src/edge_storage.rs @@ -2,9 +2,11 @@ //! //! Contains enums, structs and functions for the Bunny Edge Storage API +use std::sync::Arc; + use crate::Error; use bytes::Bytes; -use reqwest::{header::{HeaderMap, HeaderValue}, Client}; +use reqwest::Client; use serde::Deserialize; use url::Url; @@ -92,7 +94,7 @@ pub struct ListFile { #[derive(Debug, Clone)] pub struct Storage { pub(crate) url: Url, - pub(crate) reqwest: Client, + pub(crate) reqwest: Arc, } impl<'a> Storage { @@ -103,25 +105,18 @@ 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?; /// - /// // Requires own API key to use - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); /// /// Ok(()) /// } /// ``` - pub async fn init, T1: AsRef>( + pub fn init>( &mut self, - api_key: T, endpoint: Endpoint, - storage_zone: T1, + storage_zone: T, ) -> 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() + "/"; @@ -139,12 +134,12 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); /// - /// let file_bytes = fs::read("path/to/file.png").await.unwrap(); + /// let file_bytes = fs::read("path/to/file.png").await?; /// /// // Will put a file in STORAGE_ZONE/images/file.png - /// client.storage.upload("/images/file.png", file_bytes.into()).await?; + /// client.storage.upload("/images/file.png", file_bytes).await?; /// /// Ok(()) /// } @@ -178,13 +173,13 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); /// /// // 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.unwrap(); - /// file.write_all(&contents).await.unwrap(); + /// let mut file = fs::File::create("file.png").await?; + /// file.write_all(contents).await?; /// /// Ok(()) /// } @@ -215,7 +210,7 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); /// /// // Will delete the file STORAGE_ZONE/images/file.png /// client.storage.delete("/images/file.png").await?; @@ -248,12 +243,12 @@ impl<'a> Storage { /// async fn main() -> Result<(), Error> { /// let mut client = Client::new("api_key").await?; /// - /// client.storage.init("storage_zone_api_key", Endpoint::Frankfurt, "MyStorageZone").await?; + /// client.storage.init(Endpoint::Frankfurt, "MyStorageZone"); /// /// // Will list the files in STORAGE_ZONE/images/ /// let files = client.storage.list("/images/").await?; /// - /// println!("{:#?}", files); + /// println!("{:#?}", files) /// /// Ok(()) /// } diff --git a/src/error.rs b/src/error.rs index d5f7d57..4b012bd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,8 +26,4 @@ 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 7f2242f..bd1e383 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,16 +24,15 @@ use reqwest::{ Client as RClient, header::{HeaderMap, HeaderValue}, }; +use std::sync::Arc; use url::Url; -pub mod bunny; pub mod edge_storage; 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, } @@ -46,7 +45,6 @@ impl Client { /// /// #[tokio::main] /// async fn main() -> Result<(), Error> { - /// // Bunny.net api key /// let mut client = Client::new("api_key").await?; /// /// Ok(()) @@ -55,16 +53,13 @@ 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 = RClient::builder().default_headers(headers).build()?; - let storage_reqwest = RClient::new(); + let reqwest = Arc::new(RClient::builder().default_headers(headers).build()?); Ok(Self { - reqwest, storage: edge_storage::Storage { url: Url::parse("https://storage.bunnycdn.com").unwrap(), - reqwest: storage_reqwest, + reqwest, }, }) }