mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-08-07 09:43:53 +00:00
refactor: schema-driven architecture, capability traits, hardened error paths
- Split parse.rs into raw_types.rs (serde structs) + map.rs (pure transforms) for MSN and Yahoo - Replace Yahoo fundamentals dynamic HashMap with typed structs (SummaryDetail, DefaultKeyStatistics, etc.) - Introduce capability-based provider traits: QuoteProvider, FundamentalsProvider, HistoryProvider - Add future MSN capability traits: ProfileProvider, EarningsProvider, FinancialsProvider, SentimentProvider, InsightsProvider, NewsProvider (all dead_code until wired to CLI) - Add shared domain types in src/api/types.rs (CompanyProfile, EarningsReport, FinancialStatements, SentimentData, InsightData, NewsItem) - Harden error propagation: Yahoo cookie auth, MSN partial fundamentals, history symbol context, cache clear failures - Strict config parsing: invalid IDX_OUTPUT returns ConfigError instead of silent fallback - Cache schema version enforcement: version mismatch treated as cache miss - Extract fetch_with_cache() helper in cli/stocks.rs - Add MSN retry/backoff parity with Yahoo client - Standardize Option<T> policy through parse/map layers - All 56 tests passing, clippy clean
This commit is contained in:
parent
a45ee3620f
commit
3998e38ffc
17 changed files with 1185 additions and 969 deletions
|
|
@ -4,7 +4,7 @@ use serde::de::DeserializeOwned;
|
|||
|
||||
use crate::error::IdxError;
|
||||
|
||||
use super::parse::{KeyRatios, MsnQuote};
|
||||
use super::raw_types::{KeyRatios, MsnQuote};
|
||||
use super::symbols::resolve_msn_id;
|
||||
|
||||
const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
|
||||
|
|
@ -34,25 +34,48 @@ impl MsnClient {
|
|||
symbol: &str,
|
||||
endpoint: &str,
|
||||
) -> Result<T, IdxError> {
|
||||
let response = self
|
||||
.agent
|
||||
.get(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", "application/json")
|
||||
.header("Accept-Language", "en-US,en;q=0.9,id;q=0.8")
|
||||
.header("Origin", "https://www.msn.com")
|
||||
.header("Referer", "https://www.msn.com/")
|
||||
.call();
|
||||
let mut wait = Duration::from_millis(500);
|
||||
for attempt in 0..3 {
|
||||
let response = self
|
||||
.agent
|
||||
.get(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", "application/json")
|
||||
.header("Accept-Language", "en-US,en;q=0.9,id;q=0.8")
|
||||
.header("Origin", "https://www.msn.com")
|
||||
.header("Referer", "https://www.msn.com/")
|
||||
.call();
|
||||
|
||||
match response {
|
||||
Ok(ok) => ok
|
||||
.into_body()
|
||||
.read_json::<T>()
|
||||
.map_err(|e| IdxError::ParseError(format!("msn {endpoint}: {e}"))),
|
||||
Err(ureq::Error::StatusCode(404)) => Err(IdxError::SymbolNotFound(symbol.to_string())),
|
||||
Err(ureq::Error::StatusCode(429)) => Err(IdxError::RateLimited),
|
||||
Err(err) => Err(IdxError::Http(format!("msn {endpoint}: {err}"))),
|
||||
match response {
|
||||
Ok(ok) => {
|
||||
return ok
|
||||
.into_body()
|
||||
.read_json::<T>()
|
||||
.map_err(|e| IdxError::ParseError(format!("msn {endpoint}: {e}")));
|
||||
}
|
||||
Err(ureq::Error::StatusCode(404)) => {
|
||||
return Err(IdxError::SymbolNotFound(symbol.to_string()));
|
||||
}
|
||||
Err(ureq::Error::StatusCode(429)) => {
|
||||
if attempt < 2 {
|
||||
std::thread::sleep(wait);
|
||||
wait *= 2;
|
||||
continue;
|
||||
}
|
||||
return Err(IdxError::RateLimited);
|
||||
}
|
||||
Err(ureq::Error::StatusCode(code)) if code >= 500 => {
|
||||
if attempt < 2 {
|
||||
std::thread::sleep(wait);
|
||||
wait *= 2;
|
||||
continue;
|
||||
}
|
||||
return Err(IdxError::Http(format!("msn {endpoint}: status {code}")));
|
||||
}
|
||||
Err(err) => return Err(IdxError::Http(format!("msn {endpoint}: {err}"))),
|
||||
}
|
||||
}
|
||||
Err(IdxError::RateLimited)
|
||||
}
|
||||
|
||||
pub(super) fn fetch_quotes(&self, symbol: &str) -> Result<Vec<MsnQuote>, IdxError> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue