financeapi/
lib.rs

1//! # financeapi
2//!
3//! This crate provides a simple set of APIs to interface with
4//! [financeapi.net](http://financeapi.net) to retrieve financial data for
5//! stocks, ETFs, mutual funds, etc...
6//!
7//! To be able to use this API the user needs to register and get an API key
8//! from [financeapi.net](http://financeapi.net).
9//!
10//! Currently only the following modules are available:
11//! - `/v6/finance/quote` (Real time quote data for stocks, ETFs, mutuals funds, etc...)
12//! - `/v6/finance/autocomplete` (Get auto complete stock suggestions)
13//!
14//! The crate is using `reqwest` with `async` features. In a blocking /
15//! synchronous context these functions must be called using `block_on` or
16//! equivalent (see example).
17//!
18//! # Examples
19//!
20//! ```ignore
21//!    
22//!     // Here goes your API key
23//!     let connector = FinanceapiConnector::new("...");
24//!
25//!     // v6/finance/quote
26//!     let quote = tokio::runtime::Builder::new_current_thread()
27//!         .enable_all()
28//!         .build()
29//!         .unwrap()
30//!         .block_on(connector.quote("AAPL"))
31//!         .unwrap_or_else(|e| panic!("ERROR: {}", e));
32//!
33//!     println!(
34//!         "AAPL ({}) is currently at {} {}",
35//!         quote.long_name.unwrap_or_default(),
36//!         quote.regular_market_price.unwrap_or_default(),
37//!         quote.financial_currency.unwrap_or_default()
38//!     );
39//!
40//!     let symbol = "VWCE";
41//!
42//!     // v6/finance/autocomplete
43//!     let search = tokio::runtime::Builder::new_current_thread()
44//!         .enable_all()
45//!         .build()
46//!         .unwrap()
47//!         .block_on(connector.autocomplete(symbol))
48//!         .unwrap_or_else(|e| panic!("ERROR: {}", e));
49//!
50//!     println!("\nFound {} results for {}", search.len(), symbol);
51//!
52//!     for (i, v) in search.iter().enumerate() {
53//!         println!("{}: {} ({})", i, v.symbol, v.name);
54//!     }
55//! ```
56
57use const_format::formatcp;
58use reqwest::IntoUrl;
59use reqwest::{Client, Response};
60use url::Url;
61
62mod autocomplete;
63mod error;
64mod quote;
65
66pub use autocomplete::FinanceapiAutocomplete;
67pub use error::FinanceapiError;
68pub use quote::FinanceapiQuote;
69
70const YH_LOCALE: &str = "region=US&lang=en";
71const YH_URL: &str = "https://yfapi.net";
72
73const YH_MODULE_FQ: &str = "v6/finance/quote";
74const YH_URL_FQ: &str = formatcp!("{YH_URL}/{YH_MODULE_FQ}?{YH_LOCALE}");
75
76const YH_MODULE_FA: &str = "v6/finance/autocomplete";
77const YH_URL_FA: &str = formatcp!("{YH_URL}/{YH_MODULE_FA}?{YH_LOCALE}");
78
79#[derive(Default, Debug)]
80pub struct FinanceapiConnector {
81    client: Client,
82    api_key: String,
83}
84
85impl FinanceapiConnector {
86    async fn send_request<U: IntoUrl>(&self, url: U) -> Result<Response, FinanceapiError> {
87        Ok(self
88            .client
89            .get(url)
90            .header("accept", "application/json")
91            .header("X-API-KEY", &self.api_key)
92            .send()
93            .await?
94            .error_for_status()?)
95    }
96
97    async fn json<U: IntoUrl>(&self, url: U) -> Result<serde_json::Value, FinanceapiError> {
98        Ok(self.send_request(url).await?.json().await?)
99    }
100
101    /// Create a new connection to [financeapi.net](http://financeapi.net) using the
102    /// provided API key.
103    ///
104    /// To get an API key refer to [financeapi.net](http://financeapi.net).
105    pub fn new<T: Into<String>>(key: T) -> Self {
106        Self {
107            client: Client::new(),
108            api_key: key.into(),
109        }
110    }
111
112    /// Get real time quote data for stocks, ETFs, mutuals funds, etc... given a
113    /// provided symbol.
114    ///
115    /// This is leveraging the `/v6/finance/quote` module.
116    pub async fn quote<S: AsRef<str>>(
117        &self,
118        symbol: S,
119    ) -> Result<FinanceapiQuote, FinanceapiError> {
120        let url = Url::parse_with_params(YH_URL_FQ, &[("symbols", symbol.as_ref())])?;
121        let json = self.json(url).await?;
122
123        FinanceapiQuote::from_json(json)
124    }
125
126    /// Get autocomplete stock suggestions given a provided string.
127    ///
128    /// This is leveraging the `/v6/finance/autocomplete` module.
129    pub async fn autocomplete<S: AsRef<str>>(
130        &self,
131        query: S,
132    ) -> Result<Vec<FinanceapiAutocomplete>, FinanceapiError> {
133        let url = Url::parse_with_params(YH_URL_FA, &[("query", query.as_ref())])?;
134        let json = self.json(url).await?;
135
136        FinanceapiAutocomplete::from_json(json)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    #[tokio::test]
143    #[should_panic(expected = "403")]
144    async fn wrong_api_key() {
145        use super::*;
146
147        let connector = FinanceapiConnector::new("ABC");
148        connector.quote("SYMBOL").await.unwrap();
149    }
150}