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;