use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use super::build_client;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct StockNews {
pub symbol: Option<String>,
#[serde(rename = "publishedDate")]
pub published_date: Option<String>,
pub title: Option<String>,
pub image: Option<String>,
pub site: Option<String>,
pub text: Option<String>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FmpArticle {
pub title: Option<String>,
pub date: Option<String>,
pub content: Option<String>,
pub tickers: Option<String>,
pub image: Option<String>,
pub link: Option<String>,
pub author: Option<String>,
pub site: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FmpArticlesResponse {
pub content: Option<Vec<FmpArticle>>,
pub page: Option<u32>,
pub size: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PressRelease {
pub symbol: Option<String>,
pub date: Option<String>,
pub title: Option<String>,
pub text: Option<String>,
}
pub async fn stock_news(tickers: &str, limit: u32) -> Result<Vec<StockNews>> {
let client = build_client()?;
let limit_str = limit.to_string();
client
.get(
"/api/v3/stock_news",
&[("tickers", tickers), ("limit", &limit_str)],
)
.await
}
pub async fn fmp_articles(page: u32, size: u32) -> Result<FmpArticlesResponse> {
let client = build_client()?;
let page_str = page.to_string();
let size_str = size.to_string();
client
.get(
"/api/v3/fmp/articles",
&[("page", &*page_str), ("size", &*size_str)],
)
.await
}
pub async fn press_releases(symbol: &str, limit: u32) -> Result<Vec<PressRelease>> {
let client = build_client()?;
let path = format!("/api/v3/press-releases/{}", encode_path_segment(symbol));
let limit_str = limit.to_string();
client.get(&path, &[("limit", &*limit_str)]).await
}
pub async fn crypto_news(limit: u32) -> Result<Vec<StockNews>> {
let client = build_client()?;
let size_str = limit.to_string();
client
.get("/api/v4/crypto_news", &[("page", "0"), ("size", &size_str)])
.await
}
pub async fn forex_news(limit: u32) -> Result<Vec<StockNews>> {
let client = build_client()?;
let size_str = limit.to_string();
client
.get("/api/v4/forex_news", &[("page", "0"), ("size", &size_str)])
.await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_stock_news_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/stock_news")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("tickers".into(), "AAPL".into()),
mockito::Matcher::UrlEncoded("limit".into(), "5".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"symbol": "AAPL",
"publishedDate": "2024-01-15 12:00:00",
"title": "Apple Reports Record Quarter",
"image": "https://example.com/image.jpg",
"site": "Reuters",
"text": "Apple Inc. reported record quarterly earnings...",
"url": "https://example.com/article"
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let resp: Vec<StockNews> = client
.get("/api/v3/stock_news", &[("tickers", "AAPL"), ("limit", "5")])
.await
.unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].symbol.as_deref(), Some("AAPL"));
assert_eq!(resp[0].site.as_deref(), Some("Reuters"));
}
#[tokio::test]
async fn test_press_releases_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/press-releases/AAPL")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("limit".into(), "10".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"symbol": "AAPL",
"date": "2024-01-15",
"title": "Apple Announces New Product",
"text": "Cupertino, CA -- Apple today announced..."
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let resp: Vec<PressRelease> = client
.get("/api/v3/press-releases/AAPL", &[("limit", "10")])
.await
.unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(
resp[0].title.as_deref(),
Some("Apple Announces New Product")
);
}
}