1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum ImageSource {
7 Build { base: String },
8 Prebuilt { ref_str: String },
9}
10
11impl ImageSource {
12 pub fn is_prebuilt(&self) -> bool {
13 matches!(self, Self::Prebuilt { .. })
14 }
15
16 pub fn is_build(&self) -> bool {
17 matches!(self, Self::Build { .. })
18 }
19}
20
21#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
26#[serde(rename_all = "lowercase")]
27pub enum PackageManager {
28 #[default]
29 Dnf,
30 Apt,
31 Pacman,
32 Apk,
33 Zypper,
34}
35
36impl PackageManager {
37 pub const fn as_str(&self) -> &'static str {
39 match self {
40 Self::Dnf => "dnf",
41 Self::Apt => "apt",
42 Self::Pacman => "pacman",
43 Self::Apk => "apk",
44 Self::Zypper => "zypper",
45 }
46 }
47
48 pub fn from_str_opt(s: &str) -> Option<Self> {
51 match s {
52 "dnf" => Some(Self::Dnf),
53 "apt" => Some(Self::Apt),
54 "pacman" => Some(Self::Pacman),
55 "apk" => Some(Self::Apk),
56 "zypper" => Some(Self::Zypper),
57 _ => None,
58 }
59 }
60}
61
62impl std::fmt::Display for PackageManager {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(f, "{}", self.as_str())
65 }
66}
67
68impl FromStr for PackageManager {
69 type Err = String;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 Self::from_str_opt(s.trim()).ok_or_else(|| format!("unknown package manager: {s}"))
73 }
74}
75
76#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
77pub enum GpuMode {
78 #[default]
79 Auto,
80 Enabled,
81 Disabled,
82 Nvidia,
83}
84
85impl<'de> Deserialize<'de> for GpuMode {
86 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87 where
88 D: serde::Deserializer<'de>,
89 {
90 use serde::de;
91
92 struct GpuModeVisitor;
93
94 impl<'de> de::Visitor<'de> for GpuModeVisitor {
95 type Value = GpuMode;
96
97 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98 f.write_str("true, false, \"auto\", or \"nvidia\"")
99 }
100
101 fn visit_bool<E: de::Error>(self, v: bool) -> Result<GpuMode, E> {
102 Ok(if v {
103 GpuMode::Enabled
104 } else {
105 GpuMode::Disabled
106 })
107 }
108
109 fn visit_str<E: de::Error>(self, v: &str) -> Result<GpuMode, E> {
110 match v {
111 "auto" => Ok(GpuMode::Auto),
112 "nvidia" => Ok(GpuMode::Nvidia),
113 "true" => Ok(GpuMode::Enabled),
114 "false" => Ok(GpuMode::Disabled),
115 _ => Err(de::Error::unknown_variant(
116 v,
117 &["auto", "nvidia", "true", "false"],
118 )),
119 }
120 }
121 }
122
123 deserializer.deserialize_any(GpuModeVisitor)
124 }
125}
126
127impl Serialize for GpuMode {
128 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129 where
130 S: serde::Serializer,
131 {
132 match self {
133 GpuMode::Auto => serializer.serialize_str("auto"),
134 GpuMode::Enabled => serializer.serialize_bool(true),
135 GpuMode::Disabled => serializer.serialize_bool(false),
136 GpuMode::Nvidia => serializer.serialize_str("nvidia"),
137 }
138 }
139}
140
141#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
145#[serde(rename_all = "lowercase")]
146pub enum CapPreset {
147 None,
149 #[default]
152 Default,
153 Monitoring,
156 Admin,
159}
160
161impl CapPreset {
162 pub fn caps(&self) -> &'static [&'static str] {
163 match self {
164 Self::None => &[],
165 Self::Default => &["DAC_READ_SEARCH", "PERFMON"],
166 Self::Monitoring => &["DAC_READ_SEARCH", "PERFMON", "SYS_PTRACE", "SYS_NICE"],
167 Self::Admin => &[
168 "DAC_READ_SEARCH",
169 "PERFMON",
170 "SYS_PTRACE",
171 "SYS_NICE",
172 "SYS_ADMIN",
173 "NET_ADMIN",
174 "SYSLOG",
175 ],
176 }
177 }
178}
179
180#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
181#[serde(rename_all = "lowercase")]
182pub enum OnStop {
183 #[default]
184 Keep,
185 Remove,
186}
187
188#[derive(Debug, Deserialize, Serialize, Clone)]
189#[serde(untagged)]
190pub enum XdgDirValue {
191 Simple(bool),
192 Detailed {
193 enabled: bool,
194 #[serde(default)]
195 read_write: bool,
196 },
197}
198
199impl Default for XdgDirValue {
200 fn default() -> Self {
201 XdgDirValue::Simple(false)
202 }
203}
204
205impl XdgDirValue {
206 pub fn is_enabled(&self) -> bool {
207 match self {
208 XdgDirValue::Simple(b) => *b,
209 XdgDirValue::Detailed { enabled, .. } => *enabled,
210 }
211 }
212
213 pub fn is_read_write(&self) -> bool {
214 match self {
215 XdgDirValue::Simple(_) => false,
216 XdgDirValue::Detailed { read_write, .. } => *read_write,
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use crate::config::Config;
224
225 use super::*;
226
227 #[test]
228 fn test_gpu_mode_parses_true() {
229 let toml = r#"
230[image]
231base = "fedora:41"
232name = "env"
233[container]
234name = "env"
235home = "~/env"
236[integration]
237gpu = true
238"#;
239 let cfg: Config = toml::from_str(toml).unwrap();
240 assert_eq!(cfg.integration.gpu, GpuMode::Enabled);
241 }
242
243 #[test]
244 fn test_gpu_mode_parses_false() {
245 let toml = r#"
246[image]
247base = "fedora:41"
248name = "env"
249[container]
250name = "env"
251home = "~/env"
252[integration]
253gpu = false
254"#;
255 let cfg: Config = toml::from_str(toml).unwrap();
256 assert_eq!(cfg.integration.gpu, GpuMode::Disabled);
257 }
258
259 #[test]
260 fn test_gpu_mode_parses_auto_string() {
261 let toml = r#"
262[image]
263base = "fedora:41"
264name = "env"
265[container]
266name = "env"
267home = "~/env"
268[integration]
269gpu = "auto"
270"#;
271 let cfg: Config = toml::from_str(toml).unwrap();
272 assert_eq!(cfg.integration.gpu, GpuMode::Auto);
273 }
274
275 #[test]
276 fn test_gpu_mode_parses_nvidia_string() {
277 let toml = r#"
278[image]
279base = "fedora:41"
280name = "env"
281[container]
282name = "env"
283home = "~/env"
284[integration]
285gpu = "nvidia"
286"#;
287 let cfg: Config = toml::from_str(toml).unwrap();
288 assert_eq!(cfg.integration.gpu, GpuMode::Nvidia);
289 }
290
291 #[test]
292 fn test_gpu_mode_serialize() {
293 assert_eq!(serde_json::to_string(&GpuMode::Auto).unwrap(), "\"auto\"");
294 assert_eq!(serde_json::to_string(&GpuMode::Enabled).unwrap(), "true");
295 assert_eq!(serde_json::to_string(&GpuMode::Disabled).unwrap(), "false");
296 assert_eq!(
297 serde_json::to_string(&GpuMode::Nvidia).unwrap(),
298 "\"nvidia\""
299 );
300 #[derive(Serialize)]
301 struct Wrapper {
302 gpu: GpuMode,
303 }
304 let wrapper = Wrapper {
305 gpu: GpuMode::Nvidia,
306 };
307 let toml_out = toml::to_string(&wrapper).unwrap();
308 assert!(toml_out.contains("gpu = \"nvidia\""));
309 let wrapper = Wrapper {
310 gpu: GpuMode::Enabled,
311 };
312 let toml_out = toml::to_string(&wrapper).unwrap();
313 assert!(toml_out.contains("gpu = true"));
314 }
315
316 #[test]
317 fn test_gpu_mode_deserialize_toml_key() {
318 let cases = [
319 ("gpu = true", GpuMode::Enabled),
320 ("gpu = false", GpuMode::Disabled),
321 ("gpu = \"auto\"", GpuMode::Auto),
322 ("gpu = \"nvidia\"", GpuMode::Nvidia),
323 ];
324 for (toml_snippet, expected) in &cases {
325 let full = format!(
326 r#"
327[image]
328base = "fedora:41"
329name = "env"
330[container]
331name = "env"
332home = "~/env"
333[integration]
334{}
335"#,
336 toml_snippet
337 );
338 let cfg: Config = toml::from_str(&full).unwrap();
339 assert_eq!(cfg.integration.gpu, *expected);
340 }
341 }
342}