use log::{error, trace};
use reqwest::{header::HeaderMap, Client as HttpClient};
mod error;
pub mod models;
#[doc(inline)]
pub use error::ClientError;
const QUIVERQUANT_BASE_URL: &str = "https://api.quiverquant.com";
type Ticker = String;
type Result<T> = std::result::Result<T, ClientError>;
pub struct Client {
api_token: String,
http: HttpClient,
base_url: String,
}
impl Client {
pub fn new<A: Into<String>>(api_token: A) -> Self {
Self {
api_token: api_token.into(),
http: HttpClient::default(),
base_url: QUIVERQUANT_BASE_URL.to_string(),
}
}
pub fn with_base_url<A: Into<String>, B: Into<String>>(api_token: A, base_url: B) -> Self {
Self {
api_token: api_token.into(),
http: HttpClient::default(),
base_url: base_url.into(),
}
}
fn get_authed_header(&self) -> Result<HeaderMap> {
let mut map = HeaderMap::new();
map.insert(
"Authorization",
format!("Token {}", &self.api_token).parse()?,
);
trace!("Building Authorized header map.");
Ok(map)
}
async fn base_request<T: serde::de::DeserializeOwned, U: Into<String>>(
&self,
url: U,
) -> Result<T> {
let uri_string = format!("{}{}", &self.base_url, &url.into());
trace!("Making request to {}.", uri_string);
let response = self
.http
.get(&uri_string)
.headers(self.get_authed_header()?)
.send()
.await?;
trace!("Got response from QuiverQuant API");
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
error!("API Token may be invalid, or you may be attempting to access a dataset you do not have access to.");
return Err(ClientError::Unauthorized);
}
if response.status() == reqwest::StatusCode::INTERNAL_SERVER_ERROR {
error!("QuiverQuant's API Returned a 500 - no additional info was provided");
return Err(ClientError::InternalServerError);
}
trace!("Deserializing JSON body");
let json = response.json().await?;
trace!("Deserialized Body");
Ok(json)
}
pub async fn congress_trades(&self) -> Result<models::CongressTrading> {
self.base_request("/beta/live/congresstrading").await
}
pub async fn congress_trades_by_ticker<T: Into<Ticker>>(
&self,
ticker: T,
) -> Result<models::CongressTrading> {
self.base_request(format!(
"/beta/historical/congresstrading/{}",
ticker.into()
))
.await
}
pub async fn senate_trades(&self) -> Result<models::SenateTrading> {
self.base_request("/beta/live/senatetrading").await
}
pub async fn senate_trades_by_ticker<T: Into<Ticker>>(
&self,
ticker: T,
) -> Result<models::SenateTrading> {
self.base_request(format!("/beta/historical/senatetrading/{}", ticker.into()))
.await
}
pub async fn house_trades(&self) -> Result<models::HouseTrading> {
self.base_request("/beta/live/housetrading").await
}
pub async fn house_trades_by_ticker<T: Into<Ticker>>(
&self,
ticker: T,
) -> Result<models::HouseTrading> {
self.base_request(format!("/beta/historical/housetrading/{}", ticker.into()))
.await
}
pub async fn gov_contracts(&self) -> Result<models::GovContracts> {
self.base_request("/beta/live/govcontracts").await
}
pub async fn gov_contracts_by_ticker<T: Into<Ticker>>(
&self,
ticker: T,
) -> Result<models::GovContracts> {
self.base_request(format!("/beta/historical/govcontracts/{}", ticker.into()))
.await
}
pub async fn lobbying(&self) -> Result<models::Lobbying> {
self.base_request("/beta/live/lobbying").await
}
pub async fn lobbying_by_ticker<T: Into<Ticker>>(&self, ticker: T) -> Result<models::Lobbying> {
self.base_request(format!("/beta/historical/lobbying/{}", ticker.into()))
.await
}
}