quiverquant 0.2.2

A crate for accessing the QuiverQuant API.
Documentation
//! # quiverquant.rs
//! [QuiverQuant API][qq-api] Wrapper for Rust.
//!
//! Here is a simple example of using the `Client`
//!
//! ```rust
//! use quiverquant::Client;
//! #[tokio::main]
//! async fn main() {
//!     let client = Client::new("<api-key>");
//!     let recent_lobbying = client.lobbying().await;
//! }
//!
//! ```
//!
//! See the `models` module for more info on the types related to the individual DataSets
//!
//! [qq-api]: https://api.quiverquant.com/

use log::{error, trace};
use reqwest::{header::HeaderMap, Client as HttpClient};

mod error;
pub mod models;

#[doc(inline)]
pub use error::ClientError;

/// Default BASE URL used when no url is provided by the user.
const QUIVERQUANT_BASE_URL: &str = "https://api.quiverquant.com";

/// Market Ticker
type Ticker = String;

/// API Result
type Result<T> = std::result::Result<T, ClientError>;

/// QuiverQuant API client
pub struct Client {
    api_token: String,
    http: HttpClient,
    base_url: String,
}

impl Client {
    /// Create a new API Client using your API token
    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(),
        }
    }

    /// Create a new API Client with your API Token and a specific base url
    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)
    }

    /// Returns all of the stock transactions by members of U.S. Congress involving the given
    /// ticker
    pub async fn congress_trades(&self) -> Result<models::CongressTrading> {
        self.base_request("/beta/live/congresstrading").await
    }

    /// Returns the most recent transactions by members of U.S. Congress
    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
    }

    /// Returns the most recent transactions by U.S. Senators
    pub async fn senate_trades(&self) -> Result<models::SenateTrading> {
        self.base_request("/beta/live/senatetrading").await
    }

    /// Returns all of the stock transactions by U.S. Senators involving the given ticker
    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
    }

    /// Returns the most recent transactions by U.S. Representatives
    pub async fn house_trades(&self) -> Result<models::HouseTrading> {
        self.base_request("/beta/live/housetrading").await
    }

    /// Returns all of the stock transactions by U.S. Representatives involving the given ticker
    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
    }

    /// Returns last quarter's government contract amounts for all companies
    pub async fn gov_contracts(&self) -> Result<models::GovContracts> {
        self.base_request("/beta/live/govcontracts").await
    }

    /// Returns historical quarterly government contracts amounts for the given ticker
    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
    }

    /// Returns the most recent lobbying spending instances across all companies.
    pub async fn lobbying(&self) -> Result<models::Lobbying> {
        self.base_request("/beta/live/lobbying").await
    }

    /// Returns all lobbying spending instances for the given ticker.
    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
    }
}