use serde_json::{self, Value};
use thiserror::Error;
pub mod gurus;
pub mod stock;
pub use stock::*;
pub mod financials;
pub use financials::*;
pub mod keyratios;
pub use keyratios::*;
pub mod insiders;
pub use insiders::*;
pub mod portfolio;
pub use portfolio::*;
pub mod strnum;
pub mod hexnum;
#[derive(Error, Debug)]
pub enum GuruFocusError {
#[error("Request failure")]
RequestFailure(#[from] reqwest::Error),
}
pub struct GuruFocusConnector {
url: &'static str,
user_token: String,
}
impl GuruFocusConnector {
pub fn new(token: String) -> GuruFocusConnector {
GuruFocusConnector {
url: "https://api.gurufocus.com/public/user/",
user_token: token,
}
}
pub async fn get_financials(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/financials", stock);
self.send_request(args.as_str()).await
}
pub async fn get_key_ratios(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/keyratios", stock);
self.send_request(args.as_str()).await
}
pub async fn get_quotes(&self, stocks: &[&str]) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/quote", compact_list(stocks));
self.send_request(args.as_str()).await
}
pub async fn get_price_hist(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/price", stock);
self.send_request(args.as_str()).await
}
pub async fn get_unadj_price_hist(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/unadjusted_price", stock);
self.send_request(args.as_str()).await
}
pub async fn get_stock_summary(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/summary", stock);
self.send_request(args.as_str()).await
}
pub async fn get_guru_trades(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/gurus", stock);
self.send_request(args.as_str()).await
}
pub async fn get_insider_trades(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/insider", stock);
self.send_request(args.as_str()).await
}
pub async fn get_gurus(&self) -> Result<Value, GuruFocusError> {
self.send_request("gurulist").await
}
pub async fn get_guru_picks(
&self,
gurus: &[&str],
start_date: time::Date,
page: i32,
) -> Result<Value, GuruFocusError> {
let args = format!("guru/{}/picks/{}/{}", compact_list(gurus), start_date, page);
self.send_request(args.as_str()).await
}
pub async fn get_guru_portfolios(&self, gurus: &[&str]) -> Result<Value, GuruFocusError> {
let args = format!("guru/{}/aggregated", compact_list(gurus));
self.send_request(args.as_str()).await
}
pub async fn get_exchanges(&self) -> Result<Value, GuruFocusError> {
self.send_request("exchange_list").await
}
pub async fn get_listed_stocks(&self, exchange: &str) -> Result<Value, GuruFocusError> {
let args = format!("exchange_stocks/{}", exchange);
self.send_request(args.as_str()).await
}
pub async fn get_insider_updates(&self) -> Result<Value, GuruFocusError> {
self.send_request("insider_updates").await
}
pub async fn get_dividend_history(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/dividend", stock);
self.send_request(args.as_str()).await
}
pub async fn get_analyst_estimate(&self, stock: &str) -> Result<Value, GuruFocusError> {
let args = format!("stock/{}/analyst_estimate ", stock);
self.send_request(args.as_str()).await
}
pub async fn get_personal_portfolio(&self) -> Result<Value, GuruFocusError> {
self.send_request("portfolio/my_portfolios").await
}
pub async fn get_updated_stocks(&self, date: time::Date) -> Result<Value, GuruFocusError> {
let args = format!("funda_updated/{}", date);
self.send_request(args.as_str()).await
}
pub async fn get_politicians(&self) -> Result<Value, GuruFocusError> {
self.send_request("politicians").await
}
pub async fn get_politician_transactions(
&self,
page: u32,
asset_type: Option<crate::gurus::AssetType>,
) -> Result<Value, GuruFocusError> {
let args = format!(
"politicians/transactions?page={page}&asset_type={}",
match asset_type {
None => "All".to_string(),
Some(at) => format!("{at:?}"),
}
);
self.send_request(args.as_str()).await
}
async fn send_request(&self, args: &str) -> Result<Value, GuruFocusError> {
let url: String = format!("{}{}/{}", self.url, self.user_token, args);
println!("curl -o data.json {url}");
let resp = reqwest::get(url.as_str()).await?;
Ok(resp.json().await?)
}
}
fn compact_list(a: &[&str]) -> String {
if a.is_empty() {
return String::new();
}
let mut it = a.iter();
let mut res = it.next().unwrap().to_string();
for n in it {
res.push_str(&format!(",{}", n));
}
res
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::env;
use super::*;
use serde::Deserialize;
#[test]
fn test_compact_list() {
assert_eq!(compact_list(&["1", "2", "3"]), "1,2,3");
assert_eq!(compact_list(&[]), "");
assert_eq!(compact_list(&["3"]), "3");
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct SimpleStruct {
pub value: i32,
}
#[test]
fn deserialize_extra_fields() {
let data_correct = r#"
{
"value": 42
}"#;
let s: SimpleStruct = serde_json::from_str(data_correct).unwrap();
assert_eq!(s.value, 42);
let data_wrong = r#"
{
"value": 42,
"text": "bla"
}"#;
let s: serde_json::Result<SimpleStruct> = serde_json::from_str(data_wrong);
assert!(s.is_err());
}
#[tokio::test]
async fn test_exchanges() {
if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
if !token.is_empty() {
let gf_connect = GuruFocusConnector::new(token);
let exchanges = gf_connect.get_exchanges().await;
assert!(exchanges.is_ok());
let exchange_map =
serde_json::from_value::<HashMap<String, Vec<String>>>(exchanges.unwrap());
assert!(exchange_map.is_ok());
}
}
}
}