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:
Ciphercat 2026-03-06 19:17:50 +00:00
commit 3998e38ffc
17 changed files with 1185 additions and 969 deletions

View file

@ -16,12 +16,16 @@ pub enum IdxError {
ParseError(String),
#[error("cache miss: {0}")]
CacheMiss(String),
#[error("offline: {0}")]
Offline(String),
#[error("config error: {0}")]
ConfigError(String),
#[error("io error: {0}")]
Io(String),
#[error("http error: {0}")]
Http(String),
#[error("auth error: {0}")]
AuthError(String),
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
@ -32,9 +36,11 @@ pub enum ErrorCode {
Unsupported,
ParseError,
CacheMiss,
Offline,
ConfigError,
Io,
Http,
AuthError,
}
impl IdxError {
@ -46,9 +52,11 @@ impl IdxError {
Self::Unsupported(_) => ErrorCode::Unsupported,
Self::ParseError(_) => ErrorCode::ParseError,
Self::CacheMiss(_) => ErrorCode::CacheMiss,
Self::Offline(_) => ErrorCode::Offline,
Self::ConfigError(_) => ErrorCode::ConfigError,
Self::Io(_) => ErrorCode::Io,
Self::Http(_) => ErrorCode::Http,
Self::AuthError(_) => ErrorCode::AuthError,
}
}