Skip to main content

qemu_command_builder/args/
network_compat.rs

1use crate::parsers::{ARG_NET, ARG_NIC, DELIM_COMMA};
2use crate::to_command::ToCommand;
3use bon::Builder;
4use proptest_derive::Arbitrary;
5use std::str::FromStr;
6
7/// An order-preserving property used by the broad `-nic` and legacy `-net` syntaxes.
8#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
9pub struct NetworkProperty {
10    pub key: String,
11    pub value: Option<String>,
12}
13
14/// QEMU's shortcut for creating a host backend and an on-board NIC together.
15#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
16pub struct Nic {
17    backend: String,
18    #[builder(default)]
19    properties: Vec<NetworkProperty>,
20}
21
22/// The compatibility `-net` interface retained by QEMU 11.1.
23#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
24pub struct LegacyNet {
25    kind: String,
26    #[builder(default)]
27    properties: Vec<NetworkProperty>,
28}
29
30macro_rules! impl_network_option {
31    ($type:ty, $field:ident, $arg:expr) => {
32        impl ToCommand for $type {
33            fn command(&self) -> String {
34                $arg.to_string()
35            }
36
37            fn to_args(&self) -> Vec<String> {
38                let mut parts = vec![self.$field.clone()];
39                for property in &self.properties {
40                    parts.push(match &property.value {
41                        Some(value) => format!("{}={}", property.key, value),
42                        None => property.key.clone(),
43                    });
44                }
45                vec![parts.join(DELIM_COMMA)]
46            }
47        }
48
49        impl FromStr for $type {
50            type Err = String;
51
52            fn from_str(value: &str) -> Result<Self, Self::Err> {
53                let mut parts = value.split(DELIM_COMMA);
54                let first = parts.next().filter(|part| !part.is_empty()).ok_or_else(|| format!("empty {} argument", $arg))?;
55                let properties = parts
56                    .map(|part| match part.split_once('=') {
57                        Some((key, value)) if !key.is_empty() => Ok(NetworkProperty {
58                            key: key.to_string(),
59                            value: Some(value.to_string()),
60                        }),
61                        None if !part.is_empty() => Ok(NetworkProperty { key: part.to_string(), value: None }),
62                        _ => Err(format!("invalid {} property: {part}", $arg)),
63                    })
64                    .collect::<Result<Vec<_>, _>>()?;
65                Ok(Self {
66                    $field: first.to_string(),
67                    properties,
68                })
69            }
70        }
71    };
72}
73
74impl_network_option!(Nic, backend, ARG_NIC);
75impl_network_option!(LegacyNet, kind, ARG_NET);