Skip to main content

qemu_command_builder/args/
object.rs

1use crate::parsers::{ARG_OBJECT, DELIM_COMMA};
2use crate::to_command::ToCommand;
3use bon::Builder;
4use proptest_derive::Arbitrary;
5use std::str::FromStr;
6
7/// A generic QEMU `-object typename[,prop=value,...]` definition.
8#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
9pub struct Object {
10    /// The QOM object type name.
11    typename: String,
12    /// Object properties emitted in the order stored.
13    properties: Vec<(String, String)>,
14    /// Raw QMP JSON form, preserved verbatim when present.
15    json: Option<String>,
16}
17
18impl Object {
19    pub fn new<S: AsRef<str>>(typename: S) -> Self {
20        Object {
21            typename: typename.as_ref().to_string(),
22            properties: Default::default(),
23            json: None,
24        }
25    }
26    /// Creates an object from QEMU's JSON command-line form.
27    pub fn from_json(json: impl Into<String>) -> Result<Self, String> {
28        let json = json.into();
29        if !json.trim().starts_with('{') || !json.trim().ends_with('}') {
30            return Err("-object JSON must be a JSON object".to_string());
31        }
32        Ok(Self {
33            typename: String::new(),
34            properties: Vec::new(),
35            json: Some(json),
36        })
37    }
38    pub fn add_prop<S: AsRef<str>>(&mut self, key: S, value: S) -> &mut Self {
39        self.properties.push((key.as_ref().to_string(), value.as_ref().to_string()));
40        self
41    }
42
43    /// Creates a QEMU 11.1 HMP monitor object.
44    pub fn monitor_hmp(id: impl AsRef<str>, chardev: impl AsRef<str>) -> Self {
45        let mut value = Self::new("monitor-hmp");
46        value.add_prop("id", id.as_ref()).add_prop("chardev", chardev.as_ref());
47        value
48    }
49
50    /// Creates a QEMU 11.1 QMP monitor object.
51    pub fn monitor_qmp(id: impl AsRef<str>, chardev: impl AsRef<str>) -> Self {
52        let mut value = Self::new("monitor-qmp");
53        value.add_prop("id", id.as_ref()).add_prop("chardev", chardev.as_ref());
54        value
55    }
56
57    /// Creates an Intel TDX confidential-guest object. Nested socket addresses
58    /// can be supplied with [`Object::from_json`].
59    pub fn tdx_guest(id: impl AsRef<str>) -> Self {
60        let mut value = Self::new("tdx-guest");
61        value.add_prop("id", id.as_ref());
62        value
63    }
64
65    /// Creates an IOThread object; QEMU 11.1's `poll-weight` can be added with
66    /// [`Object::add_prop`].
67    pub fn iothread(id: impl AsRef<str>) -> Self {
68        let mut value = Self::new("iothread");
69        value.add_prop("id", id.as_ref());
70        value
71    }
72}
73
74impl ToCommand for Object {
75    fn command(&self) -> String {
76        ARG_OBJECT.to_string()
77    }
78    fn to_args(&self) -> Vec<String> {
79        if let Some(json) = &self.json {
80            return vec![json.clone()];
81        }
82        let mut args = vec![self.typename.clone()];
83
84        for (prop_key, prop_value) in &self.properties {
85            args.push(format!("{}={}", prop_key, prop_value));
86        }
87        vec![args.join(DELIM_COMMA)]
88    }
89}
90
91impl FromStr for Object {
92    type Err = String;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        if s.trim().starts_with('{') {
96            return Self::from_json(s);
97        }
98        let mut parts = s.split(DELIM_COMMA);
99        let typename = parts.next().ok_or_else(|| "empty -object argument".to_string())?.to_string();
100
101        let mut properties = Vec::new();
102        for part in parts {
103            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid -object property: {part}"))?;
104            properties.push((key.to_string(), value.to_string()));
105        }
106
107        Ok(Self { typename, properties, json: None })
108    }
109}