1use crate::client::FmpClient;
4use crate::error::Result;
5use crate::models::company::{
6 CompanyCoreInfo, CompanyOutlook, CompanyProfile, CompanyRating, Executive, MarketCap,
7 ShareFloat, StockPeers,
8};
9use serde::Serialize;
10
11pub struct CompanyInfo {
13 client: FmpClient,
14}
15
16impl CompanyInfo {
17 pub(crate) fn new(client: FmpClient) -> Self {
18 Self { client }
19 }
20
21 pub async fn get_profile(&self, symbol: &str) -> Result<Vec<CompanyProfile>> {
23 #[derive(Serialize)]
24 struct Query<'a> {
25 symbol: &'a str,
26 apikey: &'a str,
27 }
28
29 let url = self.client.build_url("/profile");
30 self.client
31 .get_with_query(
32 &url,
33 &Query {
34 symbol,
35 apikey: self.client.api_key(),
36 },
37 )
38 .await
39 }
40
41 pub async fn get_executives(&self, symbol: &str) -> Result<Vec<Executive>> {
43 #[derive(Serialize)]
44 struct Query<'a> {
45 symbol: &'a str,
46 apikey: &'a str,
47 }
48
49 let url = self.client.build_url("/key-executives");
50 self.client
51 .get_with_query(
52 &url,
53 &Query {
54 symbol,
55 apikey: self.client.api_key(),
56 },
57 )
58 .await
59 }
60
61 pub async fn get_market_cap(&self, symbol: &str) -> Result<Vec<MarketCap>> {
63 #[derive(Serialize)]
64 struct Query<'a> {
65 symbol: &'a str,
66 apikey: &'a str,
67 }
68
69 let url = self.client.build_url("/market-capitalization");
70 self.client
71 .get_with_query(
72 &url,
73 &Query {
74 symbol,
75 apikey: self.client.api_key(),
76 },
77 )
78 .await
79 }
80
81 pub async fn get_historical_market_cap(
83 &self,
84 symbol: &str,
85 from: Option<&str>,
86 to: Option<&str>,
87 ) -> Result<Vec<MarketCap>> {
88 #[derive(Serialize)]
89 struct Query<'a> {
90 symbol: &'a str,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 from: Option<&'a str>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 to: Option<&'a str>,
95 apikey: &'a str,
96 }
97
98 let url = self.client.build_url("/historical-market-capitalization");
99 self.client
100 .get_with_query(
101 &url,
102 &Query {
103 symbol,
104 from,
105 to,
106 apikey: self.client.api_key(),
107 },
108 )
109 .await
110 }
111
112 pub async fn get_share_float(&self, symbol: &str) -> Result<Vec<ShareFloat>> {
114 #[derive(Serialize)]
115 struct Query<'a> {
116 symbol: &'a str,
117 apikey: &'a str,
118 }
119
120 let url = self.client.build_url("/shares-float");
121 self.client
122 .get_with_query(
123 &url,
124 &Query {
125 symbol,
126 apikey: self.client.api_key(),
127 },
128 )
129 .await
130 }
131
132 pub async fn get_peers(&self, symbol: &str) -> Result<Vec<StockPeers>> {
134 #[derive(Serialize)]
135 struct Query<'a> {
136 symbol: &'a str,
137 apikey: &'a str,
138 }
139
140 let url = self.client.build_url("/stock-peers");
141 self.client
142 .get_with_query(
143 &url,
144 &Query {
145 symbol,
146 apikey: self.client.api_key(),
147 },
148 )
149 .await
150 }
151
152 pub async fn get_outlook(&self, symbol: &str) -> Result<CompanyOutlook> {
177 #[derive(Serialize)]
178 struct Query<'a> {
179 symbol: &'a str,
180 apikey: &'a str,
181 }
182
183 let url = self
184 .client
185 .build_url(&format!("/company-outlook?symbol={}", symbol));
186 self.client
187 .get_with_query(
188 &url,
189 &Query {
190 symbol,
191 apikey: self.client.api_key(),
192 },
193 )
194 .await
195 }
196
197 pub async fn get_rating(&self, symbol: &str) -> Result<Vec<CompanyRating>> {
219 #[derive(Serialize)]
220 struct Query<'a> {
221 symbol: &'a str,
222 apikey: &'a str,
223 }
224
225 let url = self.client.build_url(&format!("/rating/{}", symbol));
226 self.client
227 .get_with_query(
228 &url,
229 &Query {
230 symbol,
231 apikey: self.client.api_key(),
232 },
233 )
234 .await
235 }
236
237 pub async fn get_historical_rating(
259 &self,
260 symbol: &str,
261 limit: Option<u32>,
262 ) -> Result<Vec<CompanyRating>> {
263 #[derive(Serialize)]
264 struct Query<'a> {
265 symbol: &'a str,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 limit: Option<u32>,
268 apikey: &'a str,
269 }
270
271 let url = self
272 .client
273 .build_url(&format!("/historical-rating/{}", symbol));
274 self.client
275 .get_with_query(
276 &url,
277 &Query {
278 symbol,
279 limit,
280 apikey: self.client.api_key(),
281 },
282 )
283 .await
284 }
285
286 pub async fn get_core_info(&self, symbol: &str) -> Result<Vec<CompanyCoreInfo>> {
305 #[derive(Serialize)]
306 struct Query<'a> {
307 symbol: &'a str,
308 apikey: &'a str,
309 }
310
311 let url = self.client.build_url("/company-core-information");
312 self.client
313 .get_with_query(
314 &url,
315 &Query {
316 symbol,
317 apikey: self.client.api_key(),
318 },
319 )
320 .await
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_new() {
330 let client = FmpClient::builder().api_key("test_key").build().unwrap();
331 let _ = CompanyInfo::new(client);
332 }
333
334 #[tokio::test]
336 #[ignore = "requires FMP API key"]
337 async fn test_get_profile() {
338 let client = FmpClient::new().unwrap();
339 let result = client.company_info().get_profile("AAPL").await;
340 assert!(result.is_ok());
341 let profiles = result.unwrap();
342 assert!(!profiles.is_empty());
343 assert_eq!(profiles[0].symbol, "AAPL");
344 }
345
346 #[tokio::test]
347 #[ignore = "requires FMP API key"]
348 async fn test_get_outlook() {
349 let client = FmpClient::new().unwrap();
350 let result = client.company_info().get_outlook("AAPL").await;
351 assert!(result.is_ok());
352 let outlook = result.unwrap();
353 assert_eq!(outlook.profile.symbol, "AAPL");
354 assert!(outlook.profile.company_name.contains("Apple"));
355 }
356
357 #[tokio::test]
358 #[ignore = "requires FMP API key"]
359 async fn test_get_rating() {
360 let client = FmpClient::new().unwrap();
361 let result = client.company_info().get_rating("AAPL").await;
362 assert!(result.is_ok());
363 let ratings = result.unwrap();
364 assert!(!ratings.is_empty());
365 assert_eq!(ratings[0].symbol, "AAPL");
366 }
367
368 #[tokio::test]
369 #[ignore = "requires FMP API key"]
370 async fn test_get_historical_rating() {
371 let client = FmpClient::new().unwrap();
372 let result = client
373 .company_info()
374 .get_historical_rating("AAPL", Some(5))
375 .await;
376 assert!(result.is_ok());
377 let ratings = result.unwrap();
378 assert!(!ratings.is_empty());
379 assert!(ratings.len() <= 5);
380 }
381
382 #[tokio::test]
383 #[ignore = "requires FMP API key"]
384 async fn test_get_core_info() {
385 let client = FmpClient::new().unwrap();
386 let result = client.company_info().get_core_info("AAPL").await;
387 assert!(result.is_ok());
388 let info = result.unwrap();
389 assert!(!info.is_empty());
390 assert_eq!(info[0].symbol, "AAPL");
391 }
392
393 #[tokio::test]
394 #[ignore = "requires FMP API key"]
395 async fn test_get_executives() {
396 let client = FmpClient::new().unwrap();
397 let result = client.company_info().get_executives("AAPL").await;
398 assert!(result.is_ok());
399 let execs = result.unwrap();
400 assert!(!execs.is_empty());
401 }
402
403 #[tokio::test]
404 #[ignore = "requires FMP API key"]
405 async fn test_get_market_cap() {
406 let client = FmpClient::new().unwrap();
407 let result = client.company_info().get_market_cap("AAPL").await;
408 assert!(result.is_ok());
409 }
410
411 #[tokio::test]
412 #[ignore = "requires FMP API key"]
413 async fn test_get_share_float() {
414 let client = FmpClient::new().unwrap();
415 let result = client.company_info().get_share_float("AAPL").await;
416 assert!(result.is_ok());
417 }
418
419 #[tokio::test]
420 #[ignore = "requires FMP API key"]
421 async fn test_get_peers() {
422 let client = FmpClient::new().unwrap();
423 let result = client.company_info().get_peers("AAPL").await;
424 assert!(result.is_ok());
425 let peers = result.unwrap();
426 assert!(!peers.is_empty());
427 assert_eq!(peers[0].symbol, "AAPL");
428 }
429
430 #[tokio::test]
432 #[ignore = "requires FMP API key"]
433 async fn test_get_profile_invalid_symbol() {
434 let client = FmpClient::new().unwrap();
435 let result = client
436 .company_info()
437 .get_profile("INVALID_SYMBOL_XYZ123")
438 .await;
439 if let Ok(profiles) = result {
441 assert!(profiles.is_empty());
442 }
443 }
444
445 #[tokio::test]
446 #[ignore = "requires FMP API key"]
447 async fn test_get_rating_invalid_symbol() {
448 let client = FmpClient::new().unwrap();
449 let result = client
450 .company_info()
451 .get_rating("INVALID_SYMBOL_XYZ123")
452 .await;
453 if let Ok(ratings) = result {
455 assert!(ratings.is_empty());
456 }
457 }
458
459 #[tokio::test]
460 #[ignore = "requires FMP API key"]
461 async fn test_get_historical_market_cap_with_date_range() {
462 let client = FmpClient::new().unwrap();
463 let result = client
464 .company_info()
465 .get_historical_market_cap("AAPL", Some("2024-01-01"), Some("2024-12-31"))
466 .await;
467 assert!(result.is_ok());
468 }
469
470 #[tokio::test]
471 #[ignore = "requires FMP API key"]
472 async fn test_get_historical_rating_with_limit() {
473 let client = FmpClient::new().unwrap();
474 let result = client
475 .company_info()
476 .get_historical_rating("AAPL", Some(3))
477 .await;
478 assert!(result.is_ok());
479 if let Ok(ratings) = result {
480 assert!(ratings.len() <= 3);
481 }
482 }
483
484 #[tokio::test]
486 async fn test_invalid_api_key() {
487 let client = FmpClient::builder()
488 .api_key("invalid_key_12345")
489 .build()
490 .unwrap();
491 let result = client.company_info().get_profile("AAPL").await;
492 assert!(result.is_err());
493 }
494
495 #[tokio::test]
496 async fn test_empty_symbol() {
497 let client = FmpClient::builder().api_key("test_key").build().unwrap();
498 let result = client.company_info().get_profile("").await;
499 assert!(result.is_err() || result.unwrap().is_empty());
501 }
502}