squib_api/schemas/
balloon.rs1use serde::{Deserialize, Serialize};
13
14pub const MAX_STATS_POLL_INTERVAL: u8 = u8::MAX;
17
18#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum BalloonHintingOp {
22 Start,
24 Status,
26 Stop,
28}
29
30impl BalloonHintingOp {
31 pub fn from_url_segment(s: &str) -> Result<Self, String> {
33 match s {
34 "start" => Ok(Self::Start),
35 "status" => Ok(Self::Status),
36 "stop" => Ok(Self::Stop),
37 other => Err(format!(
38 "Invalid balloon-hinting op: must be one of start | status | stop (got {other})"
39 )),
40 }
41 }
42}
43
44#[derive(Debug, Clone, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct RawBalloonConfig {
48 pub amount_mib: u64,
50 #[serde(default)]
52 pub deflate_on_oom: bool,
53 #[serde(default)]
55 pub stats_polling_interval_s: u8,
56 #[serde(default)]
58 pub free_page_hinting: bool,
59 #[serde(default)]
61 pub free_page_reporting: bool,
62}
63
64#[derive(Debug, Clone, Serialize)]
66#[non_exhaustive]
67pub struct BalloonConfig {
68 pub amount_mib: u64,
70 pub deflate_on_oom: bool,
72 pub stats_polling_interval_s: u8,
74 pub free_page_hinting: bool,
76 pub free_page_reporting: bool,
78}
79
80impl TryFrom<RawBalloonConfig> for BalloonConfig {
81 type Error = String;
82
83 fn try_from(raw: RawBalloonConfig) -> Result<Self, Self::Error> {
84 Ok(Self {
86 amount_mib: raw.amount_mib,
87 deflate_on_oom: raw.deflate_on_oom,
88 stats_polling_interval_s: raw.stats_polling_interval_s,
89 free_page_hinting: raw.free_page_hinting,
90 free_page_reporting: raw.free_page_reporting,
91 })
92 }
93}
94
95#[derive(Debug, Clone, Deserialize)]
97#[serde(deny_unknown_fields)]
98pub struct RawBalloonUpdate {
99 pub amount_mib: u64,
101}
102
103#[derive(Debug, Clone, Serialize)]
105#[non_exhaustive]
106pub struct BalloonUpdate {
107 pub amount_mib: u64,
109}
110
111impl TryFrom<RawBalloonUpdate> for BalloonUpdate {
112 type Error = String;
113
114 fn try_from(raw: RawBalloonUpdate) -> Result<Self, Self::Error> {
115 Ok(Self {
116 amount_mib: raw.amount_mib,
117 })
118 }
119}
120
121#[derive(Debug, Clone, Deserialize)]
123#[serde(deny_unknown_fields)]
124pub struct RawBalloonStatsUpdate {
125 pub stats_polling_interval_s: u8,
127}
128
129#[derive(Debug, Clone, Serialize)]
131#[non_exhaustive]
132pub struct BalloonStatsUpdate {
133 pub stats_polling_interval_s: u8,
135}
136
137impl TryFrom<RawBalloonStatsUpdate> for BalloonStatsUpdate {
138 type Error = String;
139
140 fn try_from(raw: RawBalloonStatsUpdate) -> Result<Self, Self::Error> {
141 Ok(Self {
143 stats_polling_interval_s: raw.stats_polling_interval_s,
144 })
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_should_accept_minimal_balloon_config() {
154 let raw = RawBalloonConfig {
155 amount_mib: 0,
156 deflate_on_oom: false,
157 stats_polling_interval_s: 0,
158 free_page_hinting: false,
159 free_page_reporting: false,
160 };
161 let cfg = BalloonConfig::try_from(raw).unwrap();
162 assert_eq!(cfg.amount_mib, 0);
163 }
164
165 #[test]
166 fn test_should_reject_oversize_polling_interval() {
167 let raw = RawBalloonConfig {
170 amount_mib: 0,
171 deflate_on_oom: false,
172 stats_polling_interval_s: u8::MAX,
173 free_page_hinting: false,
174 free_page_reporting: false,
175 };
176 assert!(BalloonConfig::try_from(raw).is_ok());
177 }
178
179 #[test]
180 fn test_should_round_trip_balloon_update() {
181 let raw = RawBalloonUpdate { amount_mib: 128 };
182 let upd = BalloonUpdate::try_from(raw).unwrap();
183 assert_eq!(upd.amount_mib, 128);
184 }
185}