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