Skip to main content

stackless_cloud/
spend.rs

1//! Shared spend line (§4 — never silently nothing): prefer the plugin's live
2//! spend summary; fall back to the hard cap plus a dashboard pointer when the
3//! plugin doesn't expose spend. Each substrate passes its own provider label,
4//! cap, and dashboard URL.
5
6use std::path::Path;
7
8use stackless_core::substrate::SpendInfo;
9use stackless_stripe_projects::project;
10use stackless_stripe_projects::stripe::{StripeProjects, TokioRunner};
11
12/// Fetch structured spend for `--json` envelopes.
13pub async fn fetch(
14    definition_dir: &Path,
15    provider: &str,
16    cap_usd: u32,
17    dashboard: &str,
18) -> SpendInfo {
19    let stripe = StripeProjects::new(TokioRunner, definition_dir.to_path_buf());
20    match project::spend_summary(&stripe, Some(provider)).await {
21        Some(data) => {
22            let summary = format!("spend: {data}");
23            let parsed = serde_json::from_str::<serde_json::Value>(&data).ok();
24            SpendInfo {
25                provider: provider.to_owned(),
26                cap_usd,
27                summary,
28                data: parsed.or(Some(serde_json::Value::String(data))),
29            }
30        }
31        None => SpendInfo {
32            provider: provider.to_owned(),
33            cap_usd,
34            summary: format!(
35                "spend: unavailable from the plugin; hard cap is ${cap_usd}/mo \
36                 (provider {provider}) — see {dashboard}"
37            ),
38            data: None,
39        },
40    }
41}
42
43/// The line printed after `up`/`down`. `provider` is the substrate label,
44/// `cap_usd` the hard per-provider spend cap, `dashboard` where the operator
45/// confirms spend by hand.
46pub async fn line(definition_dir: &Path, provider: &str, cap_usd: u32, dashboard: &str) -> String {
47    fetch(definition_dir, provider, cap_usd, dashboard)
48        .await
49        .summary
50}