Skip to main content

claude_code_stats/source/
mod.rs

1pub mod cli_probe;
2pub mod cost_scanner;
3pub mod oauth;
4pub mod web;
5
6use anyhow::Result;
7use std::fmt;
8
9use crate::types::ApiResponse;
10
11pub enum SourceError {
12    NotAvailable(String),
13    Failed(anyhow::Error),
14}
15
16impl fmt::Display for SourceError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            SourceError::NotAvailable(msg) => write!(f, "not available: {msg}"),
20            SourceError::Failed(err) => write!(f, "failed: {err}"),
21        }
22    }
23}
24
25pub trait UsageSource {
26    fn name(&self) -> &'static str;
27    fn try_fetch(&self) -> Result<ApiResponse, SourceError>;
28}
29
30pub fn fetch_with_failover(sources: &[&dyn UsageSource]) -> Result<(ApiResponse, &'static str)> {
31    let mut last_error: Option<anyhow::Error> = None;
32
33    for source in sources {
34        match source.try_fetch() {
35            Ok(response) => return Ok((response, source.name())),
36            Err(SourceError::NotAvailable(reason)) => {
37                eprintln!("claude-code-stats: {} skipped: {}", source.name(), reason);
38            }
39            Err(SourceError::Failed(err)) => {
40                eprintln!("claude-code-stats: {} failed: {}", source.name(), err);
41                last_error = Some(err);
42            }
43        }
44    }
45
46    Err(last_error.unwrap_or_else(|| anyhow::anyhow!("no usage sources available")))
47}