forked from proxmox-mirrors/proxmox
schema: support PVE's "keyAlias" legacy property strings
Because the UI kept producing them... And still does... This is NOT meant to be used by anything other than generated legacy code for PDM (pve-api-types) and only affects the serde based deserializer. The "old" `schema.parse_property_string() -> Value` method does not utilize this for now. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
4378d44bcb
commit
dc232f8bd4
@ -498,6 +498,9 @@ pub struct MapAccess<'de, 'i> {
|
||||
|
||||
/// The current next value's key, value and schema (if available).
|
||||
value: Option<(Cow<'de, str>, Cow<'de, str>, Option<&'static Schema>)>,
|
||||
|
||||
/// We just returned the key-value pair of a keyAlias:
|
||||
was_alias: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl<'de, 'i> MapAccess<'de, 'i> {
|
||||
@ -508,6 +511,7 @@ impl<'de, 'i> MapAccess<'de, 'i> {
|
||||
schema,
|
||||
input_at: 0,
|
||||
value: None,
|
||||
was_alias: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,6 +525,7 @@ impl<'de, 'i> MapAccess<'de, 'i> {
|
||||
schema,
|
||||
input_at: 0,
|
||||
value: None,
|
||||
was_alias: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -534,8 +539,26 @@ impl<'de, 'i> MapAccess<'de, 'i> {
|
||||
schema,
|
||||
input_at: 0,
|
||||
value: None,
|
||||
was_alias: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns (key, value), since the key exists as a static string in the KeyAliasInfo, we
|
||||
/// return the static one instead of the passed parameter, this simplifies later lifetime
|
||||
/// handling.
|
||||
fn try_key_alias_info(
|
||||
&self,
|
||||
key: Option<&str>,
|
||||
) -> Option<(&'static str, &'static str, &'static str)> {
|
||||
let key = key?;
|
||||
let info = self.schema.key_alias_info()?;
|
||||
|
||||
let Ok(index) = info.values.binary_search(&key) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((info.key_alias, info.values[index], info.alias))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::MapAccess<'de> for MapAccess<'de, '_> {
|
||||
@ -552,11 +575,30 @@ impl<'de> de::MapAccess<'de> for MapAccess<'de, '_> {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (key, value, rem) = match next_property(&self.input[self.input_at..]) {
|
||||
let (mut key, value, rem) = match next_property(&self.input[self.input_at..]) {
|
||||
None => return Ok(None),
|
||||
Some(entry) => entry?,
|
||||
};
|
||||
|
||||
if let Some(alias) = std::mem::take(&mut self.was_alias) {
|
||||
key = Some(alias);
|
||||
} else if let Some((key, value, alias)) = self.try_key_alias_info(key) {
|
||||
// If the object schema has a "KeyAliasInfo", take a detour through it: if our
|
||||
// "key" is defined in it, we first return the key as a value for its declared
|
||||
// `keyAlias`.
|
||||
self.was_alias = Some(alias);
|
||||
let schema = self
|
||||
.schema
|
||||
.lookup(key)
|
||||
.ok_or(Error::msg("key alias info pointed to key a without schema"))?
|
||||
.1;
|
||||
|
||||
let out =
|
||||
seed.deserialize(de::value::BorrowedStrDeserializer::<'de, Error>::new(key))?;
|
||||
self.value = Some((Cow::Borrowed(key), Cow::Borrowed(value), Some(schema)));
|
||||
return Ok(Some(out));
|
||||
}
|
||||
|
||||
if rem.is_empty() {
|
||||
self.input_at = self.input.len();
|
||||
} else {
|
||||
|
@ -468,4 +468,51 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct NetworkCard {
|
||||
model: String,
|
||||
macaddr: String,
|
||||
disconnected: Option<bool>,
|
||||
}
|
||||
|
||||
impl ApiType for NetworkCard {
|
||||
const API_SCHEMA: Schema = ObjectSchema::new(
|
||||
"A network card",
|
||||
&[
|
||||
// MUST BE SORTED
|
||||
(
|
||||
"disconnected",
|
||||
true,
|
||||
&BooleanSchema::new("disconnected").schema(),
|
||||
),
|
||||
("macaddr", false, &StringSchema::new("macaddr").schema()),
|
||||
("model", false, &StringSchema::new("model").schema()),
|
||||
],
|
||||
)
|
||||
.key_alias_info(crate::schema::KeyAliasInfo::new(
|
||||
"model",
|
||||
&["e1000", "virtio"],
|
||||
"macaddr",
|
||||
))
|
||||
.schema();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_alias_info() -> Result<(), super::Error> {
|
||||
let deserialized: NetworkCard = super::parse("virtio=aa:bb:cc:dd:ee,disconnected=0")
|
||||
.expect("failed to parse property string");
|
||||
|
||||
assert_eq!(
|
||||
deserialized,
|
||||
NetworkCard {
|
||||
model: "virtio".to_string(),
|
||||
macaddr: "aa:bb:cc:dd:ee".to_string(),
|
||||
disconnected: Some(false),
|
||||
},
|
||||
"KeyAliasInfo deserialization failed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -604,6 +604,31 @@ pub type SchemaPropertyEntry = (&'static str, bool, &'static Schema);
|
||||
/// This is a workaround unless RUST can const_fn `Hash::new()`
|
||||
pub type SchemaPropertyMap = &'static [SchemaPropertyEntry];
|
||||
|
||||
/// Legacy property strings may contain shortcuts where the *value* of a specific key is used as a
|
||||
/// *key* for yet another option. Most notably, PVE's `netX` properties use `<model>=<macaddr>`
|
||||
/// instead of `model=<model>,macaddr=<macaddr>`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
|
||||
pub struct KeyAliasInfo {
|
||||
pub key_alias: &'static str,
|
||||
pub values: &'static [&'static str],
|
||||
pub alias: &'static str,
|
||||
}
|
||||
|
||||
impl KeyAliasInfo {
|
||||
pub const fn new(
|
||||
key_alias: &'static str,
|
||||
values: &'static [&'static str],
|
||||
alias: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
key_alias,
|
||||
values,
|
||||
alias,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data type to describe objects (maps).
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
|
||||
@ -616,6 +641,13 @@ pub struct ObjectSchema {
|
||||
pub properties: SchemaPropertyMap,
|
||||
/// Default key name - used by `parse_parameter_string()`
|
||||
pub default_key: Option<&'static str>,
|
||||
/// DO NOT USE!
|
||||
///
|
||||
/// This is meant for the PVE schema generator ONLY!
|
||||
///
|
||||
/// This is to support legacy property string information: declare a `keyAlias` and its
|
||||
/// corresponding `alias` property (as defined in PVE's schema).
|
||||
pub key_alias_info: Option<KeyAliasInfo>,
|
||||
}
|
||||
|
||||
impl ObjectSchema {
|
||||
@ -625,6 +657,7 @@ impl ObjectSchema {
|
||||
properties,
|
||||
additional_properties: false,
|
||||
default_key: None,
|
||||
key_alias_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,6 +698,17 @@ impl ObjectSchema {
|
||||
) -> Result<Value, ParameterError> {
|
||||
ParameterSchema::from(self).parse_parameter_strings(data, test_required)
|
||||
}
|
||||
|
||||
/// DO NOT USE!
|
||||
///
|
||||
/// This is meant for the PVE schema generator ONLY!
|
||||
///
|
||||
/// This is to support legacy property string information: declare a `keyAlias` and its
|
||||
/// corresponding `alias` property (as defined in PVE's schema).
|
||||
pub const fn key_alias_info(mut self, key_alias_info: KeyAliasInfo) -> Self {
|
||||
self.key_alias_info = Some(key_alias_info);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines multiple *object* schemas into one.
|
||||
@ -822,6 +866,11 @@ pub trait ObjectSchemaType: private::Sealed + Send + Sync {
|
||||
fn additional_properties(&self) -> bool;
|
||||
fn default_key(&self) -> Option<&'static str>;
|
||||
|
||||
/// Should always return `None`, unless dealing with *legacy* PVE property strings.
|
||||
fn key_alias_info(&self) -> Option<KeyAliasInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Verify JSON value using an object schema.
|
||||
fn verify_json(&self, data: &Value) -> Result<(), Error> {
|
||||
let map = match data {
|
||||
@ -905,6 +954,10 @@ impl ObjectSchemaType for ObjectSchema {
|
||||
fn default_key(&self) -> Option<&'static str> {
|
||||
self.default_key
|
||||
}
|
||||
|
||||
fn key_alias_info(&self) -> Option<KeyAliasInfo> {
|
||||
self.key_alias_info
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectSchemaType for AllOfSchema {
|
||||
|
@ -25,6 +25,7 @@ fn test_schema1() {
|
||||
additional_properties: false,
|
||||
properties: &[],
|
||||
default_key: None,
|
||||
key_alias_info: None,
|
||||
});
|
||||
|
||||
println!("TEST Schema: {:?}", schema);
|
||||
|
Loading…
Reference in New Issue
Block a user