qemu_command_builder/args/
device.rs1use crate::parsers::ARG_DEVICE;
2use crate::parsers::DELIM_COMMA;
3use crate::shell_string::ShellString;
4use crate::to_command::ToCommand;
5use bon::Builder;
6use proptest_derive::Arbitrary;
7use std::collections::BTreeMap;
8use std::str::FromStr;
9
10#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
12pub struct DeviceProperty {
13 key: ShellString,
14 value: Option<ShellString>,
15}
16
17impl DeviceProperty {
18 pub fn with_value(key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
20 Self {
21 key: ShellString::from(key.as_ref()),
22 value: Some(ShellString::from(value.as_ref())),
23 }
24 }
25
26 pub fn flag(key: impl AsRef<str>) -> Self {
28 Self {
29 key: ShellString::from(key.as_ref()),
30 value: None,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
40pub struct Device {
41 device: ShellString,
42 #[builder(default)]
43 properties: BTreeMap<ShellString, Option<ShellString>>,
44 json: Option<String>,
45}
46
47impl Device {
48 pub fn new<S: AsRef<str>>(device: S) -> Self {
50 Device {
51 device: ShellString::from(device.as_ref()),
52 properties: Default::default(),
53 json: None,
54 }
55 }
56
57 pub fn arm_smmuv3() -> Self {
61 Self::new("arm-smmuv3")
62 }
63 pub fn from_json(json: impl Into<String>) -> Result<Self, String> {
65 let json = json.into();
66 if !json.trim().starts_with('{') || !json.trim().ends_with('}') {
67 return Err("-device JSON must be a JSON object".to_string());
68 }
69 Ok(Self {
70 device: ShellString::from(""),
71 properties: BTreeMap::new(),
72 json: Some(json),
73 })
74 }
75
76 pub fn add_prop<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) -> &mut Self {
78 self.properties.insert(ShellString::from(key.as_ref()), Some(ShellString::from(value.as_ref())));
79 self
80 }
81
82 pub fn add_flag<K: AsRef<str>>(&mut self, key: K) -> &mut Self {
84 self.properties.insert(ShellString::from(key.as_ref()), None);
85 self
86 }
87}
88
89impl ToCommand for Device {
90 fn command(&self) -> String {
91 ARG_DEVICE.to_string()
92 }
93
94 fn to_args(&self) -> Vec<String> {
95 if let Some(json) = &self.json {
96 return vec![json.clone()];
97 }
98 let mut args = vec![self.device.as_ref().to_string()];
99
100 for (prop_key, prop_value) in &self.properties {
101 match prop_value {
102 Some(value) => args.push(format!("{}={}", prop_key.as_ref(), value.as_ref())),
103 None => args.push(prop_key.as_ref().to_string()),
104 }
105 }
106
107 vec![args.join(DELIM_COMMA)]
108 }
109}
110
111impl FromStr for Device {
112 type Err = String;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 if s.trim().starts_with('{') {
116 return Self::from_json(s);
117 }
118 let mut parts = s.split(DELIM_COMMA);
119 let device = parts.next().ok_or_else(|| "empty device argument".to_string())?;
120 if device.is_empty() {
121 return Err("missing device driver".to_string());
122 }
123
124 let mut properties = BTreeMap::new();
125 for part in parts {
126 match part.split_once('=') {
127 Some((key, value)) => {
128 properties.insert(ShellString::from(key), Some(ShellString::from_str(value)?));
129 }
130 None => {
131 properties.insert(ShellString::from(part), None);
132 }
133 }
134 }
135
136 Ok(Device {
137 device: ShellString::from(device),
138 properties,
139 json: None,
140 })
141 }
142}