claude_code_stats/source/
mod.rs1pub 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}