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}