use reqwest::Client as HttpClient;
use crate::types::*;
#[derive(Debug)]
pub enum Error {
Http(reqwest::Error),
Api { status: u16, message: String },
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Http(e) => write!(f, "readwhitepaper: HTTP error: {e}"),
Error::Api { status, message } => {
write!(f, "readwhitepaper: API error {status}: {message}")
}
}
}
}
impl std::error::Error for Error {}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::Http(err)
}
}
pub struct ReadWhitepaperClient {
base_url: String,
language: String,
http: HttpClient,
}
impl Default for ReadWhitepaperClient {
fn default() -> Self {
Self::new()
}
}
impl ReadWhitepaperClient {
pub fn new() -> Self {
Self {
base_url: "https://readwhitepaper.com/api".to_string(),
language: "en".to_string(),
http: HttpClient::builder()
.user_agent("readwhitepaper-rs/0.1.0")
.timeout(std::time::Duration::from_secs(30))
.build()
.expect("failed to build HTTP client"),
}
}
pub fn with_base_url(mut self, base_url: &str) -> Self {
self.base_url = base_url.trim_end_matches('/').to_string();
self
}
pub fn with_language(mut self, language: &str) -> Self {
self.language = language.to_string();
self
}
async fn get<T: serde::de::DeserializeOwned>(
&self,
path: &str,
params: &[(&str, &str)],
) -> Result<T, Error> {
let url = format!("{}{}", self.base_url, path);
let resp = self.http.get(&url).query(params).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let message = resp.text().await.unwrap_or_default();
return Err(Error::Api {
status,
message: message.chars().take(200).collect(),
});
}
Ok(resp.json().await?)
}
fn lang<'a>(&'a self, language: Option<&'a str>) -> &'a str {
language.unwrap_or(&self.language)
}
pub async fn list_whitepapers(
&self,
limit: Option<i32>,
page: Option<i32>,
language: Option<&str>,
) -> Result<PaginatedResponse<Whitepaper>, Error> {
let lang = self.lang(language).to_string();
let limit_s = limit.map(|l| l.to_string());
let page_s = page.map(|p| p.to_string());
let mut params: Vec<(&str, &str)> = vec![("language", &lang)];
if let Some(ref l) = limit_s {
params.push(("limit", l));
}
if let Some(ref p) = page_s {
params.push(("page", p));
}
self.get("/whitepapers/", ¶ms).await
}
pub async fn get_whitepaper(
&self,
slug: &str,
language: Option<&str>,
) -> Result<WhitepaperDetail, Error> {
let lang = self.lang(language).to_string();
let path = format!("/whitepapers/{}/", slug);
self.get(&path, &[("language", &lang)]).await
}
pub async fn get_sections(
&self,
slug: &str,
language: Option<&str>,
) -> Result<Vec<Section>, Error> {
let lang = self.lang(language).to_string();
let path = format!("/whitepapers/{}/sections/", slug);
self.get(&path, &[("language", &lang)]).await
}
pub async fn get_toc(
&self,
slug: &str,
language: Option<&str>,
) -> Result<Vec<TocEntry>, Error> {
let lang = self.lang(language).to_string();
let path = format!("/whitepapers/{}/toc/", slug);
let resp: TocResponse = self.get(&path, &[("language", &lang)]).await?;
Ok(resp.toc)
}
pub async fn get_coverage(&self, slug: &str) -> Result<Coverage, Error> {
let path = format!("/whitepapers/{}/coverage/", slug);
self.get(&path, &[]).await
}
pub async fn list_glossary(
&self,
limit: Option<i32>,
page: Option<i32>,
language: Option<&str>,
) -> Result<PaginatedResponse<GlossaryTerm>, Error> {
let lang = self.lang(language).to_string();
let limit_s = limit.map(|l| l.to_string());
let page_s = page.map(|p| p.to_string());
let mut params: Vec<(&str, &str)> = vec![("language", &lang)];
if let Some(ref l) = limit_s {
params.push(("limit", l));
}
if let Some(ref p) = page_s {
params.push(("page", p));
}
self.get("/glossary/", ¶ms).await
}
pub async fn get_glossary_term(
&self,
slug: &str,
language: Option<&str>,
) -> Result<GlossaryTerm, Error> {
let lang = self.lang(language).to_string();
let path = format!("/glossary/{}/", slug);
self.get(&path, &[("language", &lang)]).await
}
pub async fn search(
&self,
query: &str,
slug: Option<&str>,
language: Option<&str>,
) -> Result<Vec<SearchResult>, Error> {
let lang = self.lang(language).to_string();
let mut params: Vec<(&str, &str)> = vec![("q", query), ("language", &lang)];
if let Some(s) = slug {
params.push(("slug", s));
}
let resp: SearchResponse = self.get("/search/", ¶ms).await?;
Ok(resp.results)
}
pub async fn get_stats(&self) -> Result<Stats, Error> {
self.get("/stats/", &[]).await
}
pub async fn get_languages(&self) -> Result<Vec<Language>, Error> {
let resp: LanguagesResponse = self.get("/languages/", &[]).await?;
Ok(resp.results)
}
pub async fn get_graph(&self) -> Result<GraphData, Error> {
self.get("/graph/", &[]).await
}
}