use super::Edgar;
use super::FeedOperations;
use super::error::{EdgarError, Result};
use super::options::FeedOptions;
use crate::parsing::{
atom::{AtomConfig, AtomDocument, AtomParser},
rss::{RssConfig, RssDocument, RssParser},
};
use async_trait::async_trait;
#[async_trait]
impl FeedOperations for Edgar {
async fn current_feed(&self, opts: Option<FeedOptions>) -> Result<AtomDocument> {
let feed_opts = FeedOptions::new(opts);
let query = serde_urlencoded::to_string(feed_opts.params())
.map_err(|e| EdgarError::InvalidResponse(e.to_string()))?;
let url = format!(
"https://www.sec.gov/cgi-bin/browse-edgar?action=getcurrent&{}",
query
);
let content = self.get(&url).await?;
self.current_feed_from_string(&content)
}
fn current_feed_from_string(&self, content: &str) -> Result<AtomDocument> {
let parser = AtomParser::new(AtomConfig::default());
parser.parse(content)
}
async fn company_feed(&self, cik: &str, opts: Option<FeedOptions>) -> Result<AtomDocument> {
let feed_opts = FeedOptions::new(opts).with_param("CIK", cik);
let query = serde_urlencoded::to_string(feed_opts.params())
.map_err(|e| EdgarError::InvalidResponse(e.to_string()))?;
let url = format!(
"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&{}",
query
);
let content = self.get(&url).await?;
self.company_feed_from_string(&content)
}
fn company_feed_from_string(&self, content: &str) -> Result<AtomDocument> {
let parser = AtomParser::new(AtomConfig::default());
parser.parse(content)
}
async fn get_rss_feed(&self, url: &str) -> Result<RssDocument> {
let content = self.get(url).await?;
self.rss_feed_from_string(&content)
}
fn rss_feed_from_string(&self, content: &str) -> Result<RssDocument> {
let parser = RssParser::new(RssConfig::default());
parser.parse(content)
}
async fn press_release_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/news/pressreleases.rss")
.await
}
async fn speeches_and_statements_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/news/speeches-statements.rss")
.await
}
async fn speeches_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/news/speeches.rss")
.await
}
async fn statements_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/news/statements.rss")
.await
}
async fn testimony_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/news/testimony.rss")
.await
}
async fn administrative_proceedings_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/rss/litigation/admin.xml")
.await
}
async fn division_of_corporation_finance_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/rss/divisions/corpfin/cfnew.xml")
.await
}
async fn division_of_investment_management_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/rss/divisions/investment/imnews.xml")
.await
}
async fn investor_alerts_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/rss/investor/alerts")
.await
}
async fn filings_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/Archives/edgar/usgaap.rss.xml")
.await
}
async fn mutual_funds_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/Archives/edgar/xbrl-rr.rss.xml")
.await
}
async fn xbrl_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/Archives/edgar/xbrlrss.all.xml")
.await
}
async fn inline_xbrl_feed(&self) -> Result<RssDocument> {
self.get_rss_feed("https://www.sec.gov/Archives/edgar/xbrl-inline.rss.xml")
.await
}
async fn historical_xbrl_feed(&self, year: i32, month: i32) -> Result<RssDocument> {
if year < 2005 {
return Err(EdgarError::InvalidXBRLYear);
}
if month < 1 || month > 12 {
return Err(EdgarError::InvalidMonth);
}
let url = format!(
"https://www.sec.gov/Archives/edgar/monthly/xbrlrss-{}-{:02}.xml",
year, month
);
self.get_rss_feed(&url).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_feed() {
let edgar = Edgar::new("test_agent example@example.com").unwrap();
let empty_rss = r#"<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Empty Feed</title>
<link>http://example.com</link>
<description>Empty feed for testing</description>
</channel>
</rss>"#;
let feed = edgar.rss_feed_from_string(empty_rss).unwrap();
assert!(feed.channel.items.is_empty());
assert_eq!(feed.channel.title, "Empty Feed");
assert_eq!(feed.channel.description, "Empty feed for testing");
}
#[tokio::test]
async fn test_historical_xbrl_feed_invalid_year() {
let edgar = Edgar::new("test_agent example@example.com").unwrap();
let result = edgar.historical_xbrl_feed(2004, 1).await;
assert!(matches!(result, Err(EdgarError::InvalidXBRLYear)));
}
#[tokio::test]
async fn test_historical_xbrl_feed_invalid_month() {
let edgar = Edgar::new("test_agent example@example.com").unwrap();
let result = edgar.historical_xbrl_feed(2005, 13).await;
assert!(matches!(result, Err(EdgarError::InvalidMonth)));
}
#[test]
fn test_invalid_feed() {
let edgar = Edgar::new("test_agent example@example.com").unwrap();
let result = edgar.rss_feed_from_string("invalid xml");
assert!(result.is_err());
}
}