From f0cf712362d6bb846d1b14ece4721b844c54021a Mon Sep 17 00:00:00 2001 From: THEGOLDENPRO <66202304+THEGOLDENPRO@users.noreply.github.com> Date: Thu, 21 Sep 2023 01:36:41 +0100 Subject: [PATCH] fixes #3, performance and code improvements. --- Cargo.toml | 4 +++- README.md | 11 ++++++--- src/book.rs | 11 ++++++++- src/client.rs | 63 ++++++++++++++++++++++++++++++++++++++++----------- src/lib.rs | 22 ++++++++++-------- 5 files changed, 83 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1249e9..476d744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,14 @@ readme = "README.md" documentation = "https://docs.rs/aghpb" repository = "https://github.com/THEGOLDENPRO/aghpb.rs" edition = "2021" -version = "1.2.0" +version = "1.3.0" [dependencies] reqwest = "0.11.18" image = "0.24.6" serde_json = "1" +chrono = "0.4.31" +bytes = "1.5.0" [dev-dependencies] tokio = { version = "1.29.1", features = ["full"] } diff --git a/README.md b/README.md index 10a8b64..6475bca 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,11 @@
-> #### BTW, my first ever rust library. +> **Note** +> +> This is part of my [aghpb api](https://github.com/THEGOLDENPRO/aghpb_api) wrapper challenge where I attempt to write an api wrapper in every language possible. So yes expect spaghetti code as it will be my first time writing in these languages. Although I'm 100% open to improvements and corrections so feel free to contribute anything. +> +> **[Other languages I've done](https://github.com/THEGOLDENPRO/aghpb_api#-api-wrappers)** ## Install ```rust @@ -35,8 +39,9 @@ async fn main() -> Result<(), Box> { println!("Name: {}", book.name); println!("Category: {}", book.category); + println!("Date added: {}", book.date_added); - book.image.save("./anime_girl.png")?; + fs::write("./anime_girl.png", book.raw_bytes).await?; Ok(()) } @@ -64,4 +69,4 @@ async fn main() -> Result<(), Box> { } ``` -Made using my API at 👉 https://api.devgoldy.xyz/aghpb/v1/ +Made using my API at 👉 https://api.devgoldy.xyz/aghpb/v1/ \ No newline at end of file diff --git a/src/book.rs b/src/book.rs index cbaf5e7..620ffd0 100644 --- a/src/book.rs +++ b/src/book.rs @@ -1,7 +1,16 @@ +use bytes::Bytes; +use chrono::{DateTime, FixedOffset}; use image::DynamicImage; pub struct Book { pub name: String, pub category: String, - pub image: DynamicImage, + pub date_added: DateTime, + pub raw_bytes: Bytes, +} + +impl Book { + pub fn to_image(&self) -> DynamicImage { + image::load_from_memory(&self.raw_bytes).expect("Failed to convert bytes into image.") + } } \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index c1a164b..096b666 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,9 @@ +use std::collections::HashMap; + +use chrono::DateTime; +use reqwest::header::HeaderMap; +use bytes::Bytes; + use crate::book::Book; #[derive(Clone, Debug)] @@ -7,33 +13,36 @@ pub struct Client { } impl Client { - pub fn new(api_url: Option) -> Self { + pub fn new(api_url: Option<&str>) -> Self { Self { - api_url: api_url.unwrap_or("https://api.devgoldy.xyz/aghpb".to_owned()), + api_url: api_url.unwrap_or("https://api.devgoldy.xyz/aghpb").to_string(), client: reqwest::Client::new() } } /// Asynchronously grabs a random anime girl holding a programming book. /// + /// WARNING: Will panic on incorrect category. + /// /// Uses the ``/v1/random`` endpoint. pub async fn random(&self, category: Option<&str>) -> Result { - let mut base_url = self.api_url.clone(); - - base_url.push_str("/v1/random"); + let mut queries: Vec<(&str, &str)> = Vec::new(); if let Some(category) = category { - base_url.push_str(format!("?category={}", category).as_str()); + queries.push(("category", category)); } - let res = self.client.get(base_url).send().await?; - let headers = res.headers(); + let response = self.client.get(self.api_url.clone() + "/v1/random").query(&queries).send().await?; + + if response.status().is_success() { + let headers = response.headers().to_owned(); + let bytes = response.bytes().await?; + + Ok(get_book(headers, bytes)) + } else { + Err(panic_on_api_error(&response.text().await?)) + } - Ok(Book { - name: headers.get("book-name").expect("Failed acquiring book name").to_str().expect("Failed converting book name to string").to_owned(), - category: category.unwrap_or(headers.get("book-category").expect("Failed acquiring book category").to_str().expect("Failed converting book category to string")).to_owned(), - image: image::load_from_memory(&res.bytes().await?).expect("Failed to load image") - }) } /// Asynchronously grabs list of available categories. @@ -50,3 +59,31 @@ impl Client { Ok(json) } } + + +fn get_book(headers: HeaderMap, bytes: Bytes) -> Book { + let name = headers.get("book-name").expect("Failed acquiring book name header!").to_str().expect( + "Failed converting book name to string." + ).to_owned(); + + let category = headers.get("book-category").expect("Failed acquiring book category header!").to_str().expect( + "Failed converting book category to string.").to_owned(); + + let date_added = DateTime::parse_from_str(headers.get("book-date-added").expect( + "Failed acquiring book date added header!" + ).to_str().expect("Failed converting book date time to string."), "%Y-%m-%d %H:%M:%S%z").expect( + "Failed to convert book's date added header to date time object." + ); + + Book { + name, + category, + date_added, + raw_bytes: bytes + } +} + +fn panic_on_api_error(text: &String) -> reqwest::Error { + let error_json: HashMap = serde_json::from_str(text).unwrap(); + panic!("API Error: {:?}", error_json.get("message").unwrap()); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ca252f1..c3985d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ //! # Examples //! ### How to retrieve a random anime girl holding a programming book in Rust. //! ```rust +//! use tokio::fs; //! use std::error::Error; //! //! #[tokio::main] @@ -15,8 +16,9 @@ //! //! println!("Name: {}", book.name); //! println!("Category: {}", book.category); +//! println!("Date added: {}", book.date_added); //! -//! book.image.save("./anime_girl.png")?; +//! fs::write("./anime_girl.png", book.raw_bytes).await?; //! //! Ok(()) //! } @@ -56,31 +58,31 @@ fn get_client() -> Client { random_client.clone() } else { let new_client = Client::new(None); - _CLIENT.set(new_client.clone()).expect("Failed to initialize client"); + let _ = _CLIENT.set(new_client.clone()); new_client } } /// Asynchronously grabs a random anime girl holding a programming book. /// -/// NOTE: this uses the global client! +/// WARNING: Will panic on incorrect category. +/// +/// NOTE: Use aghpb::Client for multiple requests. This uses a global client! /// If you want more customization/speed it maybe preferable to make -/// your own client. +/// your own client. /// /// Uses the ``/v1/random`` endpoint. pub async fn random(category: Option<&str>) -> Result { - let client = get_client(); - client.random(category).await + get_client().random(category).await } /// Asynchronously grabs list of available categories. /// -/// NOTE: this uses the global client! +/// NOTE: Use aghpb::Client for multiple requests. This uses a global client! /// If you want more customization/speed it maybe preferable to make -/// your own client. +/// your own client. /// /// Uses the ``/v1/categories`` endpoint. pub async fn categories() -> Result, reqwest::Error> { - let client = get_client(); - client.categories().await + get_client().categories().await } \ No newline at end of file