1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Serialize, Deserialize)]
6pub struct UsageReport {
7 pub api_key_name: String,
8 pub since: DateTime<Utc>,
9 pub until: DateTime<Utc>,
10 pub sessions: SessionCounts,
11 pub compute: ComputeTotals,
12 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub bundle_builds: Option<BundleBuildUsage>,
15}
16
17#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
19pub struct BundleBuildUsage {
20 pub requested: u64,
21}
22
23#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
29pub struct SessionCounts {
30 pub started: u64,
31 pub completed: u64,
32 pub failed: u64,
33}
34
35#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
37pub struct ComputeTotals {
38 pub executed_slot_count: u64,
39 pub session_duration_ms: u64,
40}
41
42impl SessionCounts {
43 pub fn saturating_add(self, other: Self) -> Self {
44 Self {
45 started: self.started.saturating_add(other.started),
46 completed: self.completed.saturating_add(other.completed),
47 failed: self.failed.saturating_add(other.failed),
48 }
49 }
50}
51
52impl ComputeTotals {
53 pub fn saturating_add(self, other: Self) -> Self {
54 Self {
55 executed_slot_count: self
56 .executed_slot_count
57 .saturating_add(other.executed_slot_count),
58 session_duration_ms: self
59 .session_duration_ms
60 .saturating_add(other.session_duration_ms),
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 fn report(bundle_builds: Option<BundleBuildUsage>) -> UsageReport {
70 UsageReport {
71 api_key_name: "k".to_string(),
72 since: "2026-01-01T00:00:00Z".parse().unwrap(),
73 until: "2026-01-02T00:00:00Z".parse().unwrap(),
74 sessions: SessionCounts::default(),
75 compute: ComputeTotals::default(),
76 bundle_builds,
77 }
78 }
79
80 #[test]
81 fn usage_report_absent_bundle_builds_round_trips_to_none() {
82 let json = serde_json::to_string(&report(None)).unwrap();
83 assert!(!json.contains("bundle_builds"));
84 let parsed: UsageReport = serde_json::from_str(&json).unwrap();
85 assert_eq!(parsed.bundle_builds, None);
86 }
87
88 #[test]
89 fn usage_report_present_bundle_builds_round_trips() {
90 let json = serde_json::to_string(&report(Some(BundleBuildUsage { requested: 3 }))).unwrap();
91 let parsed: UsageReport = serde_json::from_str(&json).unwrap();
92 assert_eq!(
93 parsed.bundle_builds,
94 Some(BundleBuildUsage { requested: 3 })
95 );
96 }
97}