Compare commits
No commits in common. "main" and "v0.2.1" have entirely different histories.
7 changed files with 22 additions and 273 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bunny-api-tokio"
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
edition = "2024"
|
||||
authors = ["Radical <radical@radical.fun>"]
|
||||
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"
|
||||
|
|
|
@ -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.
|
||||
|
|
218
src/bunny/mod.rs
218
src/bunny/mod.rs
|
@ -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<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// Pagination struct used by Bunny.net API
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Pagination<T> {
|
||||
/// Vector of type T
|
||||
pub items: Vec<T>,
|
||||
/// 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<Vec<Country>, 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<Pagination<ApiKey>, 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<Vec<Region>, 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?)
|
||||
}
|
||||
}
|
|
@ -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<Client>,
|
||||
}
|
||||
|
||||
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<T: AsRef<str>, T1: AsRef<str>>(
|
||||
pub fn init<T: AsRef<str>>(
|
||||
&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(())
|
||||
/// }
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
11
src/lib.rs
11
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<T: AsRef<str>>(api_key: T) -> Result<Self, Error> {
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue