use crate::client::AkShareClient;
use crate::error::{Error, Result};
use crate::types::NewsItem;
use crate::types::value_ext::ValueExt;
impl AkShareClient {
pub async fn marketaux_news(
&self,
symbol: &str,
api_key: &str,
limit: usize,
) -> Result<Vec<NewsItem>> {
if symbol.is_empty() {
return Err(Error::invalid_input("symbol must not be empty"));
}
if api_key.is_empty() {
return Err(Error::invalid_input("api_key must not be empty"));
}
let limit_str = limit.min(3).to_string();
let resp: serde_json::Value = self
.get("https://api.marketaux.com/v1/news/all")
.query(&[
("entities", symbol),
("api_token", api_key),
("limit", &limit_str),
])
.send()
.await?
.json()
.await?;
let arr = resp
.get("data")
.and_then(|v| v.as_array())
.ok_or_else(|| Error::decode("marketaux: missing 'data' array"))?;
let items: Vec<NewsItem> = arr
.iter()
.filter_map(|item| {
let title = item.str_field(&["title"])?;
if title.is_empty() {
return None;
}
let description = item.str_or(&["description"], "");
let url = item.str_field(&["url"]).map(str::to_string);
let source = item.str_or(&["source"], "Marketaux");
let published_at = item
.str_field(&["published_at"])
.map(|s| s[..10.min(s.len())].to_string())
.unwrap_or_default();
Some(NewsItem {
published_at,
title: title.to_string(),
summary: description,
source,
url,
})
})
.collect();
Ok(items)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_published_at_truncation() {
let raw = "2026-06-15T12:00:00+00:00";
assert_eq!(&raw[..10.min(raw.len())], "2026-06-15");
}
}