kernel/event/record/
renewal.rs1use serde::{Deserialize, Serialize};
14
15use crate::budget::{Tokens, UsdMicros};
16use crate::error::AxError;
17use crate::model::{CacheCount, ModelUsage};
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[serde(untagged)]
30pub enum CacheRenewed {
31 Refused {
33 refused: AxError,
35 },
36 Answered {
38 #[serde(default, skip_serializing_if = "Option::is_none")]
41 #[cfg_attr(
42 feature = "schema",
43 schemars(with = "Option<std::collections::BTreeMap<String, u64>>")
44 )]
45 usage: Option<ModelUsage>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 billed_usd_micros: Option<UsdMicros>,
49 },
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum RenewalOutcome {
58 Read,
61 Late,
65 Unreported,
67 Refused,
69}
70
71impl CacheRenewed {
72 pub fn outcome(&self) -> RenewalOutcome {
74 match self {
75 CacheRenewed::Refused { .. } => RenewalOutcome::Refused,
76 CacheRenewed::Answered { usage, .. } => {
77 match usage.map(|usage| usage.cache_write_tokens) {
78 None | Some(CacheCount::Unreported) => RenewalOutcome::Unreported,
79 Some(CacheCount::Reported(written)) if written == Tokens::new(0) => {
80 RenewalOutcome::Read
81 }
82 Some(CacheCount::Reported(_)) => RenewalOutcome::Late,
83 }
84 }
85 }
86 }
87}
88
89#[cfg(test)]
90#[allow(clippy::unwrap_used, reason = "test code")]
91mod tests {
92 use super::*;
93 use crate::event::Payload;
94
95 #[test]
96 fn a_refusal_reads_back_as_a_refusal_and_an_empty_answer_as_an_answer() {
97 let refused = CacheRenewed::Refused {
98 refused: AxError::failure(
99 crate::error::AxCode::Provider,
100 "renew a cached prefix",
101 "rate limited",
102 )
103 .with_recovery("renewal is skipped until the next real request"),
104 };
105 let answered = CacheRenewed::Answered {
106 usage: None,
107 billed_usd_micros: None,
108 };
109 for line in [refused, answered] {
110 assert_eq!(
111 Payload::of(&line).unwrap().read::<CacheRenewed>().unwrap(),
112 line
113 );
114 }
115 }
116
117 #[test]
118 fn a_renewal_that_wrote_the_cache_reads_as_late() {
119 let answered = |written: CacheCount| CacheRenewed::Answered {
120 usage: Some(ModelUsage {
121 cache_write_tokens: written,
122 ..ModelUsage::default()
123 }),
124 billed_usd_micros: None,
125 };
126 let lines = [
127 answered(CacheCount::Reported(Tokens::new(0))),
128 answered(CacheCount::Reported(Tokens::new(4096))),
129 answered(CacheCount::Unreported),
130 CacheRenewed::Answered {
131 usage: None,
132 billed_usd_micros: None,
133 },
134 CacheRenewed::Refused {
135 refused: AxError::failure(
136 crate::error::AxCode::Provider,
137 "renew a cached prefix",
138 "rate limited",
139 )
140 .with_recovery("renewal is skipped until the next real request"),
141 },
142 ];
143 assert_eq!(
144 lines.iter().map(CacheRenewed::outcome).collect::<Vec<_>>(),
145 vec![
146 RenewalOutcome::Read,
147 RenewalOutcome::Late,
148 RenewalOutcome::Unreported,
149 RenewalOutcome::Unreported,
150 RenewalOutcome::Refused,
151 ]
152 );
153 }
154}