detcore_model/
backend_engagement.rs1use serde::Deserialize;
12use serde::Serialize;
13
14#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(tag = "backend", rename_all = "lowercase", deny_unknown_fields)]
21pub enum BackendEngagement {
22 Ptrace {
23 scheduler_turns: u64,
24 },
25 E9patch {
26 candidate_sites: u64,
27 mapped_sites: u64,
28 b0_sites: u64,
29 },
30 Dbt {
31 counted_branches: u64,
32 },
33}
34
35#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
37#[serde(deny_unknown_fields)]
38pub struct BackendEngagementReport {
39 pub schema: u8,
40 pub engagement: BackendEngagement,
41}
42
43impl BackendEngagementReport {
44 pub const SCHEMA: u8 = 2;
45
46 pub const fn new(engagement: BackendEngagement) -> Self {
47 Self {
48 schema: Self::SCHEMA,
49 engagement,
50 }
51 }
52
53 pub fn validate(&self) -> Result<(), String> {
54 if self.schema != Self::SCHEMA {
55 return Err(format!(
56 "backend-engagement schema must be {}, got {}",
57 Self::SCHEMA,
58 self.schema
59 ));
60 }
61 Ok(())
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn backend_and_counter_cannot_be_recombined() {
71 let report = BackendEngagementReport::new(BackendEngagement::E9patch {
72 candidate_sites: 7,
73 mapped_sites: 7,
74 b0_sites: 0,
75 });
76 let json = serde_json::to_string(&report).unwrap();
77 assert_eq!(
78 json,
79 r#"{"schema":2,"engagement":{"backend":"e9patch","candidate_sites":7,"mapped_sites":7,"b0_sites":0}}"#
80 );
81 let changed =
82 serde_json::to_string(&BackendEngagementReport::new(BackendEngagement::E9patch {
83 candidate_sites: 8,
84 mapped_sites: 8,
85 b0_sites: 0,
86 }))
87 .unwrap();
88 assert_ne!(json, changed, "the producer's value must reach the record");
89
90 let mismatched = r#"{"schema":2,"engagement":{"backend":"e9patch","counted_branches":7}}"#;
91 let error = serde_json::from_str::<BackendEngagementReport>(mismatched).unwrap_err();
92 assert!(
93 error
94 .to_string()
95 .contains("unknown field `counted_branches`")
96 );
97 }
98
99 #[test]
100 fn unsupported_schema_refuses_by_name() {
101 let report = BackendEngagementReport {
102 schema: 3,
103 engagement: BackendEngagement::Dbt {
104 counted_branches: 11,
105 },
106 };
107 assert_eq!(
108 report.validate().unwrap_err(),
109 "backend-engagement schema must be 2, got 3"
110 );
111 }
112 #[test]
113 fn e9patch_result_requires_all_three_counts() {
114 for incomplete in [
115 r#"{"schema":2,"engagement":{"backend":"e9patch","mapped_sites":7,"b0_sites":0}}"#,
116 r#"{"schema":2,"engagement":{"backend":"e9patch","candidate_sites":7,"b0_sites":0}}"#,
117 r#"{"schema":2,"engagement":{"backend":"e9patch","candidate_sites":7,"mapped_sites":7}}"#,
118 ] {
119 assert!(serde_json::from_str::<BackendEngagementReport>(incomplete).is_err());
120 }
121 }
122}