Skip to content

Commit

Permalink
fixes #3, performance and code improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
THEGOLDENPRO committed Sep 21, 2023
1 parent 7522fd3 commit f0cf712
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 28 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

<br>

> #### 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
Expand All @@ -35,8 +39,9 @@ async fn main() -> Result<(), Box<dyn Error>> {

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(())
}
Expand Down Expand Up @@ -64,4 +69,4 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
```

Made using my API at 👉 https://api.devgoldy.xyz/aghpb/v1/
Made using my API at 👉 https://api.devgoldy.xyz/aghpb/v1/
11 changes: 10 additions & 1 deletion src/book.rs
Original file line number Diff line number Diff line change
@@ -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<FixedOffset>,
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.")
}
}
63 changes: 50 additions & 13 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -7,33 +13,36 @@ pub struct Client {
}

impl Client {
pub fn new(api_url: Option<String>) -> 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<Book, reqwest::Error> {
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.
Expand All @@ -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<String, String> = serde_json::from_str(text).unwrap();
panic!("API Error: {:?}", error_json.get("message").unwrap());
}
22 changes: 12 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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(())
//! }
Expand Down Expand Up @@ -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<Book, reqwest::Error> {
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<Vec<String>, reqwest::Error> {
let client = get_client();
client.categories().await
get_client().categories().await
}

0 comments on commit f0cf712

Please sign in to comment.