Skip to content

Commit

Permalink
refractor!: all book data in Book is now under book.details. Added ge…
Browse files Browse the repository at this point in the history
…t_id().
  • Loading branch information
THEGOLDENPRO committed Nov 17, 2023
1 parent be471e5 commit fc49422
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 81 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ image = "0.24.6"
serde_json = "1"
chrono = "0.4.31"
bytes = "1.5.0"
urlencoding = "2.1.3"

[dev-dependencies]
tokio = { version = "1.29.1", features = ["full"] }
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ More install instructions at [crates.io](https://crates.io/crates/aghpb).
## Examples
This is how you may retrieve a random anime girls holding programming books:
```rust
use tokio::fs;
use std::error::Error;

#[tokio::main]
Expand Down Expand Up @@ -68,4 +69,36 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
```

<br>

> [!INFO]
> NEW in v4.0
How to search for an anime girl holding a programming book.
```rust
use std::error::Error;

use tokio::fs;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let books = aghpb::search("tohru".into(), None, None).await?;

let book_data = &books[0]; // I'm selecting the first book just for this example.

println!("Name: {}", book_data.name);
println!("Category: {}", book_data.category);
println!("Commit Author: {}", book_data.commit_author);
println!("Commit URL: {}", book_data.commit_url);
println!("Date Added: {}", book_data.date_added);
println!("Search ID: {}", book_data.search_id);

let book = book_data.get_book().await?;

fs::write("./anime_girl.png", book.raw_bytes).await?;

Ok(())
}
```

Made using my API at 👉 https://api.devgoldy.xyz/aghpb/v1/
119 changes: 61 additions & 58 deletions src/book.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,68 @@
use std::collections::HashMap;
use std::{collections::HashMap, error::Error};

use bytes::Bytes;
use chrono::{DateTime, FixedOffset};
use image::DynamicImage;
use reqwest::header::HeaderMap;

pub struct Book {
use crate::get_id;

pub struct BookData {
pub name: String,
pub category: String,
pub date_added: DateTime<FixedOffset>,
pub search_id: String,
pub commit_url: String,
pub commit_author: String,
}

impl BookData {
/// Creates a book from a json dictionary.
pub fn from_json(book_dict: HashMap<String, String>) -> BookData {
let name = book_dict.get("name").expect(
"Failed acquiring book name from json!"
).to_owned();

let category = book_dict.get("category").expect(
"Failed acquiring book category from json!"
).to_owned();

let date_added = DateTime::parse_from_str(book_dict.get("date_added").expect(
"Failed acquiring book date added header!"
), "%Y-%m-%d %H:%M:%S%z").expect(
"Failed to convert book's date added header to date time object."
);

let search_id = book_dict.get("search_id").expect(
"Failed acquiring book search id header!"
).to_owned();

let commit_url = book_dict.get("commit_url").expect(
"Failed acquiring book commit url header!"
).to_owned();

let commit_author = book_dict.get("commit_author").expect(
"Failed acquiring book commit author header!"
).to_owned();

Self {
name,
category,
date_added,
search_id,
commit_url,
commit_author
}
}

// Get's the book from the api.
pub async fn get_book(&self) -> Result<Book, Box<dyn Error>> {
get_id(String::from(&self.search_id)).await
}
}

pub struct Book {
pub details: BookData,
pub raw_bytes: Bytes,
}

Expand Down Expand Up @@ -45,67 +96,19 @@ impl Book {
).to_str().expect("Failed converting book commit author to string.").to_owned();

Self {
name,
category,
date_added,
search_id,
commit_url,
commit_author,
details: BookData {
name,
category,
date_added,
search_id,
commit_url,
commit_author
},
raw_bytes: bytes
}
}

pub fn to_image(&self) -> DynamicImage {
image::load_from_memory(&self.raw_bytes).expect("Failed to convert bytes into dynamic image object.")
}
}


pub struct BookResult {
pub name: String,
pub category: String,
pub date_added: DateTime<FixedOffset>,
pub search_id: String,
pub commit_url: String,
pub commit_author: String,
}

impl BookResult {
/// Creates a book from a json dictionary.
pub fn from_json(book_dict: HashMap<String, String>) -> BookResult {
let name = book_dict.get("name").expect(
"Failed acquiring book name from json!"
).to_owned();

let category = book_dict.get("category").expect(
"Failed acquiring book category from json!"
).to_owned();

let date_added = DateTime::parse_from_str(book_dict.get("date_added").expect(
"Failed acquiring book date added header!"
), "%Y-%m-%d %H:%M:%S%z").expect(
"Failed to convert book's date added header to date time object."
);

let search_id = book_dict.get("search_id").expect(
"Failed acquiring book search id header!"
).to_owned();

let commit_url = book_dict.get("commit_url").expect(
"Failed acquiring book commit url header!"
).to_owned();

let commit_author = book_dict.get("commit_author").expect(
"Failed acquiring book commit author header!"
).to_owned();

Self {
name,
category,
date_added,
search_id,
commit_url,
commit_author
}
}
}
57 changes: 39 additions & 18 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{collections::HashMap, error::Error, fmt};

use crate::book::{Book, BookResult};
use reqwest::Response;
use urlencoding::encode;

use crate::book::{Book, BookData};

#[derive(Clone, Debug)]
pub struct Client {
Expand Down Expand Up @@ -42,20 +45,7 @@ impl Client {

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(Book::from_response(headers, bytes))
} else {
let error_json: HashMap<String, String> = serde_json::from_str(&response.text().await?).unwrap();
Err(
AGHPBError {
error: error_json.get("error").unwrap().to_string(),
message: error_json.get("message").unwrap().to_string()
}.into()
)
}
get_book_or_error(response).await

}

Expand All @@ -72,7 +62,7 @@ impl Client {
/// Allows you to search for anime girls holding programming books.
///
/// Uses the ``/v1/search`` endpoint.
pub async fn search(&self, query: String, category: Option<String>, limit: Option<u8>) -> Result<Vec<BookResult>, reqwest::Error> {
pub async fn search(&self, query: String, category: Option<String>, limit: Option<u8>) -> Result<Vec<BookData>, reqwest::Error> {
let mut queries: Vec<(String, String)> = Vec::new();
queries.push(("query".into(), query));

Expand All @@ -87,14 +77,45 @@ impl Client {
let res = self.client.get(self.api_url.clone() + "/v1/search").query(&queries).send().await?;
let json: Vec<HashMap<String, String>> = serde_json::from_str(&res.text().await?).expect("Failed to deserialize json response!");

let mut books: Vec<BookResult> = Vec::new();
let mut books: Vec<BookData> = Vec::new();

for book_dict in json {
books.push(
BookResult::from_json(book_dict)
BookData::from_json(book_dict)
);
}

Ok(books)
}

/// Allows you to get a specific anime girls holding programming book by search ID.
///
/// Uses the ``/v1/get/id`` endpoint.
pub async fn get_id(&self, search_id: String) -> Result<Book, Box<dyn Error>> {

let response = self.client.get(
self.api_url.clone() + "/v1/get/id/" + encode(search_id.as_str()).to_string().as_str()
).send().await?;

get_book_or_error(response).await
}
}


/// Get's a book from a response or throws an API error.
async fn get_book_or_error(response: Response) -> Result<Book, Box<dyn Error>> {
if response.status().is_success() {
let headers = response.headers().to_owned();
let bytes = response.bytes().await?;

Ok(Book::from_response(headers, bytes))
} else {
let error_json: HashMap<String, String> = serde_json::from_str(&response.text().await?).unwrap();
Err(
AGHPBError {
error: error_json.get("error").unwrap().to_string(),
message: error_json.get("message").unwrap().to_string()
}.into()
)
}
}
51 changes: 46 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,35 @@
//! -------------
//!
//! # Examples
//! ### How to retrieve a random anime girl holding a programming book in Rust.
//!
//! ### How to search for an anime girl holding a programming book.
//! ```rust
//! use std::error::Error;
//!
//! use tokio::fs;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
//! let books = aghpb::search("tohru".into(), None, None).await?;
//!
//! let book_data = &books[0]; // I'm selecting the first book just for this example.
//!
//! println!("Name: {}", book_data.name);
//! println!("Category: {}", book_data.category);
//! println!("Commit Author: {}", book_data.commit_author);
//! println!("Commit URL: {}", book_data.commit_url);
//! println!("Date Added: {}", book_data.date_added);
//! println!("Search ID: {}", book_data.search_id);
//!
//! let book = book_data.get_book().await?;
//!
//! fs::write("./anime_girl.png", book.raw_bytes).await?;
//!
//! Ok(())
//! }
//! ```
//!
//! ### How to retrieve a random anime girl holding a programming book.
//! ```rust
//! use tokio::fs;
//! use std::error::Error;
Expand All @@ -14,9 +42,11 @@
//! async fn main() -> Result<(), Box<dyn Error>> {
//! let book = aghpb::random(None).await?;
//!
//! println!("Name: {}", book.name);
//! println!("Category: {}", book.category);
//! println!("Date added: {}", book.date_added);
//! let details = book.details;
//!
//! println!("Name: {}", details.name);
//! println!("Category: {}", details.category);
//! println!("Date added: {}", details.date_added);
//!
//! fs::write("./anime_girl.png", book.raw_bytes).await?;
//!
Expand Down Expand Up @@ -92,6 +122,17 @@ pub async fn categories() -> Result<Vec<String>, reqwest::Error> {
/// your own client.
///
/// Uses the ``/v1/search`` endpoint.
pub async fn search(query: String, category: Option<String>, limit: Option<u8>) -> Result<Vec<BookResult>, reqwest::Error> {
pub async fn search(query: String, category: Option<String>, limit: Option<u8>) -> Result<Vec<BookData>, reqwest::Error> {
get_client().search(query, category, limit).await
}

/// Allows you to get a specific anime girls holding programming book by search ID.
///
/// 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.
///
/// Uses the ``/v1/search`` endpoint.
pub async fn get_id(search_id: String) -> Result<Book, Box<dyn Error>> {
get_client().get_id(search_id).await
}

0 comments on commit fc49422

Please sign in to comment.