#![warn(missing_docs)]
mod utils;
use std::fmt::Display;
use log::debug;
use reqwest::{Client, Response};
pub mod factor;
pub mod number;
pub use factor::Factor;
pub use number::Number;
pub use number::NumberStatus;
const ENDPOINT: &str = "http://factordb.com/api";
#[derive(Debug, Clone)]
pub struct FactorDbClient {
client: Client,
}
impl FactorDbClient {
pub fn new() -> Self {
Self::with_client(Client::new())
}
pub fn with_client(client: Client) -> Self {
debug!("Creating async HTTP client");
Self { client }
}
pub async fn get<T: Display>(&self, number: T) -> Result<Number, FactorDbError> {
let response = self.fetch_response(number).await?;
let status = response.status();
if status.is_success() {
Ok(response.json().await.expect("Invalid JSON response"))
} else {
Err(FactorDbError::InvalidNumber)
}
}
pub async fn get_json<T: Display>(&self, number: T) -> Result<String, FactorDbError> {
let response = self.fetch_response(number).await?;
let status = response.status();
if status.is_success() {
Ok(response
.text()
.await
.expect("Unable to decode response body"))
} else {
Err(FactorDbError::InvalidNumber)
}
}
async fn fetch_response<T: Display>(&self, number: T) -> reqwest::Result<Response> {
let url = format!("{}?query={}", ENDPOINT, number);
debug!("Fetching API response from {}", url);
self.client.get(url).send().await
}
}
impl Default for FactorDbClient {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "blocking")]
#[derive(Debug, Clone)]
pub struct FactorDbBlockingClient {
client: reqwest::blocking::Client,
}
#[cfg(feature = "blocking")]
impl FactorDbBlockingClient {
pub fn new() -> Self {
Self::with_client(reqwest::blocking::Client::new())
}
pub fn with_client(client: reqwest::blocking::Client) -> Self {
debug!("Creating blocking HTTP client");
Self { client }
}
pub fn get<T: Display>(&self, number: T) -> Result<Number, FactorDbError> {
let response = self.fetch_response(number)?;
let status = response.status();
if status.is_success() {
Ok(response.json().expect("Invalid JSON response"))
} else {
Err(FactorDbError::InvalidNumber)
}
}
pub fn get_json<T: Display>(&self, number: T) -> Result<String, FactorDbError> {
let response = self.fetch_response(number)?;
let status = response.status();
if status.is_success() {
Ok(response.text().expect("Unable to decode response body"))
} else {
Err(FactorDbError::InvalidNumber)
}
}
fn fetch_response<T: Display>(
&self,
number: T,
) -> reqwest::Result<reqwest::blocking::Response> {
let url = format!("{}?query={}", ENDPOINT, number);
debug!("Fetching API response from {}", url);
self.client.get(url).send()
}
}
#[cfg(feature = "blocking")]
impl Default for FactorDbBlockingClient {
fn default() -> Self {
Self::new()
}
}
#[derive(thiserror::Error, Debug)]
pub enum FactorDbError {
#[error("Request error: {0}")]
RequestError(#[from] reqwest::Error),
#[error("Invalid number")]
InvalidNumber,
}
#[cfg(test)]
mod tests {
use num_bigint::BigInt;
use super::*;
#[tokio::test]
async fn test_two_factors() {
let client = FactorDbClient::new();
let result = client.get(15).await.unwrap();
assert_eq!(
vec![BigInt::from(3), BigInt::from(5)],
result.into_factors_flattened()
)
}
#[tokio::test]
async fn test_repeating_factors() {
let client = FactorDbClient::new();
let result = client.get(100).await.unwrap();
assert_eq!(
vec![
BigInt::from(2),
BigInt::from(2),
BigInt::from(5),
BigInt::from(5)
],
result.clone().into_factors_flattened()
);
assert_eq!(
vec![BigInt::from(2), BigInt::from(5)],
result.into_unique_factors()
);
}
#[tokio::test]
async fn test_prime() {
let client = FactorDbClient::new();
let result = client.get(17).await.unwrap();
let flatenned = result.clone().into_factors_flattened();
let unique = result.into_unique_factors();
assert_eq!(vec![BigInt::from(17)], flatenned);
assert_eq!(flatenned, unique);
}
#[tokio::test]
async fn test_invalid() {
let client = FactorDbClient::new();
let result = client.get("AAAAA").await;
assert!(result.is_err());
}
#[test]
#[cfg(feature = "blocking")]
fn test_two_factors_blocking() {
let client = FactorDbBlockingClient::new();
let result = client.get(15).unwrap();
assert_eq!(
vec![BigInt::from(3), BigInt::from(5)],
result.into_factors_flattened()
)
}
#[test]
#[cfg(feature = "blocking")]
fn test_repeating_factors_blocking() {
let client = FactorDbBlockingClient::new();
let result = client.get(100).unwrap();
assert_eq!(
vec![
BigInt::from(2),
BigInt::from(2),
BigInt::from(5),
BigInt::from(5)
],
result.clone().into_factors_flattened()
);
assert_eq!(
vec![BigInt::from(2), BigInt::from(5)],
result.into_unique_factors()
);
}
#[test]
#[cfg(feature = "blocking")]
fn test_prime_blocking() {
let client = FactorDbBlockingClient::new();
let result = client.get(17).unwrap();
let flatenned = result.clone().into_factors_flattened();
let unique = result.into_unique_factors();
assert_eq!(vec![BigInt::from(17)], flatenned);
assert_eq!(flatenned, unique);
}
#[test]
#[cfg(feature = "blocking")]
fn test_invalid_blocking() {
let client = FactorDbBlockingClient::new();
let result = client.get("AAAAA");
assert!(result.is_err());
}
}