1use serde_json::{self, Value};
45use thiserror::Error;
46
47pub mod gurus;
49
50pub mod stock;
52pub use stock::*;
53
54pub mod financials;
56pub use financials::*;
57
58pub mod keyratios;
60pub use keyratios::*;
61
62pub mod insiders;
64pub use insiders::*;
65
66pub mod portfolio;
68pub use portfolio::*;
69
70pub mod strnum;
72
73pub mod hexnum;
75
76#[derive(Error, Debug)]
77pub enum GuruFocusError {
78 #[error("Request failure")]
79 RequestFailure(#[from] reqwest::Error),
80}
81
82pub struct GuruFocusConnector {
84 url: &'static str,
85 user_token: String,
86}
87
88impl GuruFocusConnector {
89 pub fn new(token: String) -> GuruFocusConnector {
93 GuruFocusConnector {
94 url: "https://api.gurufocus.com/public/user/",
95 user_token: token,
96 }
97 }
98
99 pub async fn get_financials(&self, stock: &str) -> Result<Value, GuruFocusError> {
101 let args = format!("stock/{}/financials", stock);
102 self.send_request(args.as_str()).await
103 }
104
105 pub async fn get_key_ratios(&self, stock: &str) -> Result<Value, GuruFocusError> {
107 let args = format!("stock/{}/keyratios", stock);
108 self.send_request(args.as_str()).await
109 }
110
111 pub async fn get_quotes(&self, stocks: &[&str]) -> Result<Value, GuruFocusError> {
113 let args = format!("stock/{}/quote", compact_list(stocks));
114 self.send_request(args.as_str()).await
115 }
116
117 pub async fn get_price_hist(&self, stock: &str) -> Result<Value, GuruFocusError> {
119 let args = format!("stock/{}/price", stock);
120 self.send_request(args.as_str()).await
121 }
122
123 pub async fn get_unadj_price_hist(&self, stock: &str) -> Result<Value, GuruFocusError> {
125 let args = format!("stock/{}/unadjusted_price", stock);
126 self.send_request(args.as_str()).await
127 }
128
129 pub async fn get_stock_summary(&self, stock: &str) -> Result<Value, GuruFocusError> {
131 let args = format!("stock/{}/summary", stock);
132 self.send_request(args.as_str()).await
133 }
134
135 pub async fn get_guru_trades(&self, stock: &str) -> Result<Value, GuruFocusError> {
137 let args = format!("stock/{}/gurus", stock);
138 self.send_request(args.as_str()).await
139 }
140
141 pub async fn get_insider_trades(&self, stock: &str) -> Result<Value, GuruFocusError> {
143 let args = format!("stock/{}/insider", stock);
144 self.send_request(args.as_str()).await
145 }
146
147 pub async fn get_gurus(&self) -> Result<Value, GuruFocusError> {
149 self.send_request("gurulist").await
150 }
151
152 pub async fn get_guru_picks(
154 &self,
155 gurus: &[&str],
156 start_date: time::Date,
157 page: i32,
158 ) -> Result<Value, GuruFocusError> {
159 let args = format!("guru/{}/picks/{}/{}", compact_list(gurus), start_date, page);
160 self.send_request(args.as_str()).await
161 }
162
163 pub async fn get_guru_portfolios(&self, gurus: &[&str]) -> Result<Value, GuruFocusError> {
165 let args = format!("guru/{}/aggregated", compact_list(gurus));
166 self.send_request(args.as_str()).await
167 }
168
169 pub async fn get_exchanges(&self) -> Result<Value, GuruFocusError> {
171 self.send_request("exchange_list").await
172 }
173
174 pub async fn get_listed_stocks(&self, exchange: &str) -> Result<Value, GuruFocusError> {
176 let args = format!("exchange_stocks/{}", exchange);
177 self.send_request(args.as_str()).await
178 }
179
180 pub async fn get_insider_updates(&self) -> Result<Value, GuruFocusError> {
182 self.send_request("insider_updates").await
183 }
184
185 pub async fn get_dividend_history(&self, stock: &str) -> Result<Value, GuruFocusError> {
187 let args = format!("stock/{}/dividend", stock);
188 self.send_request(args.as_str()).await
189 }
190
191 pub async fn get_analyst_estimate(&self, stock: &str) -> Result<Value, GuruFocusError> {
193 let args = format!("stock/{}/analyst_estimate ", stock);
194 self.send_request(args.as_str()).await
195 }
196
197 pub async fn get_personal_portfolio(&self) -> Result<Value, GuruFocusError> {
199 self.send_request("portfolio/my_portfolios").await
200 }
201
202 pub async fn get_updated_stocks(&self, date: time::Date) -> Result<Value, GuruFocusError> {
204 let args = format!("funda_updated/{}", date);
205 self.send_request(args.as_str()).await
206 }
207
208 pub async fn get_politicians(&self) -> Result<Value, GuruFocusError> {
210 self.send_request("politicians").await
211 }
212
213 pub async fn get_politician_transactions(
215 &self,
216 page: u32,
217 asset_type: Option<crate::gurus::AssetType>,
218 ) -> Result<Value, GuruFocusError> {
219 let args = format!(
220 "politicians/transactions?page={page}&asset_type={}",
221 match asset_type {
222 None => "All".to_string(),
223 Some(at) => format!("{at:?}"),
224 }
225 );
226 self.send_request(args.as_str()).await
227 }
228
229 async fn send_request(&self, args: &str) -> Result<Value, GuruFocusError> {
231 let url: String = format!("{}{}/{}", self.url, self.user_token, args);
232 println!("curl -o data.json {url}");
233 let resp = reqwest::get(url.as_str()).await?;
234 Ok(resp.json().await?)
235 }
236}
237
238fn compact_list(a: &[&str]) -> String {
240 if a.is_empty() {
241 return String::new();
242 }
243 let mut it = a.iter();
244 let mut res = it.next().unwrap().to_string();
245 for n in it {
246 res.push_str(&format!(",{}", n));
247 }
248 res
249}
250
251#[cfg(test)]
252mod tests {
253 use std::collections::HashMap;
254 use std::env;
255
256 use super::*;
257 use serde::Deserialize;
258
259 #[test]
260 fn test_compact_list() {
261 assert_eq!(compact_list(&["1", "2", "3"]), "1,2,3");
262 assert_eq!(compact_list(&[]), "");
263 assert_eq!(compact_list(&["3"]), "3");
264 }
265
266 #[derive(Deserialize, Debug)]
267 #[serde(deny_unknown_fields)]
268 struct SimpleStruct {
269 pub value: i32,
270 }
271
272 #[test]
273 fn deserialize_extra_fields() {
274 let data_correct = r#"
276 {
277 "value": 42
278 }"#;
279
280 let s: SimpleStruct = serde_json::from_str(data_correct).unwrap();
281 assert_eq!(s.value, 42);
282
283 let data_wrong = r#"
284 {
285 "value": 42,
286 "text": "bla"
287 }"#;
288
289 let s: serde_json::Result<SimpleStruct> = serde_json::from_str(data_wrong);
290 assert!(s.is_err());
292 }
293
294 #[tokio::test]
295 async fn test_exchanges() {
296 if let Ok(token) = env::var("GURUFOCUS_TOKEN") {
297 if !token.is_empty() {
298 let gf_connect = GuruFocusConnector::new(token);
299 let exchanges = gf_connect.get_exchanges().await;
300 assert!(exchanges.is_ok());
301 let exchange_map =
302 serde_json::from_value::<HashMap<String, Vec<String>>>(exchanges.unwrap());
303 assert!(exchange_map.is_ok());
304 }
305 }
306 }
307}