use std::{collections::HashMap, error::Error, fmt};
use reqwest::Response;
use urlencoding::encode;
use crate::book::{Book, BookData};
#[derive(Clone, Debug)]
pub struct Client {
pub api_url: String,
client: reqwest::Client
}
#[derive(Debug)]
struct AGHPBError {
error: String,
message: String
}
impl Error for AGHPBError {}
impl fmt::Display for AGHPBError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "API Error: [{}] {}", self.error, self.message)
}
}
impl Client {
pub fn new(api_url: Option<&str>) -> Self {
Self {
api_url: api_url.unwrap_or("https://api.devgoldy.xyz/aghpb").to_string(),
client: reqwest::Client::new()
}
}
pub async fn random(&self, category: Option<String>) -> Result<Book, Box<dyn Error>> {
let mut queries: Vec<(String, String)> = Vec::new();
if let Some(category) = category {
queries.push(("category".into(), category));
}
let response = self.client.get(self.api_url.clone() + "/v1/random").query(&queries).send().await?;
get_book_or_error(response).await
}
pub async fn categories(&self) -> Result<Vec<String>, reqwest::Error> {
let res = self.client.get(self.api_url.clone() + "/v1/categories").send().await?;
let json: Vec<String> = serde_json::from_str(&res.text().await?).expect("Failed to deserialize json response!");
Ok(json)
}
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));
if let Some(category) = category {
queries.push(("category".into(), category));
}
if let Some(limit) = limit {
queries.push(("limit".into(), limit.to_string()));
}
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<BookData> = Vec::new();
for book_dict in json {
books.push(
BookData::from_json(book_dict)
);
}
Ok(books)
}
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
}
}
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 {
match serde_json::from_str::<HashMap<String, String>>(&response.text().await?) {
Ok(error_json) => Err(
AGHPBError {
error: error_json.get("error").unwrap().to_string(),
message: error_json.get("message").unwrap().to_string()
}.into()
),
Err(_) => Err("Failed to phrase error json! I think the API is broken. 💀".into())
}
}
}