public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types
@ 2025-11-13 15:09 Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 1/4] pve-api-types: add FixedString type Stefan Hanreich
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

## Introduction

This patch series adds a new fallback variant to all enums that are
auto-generated in pve-api-types. This ensures that the addition of new enum
variants in the Proxmox VE API does not break the deserialization of Proxmox VE
API types in PDM. Rather, the unknown enum variant is now populated with the
parsed enum variant name. For that matter this patch series introduces a new
string type, FixedString. It can store up to 23 characters and is Copy, which is
the main reason for introducing this custom string type. This lets us keep Copy
in our generated enums.


## Open Questions

### Resubmitting unknown variants

I had some off list discussions with Dominik and Dietmar regarding how consumers
of the API (mainly the UI) could handle encountering unknown values when there
is the possibility of re-submission of this data. Two main options emerged:

* Resubmitting unknown values as-is, without preventing the user from doing so

This has the upside of not preventing the user from submitting perfectly fine
values if they haven't been implemented in PDM. An argument in favor would be
that the PVE API should act as the last safeguard against invalid objects in the
configuration anyway, so we could rely on PVE validation only in the case of
unknown enums.
The downside is that we need a round-trip to the Proxmox VE API and can only
show the resulting error in the UI, without any validation. It also does not
take care of situations where setting a certain, new, enum potentially requires
special behavior when submitting values.

* Showing the value, but preventing the user from re-submitting the value

This is the safest option, since it prevents any, potentially destructive,
action from the user due to the consumer not being aware of special . It also
gives the user immediate feedback in the UI, without requiring a round-trip to
the Proxmox VE instance, but of course users lose the ability to submit
perfectly valid configurations (without upgrading).


The answer is, of course, it depends on the context - but in the general case it
seems preferable to me to show the value to the user, but prevent the user from
re-submitting the value, since the UI cannot know if there is some special
handling required in case the new enum variant is used in the UI. If there is a
good argument for letting the user re-submit certain fields, then that seems
like a fine option too, but imo *iff* there's good reasoning attached to it.

What do you think? Any options that were missed?


### Storing unknown Variants

When only reading and displaying / storing data, then including unknown values
is desired behavior imo (again, in the general case) - since it allows storing
data (e.g. metrics) that isn't yet understood but could potentially be
understood perfectly fine after an upgrade of PDM.


### Unsafe FixedString

I implemented as_bytes and as_str in FixedString via the unsafe variants of the
methods and included tests that check for correct behavior of those methods.
Using the safe variants and panicking here should be fine too imo if we don't
want to go down that road. FixedString should only get used in rare, exceptional
cases anyway - and even then not in a hot path. The adaption should be trivial.


## Adapting the existing code

One difficulty when adapting the UI was identifying places in which a struct
gets returned directly from the API, deserialized (but the value isn't used
anywhere in the frontend), then serialized again and sent back to the backend.
Since the FixedString serializes indistinguishable from a String, this could
introduce situations where we submit unkown enum values when instead an error
should be shown and the user prevented from submitting a form.

I tried to look at each component in the backend / frontend and check if it uses
and potentially re-submits am enum without checking its variants for unknown
values. Since we model enums with checkboxes in the UI and have fixed values
there, this should usually work and prevent users from submitting values that
are unknown to the UI. Nevertheless, it's very much possible I missed some
places, so here some group effort in trying to identify potentially problematic
spots would be nice.


## Maintainers Notes

pve-api-types breaks building:
* proxmox-datacenter-manager
* proxmox-yew-comp
* pve-yew-mobile-gui

proxmox-datacenter-manager and pve-yew-mobile-gui additionally need the newer
version of proxmox-yew-comp to build.


## Changelog

Changes from v1 (Thanks @Wolfgang):
* remove truncate constructor
* use macros to derive some of the traits by forwarding
* cleaned up std::fmt imports
* changed error type to zero-sized struct
* add AsRef<[u8]> and Borrow<str> impls
* improved deserialize implementation

proxmox:

Stefan Hanreich (4):
  pve-api-types: add FixedString type
  pve-api-types: generate fallback variant for enums
  pve-api-types: regenerate
  pve-api-types: sdn: handle fallback variant

 pve-api-types/generator-lib/Schema2Rust.pm |   3 +
 pve-api-types/src/generated/types.rs       | 210 ++++++++++++++++
 pve-api-types/src/sdn.rs                   |   3 +-
 pve-api-types/src/types/fixed_string.rs    | 274 +++++++++++++++++++++
 pve-api-types/src/types/mod.rs             |   3 +
 5 files changed, 492 insertions(+), 1 deletion(-)
 create mode 100644 pve-api-types/src/types/fixed_string.rs


proxmox-yew-comp:

Stefan Hanreich (1):
  pve: qemu: handle fallback enum variants

 .../qemu_property/qemu_amd_sev_property.rs    |  1 +
 .../pve/qemu_property/qemu_bios_property.rs   |  1 +
 .../qemu_property/qemu_machine_property.rs    | 20 +++++++++++--------
 3 files changed, 14 insertions(+), 8 deletions(-)


proxmox-datacenter-manager:

Stefan Hanreich (1):
  tree-wide: handle new unknown enum variants

 server/src/metric_collection/rrd_task.rs | 4 ++++
 ui/src/pve/utils.rs                      | 1 +
 ui/src/widget/migrate_window.rs          | 6 ++++++
 3 files changed, 11 insertions(+)


pve-yew-mobile-gui:

Stefan Hanreich (1):
  tree-wide: handle fallback enum values

 src/pages/page_resources.rs   | 4 +++-
 src/pages/page_task_status.rs | 7 +++++++
 2 files changed, 10 insertions(+), 1 deletion(-)


Summary over all repositories:
  13 files changed, 527 insertions(+), 10 deletions(-)

-- 
Generated by git-murpp 0.8.0

_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH proxmox v2 1/4] pve-api-types: add FixedString type
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 2/4] pve-api-types: generate fallback variant for enums Stefan Hanreich
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

A custom, immutable, string type, that is copiable, which can hold up
to 23 characters. It will be used later for storing the name of
unknown enum variants when parsing the return value of enum
properties. The main reason for introducing this type is that its copy
as opposed to String, which lets us keep the Copy implementation for
the existing enums.

Main reason for choosing 23 characters is the fact that String is 24
bytes large as well (ptr, len, capacity).

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/src/types/fixed_string.rs | 274 ++++++++++++++++++++++++
 pve-api-types/src/types/mod.rs          |   3 +
 2 files changed, 277 insertions(+)
 create mode 100644 pve-api-types/src/types/fixed_string.rs

diff --git a/pve-api-types/src/types/fixed_string.rs b/pve-api-types/src/types/fixed_string.rs
new file mode 100644
index 00000000..e70e1327
--- /dev/null
+++ b/pve-api-types/src/types/fixed_string.rs
@@ -0,0 +1,274 @@
+use std::borrow::Borrow;
+use std::cmp::Ordering;
+use std::error::Error;
+use std::fmt;
+use std::ops::Deref;
+use std::str::FromStr;
+
+use serde::{Deserialize, Serialize};
+
+/// Error type used by constructors of [`FixedString`]
+#[derive(Clone, Copy, Debug)]
+pub struct TooLongError;
+
+impl Error for TooLongError {}
+
+impl fmt::Display for TooLongError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        f.write_str("string is longer than 23 characters")
+    }
+}
+
+/// An immutable string type with a maximum size of 23 bytes.
+///
+/// After construction it is guaranteed that its contents are:
+/// * valid utf-8
+/// * not longer than 23 characters
+///
+/// FixedString is immutable, therefore it is sufficient to validate the invariants only at
+/// construction time to guarantee that they will always hold during the lifecycle of the
+/// struct.
+#[derive(Clone, Copy)]
+pub struct FixedString {
+    buf: [u8; 23],
+    len: u8,
+}
+
+impl FixedString {
+    /// Creates a new FixedString instance from a str reference.
+    ///
+    /// # Errors
+    /// This function will return an error if:
+    /// * The passed string is longer than 23 bytes
+    pub fn new(value: &str) -> Result<Self, TooLongError> {
+        if value.len() > 23 {
+            return Err(TooLongError);
+        }
+
+        let mut buf = [0; 23];
+        buf[..value.len()].copy_from_slice(value.as_bytes());
+
+        Ok(Self {
+            buf,
+            // SAFETY: self.len is at least 0 and at most 23, which fits into u8
+            len: value.len() as u8,
+        })
+    }
+
+    /// Returns a str reference to the stored data
+    #[inline]
+    pub fn as_str(&self) -> &str {
+        // SAFETY: self.buf must be a valid utf-8 string by construction
+        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
+    }
+
+    /// Returns a reference to the set bytes in the stored buffer
+    #[inline]
+    pub fn as_bytes(&self) -> &[u8] {
+        // SAFETY: self.len >= 0 and self.len <= 23 by construction
+        unsafe { self.buf.get_unchecked(..self.len as usize) }
+    }
+}
+
+macro_rules! forward_impl_to_bytes {
+    ($($trait:ident {$fn:ident -> $out:ty })+) => {
+        $(
+            impl $trait for FixedString {
+                #[inline]
+                fn $fn(&self, other: &Self) -> $out {
+                    <[u8] as $trait>::$fn(self.as_bytes(), other.as_bytes())
+                }
+            }
+        )+
+    };
+}
+
+forward_impl_to_bytes! {
+    PartialEq { eq -> bool }
+    PartialOrd { partial_cmp -> Option<Ordering> }
+    Ord { cmp -> Ordering }
+}
+
+macro_rules! forward_impl_to_str_bidir {
+    ($($trait:ident {$fn:ident -> $out:ty })+) => {
+        $(
+            impl $trait<str> for FixedString {
+                #[inline]
+                fn $fn(&self, other: &str) -> $out {
+                    <str as $trait>::$fn(self.as_str(), other)
+                }
+            }
+
+            impl $trait<FixedString> for &str {
+                #[inline]
+                fn $fn(&self, other: &FixedString) -> $out {
+                    <str as $trait>::$fn(self, other.as_str())
+                }
+            }
+        )+
+    };
+}
+
+forward_impl_to_str_bidir! {
+    PartialEq { eq -> bool }
+    PartialOrd { partial_cmp -> Option<Ordering> }
+}
+
+impl Eq for FixedString {}
+
+impl fmt::Display for FixedString {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        fmt::Display::fmt(self.as_str(), f)
+    }
+}
+
+impl fmt::Debug for FixedString {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        fmt::Display::fmt(self.as_str(), f)
+    }
+}
+
+impl Deref for FixedString {
+    type Target = str;
+
+    fn deref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<str> for FixedString {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<[u8]> for FixedString {
+    fn as_ref(&self) -> &[u8] {
+        self.as_bytes()
+    }
+}
+
+impl Borrow<str> for FixedString {
+    fn borrow(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl TryFrom<String> for FixedString {
+    type Error = TooLongError;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        FixedString::new(value.as_str())
+    }
+}
+
+impl FromStr for FixedString {
+    type Err = TooLongError;
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        FixedString::new(value)
+    }
+}
+
+impl TryFrom<&str> for FixedString {
+    type Error = TooLongError;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        FixedString::new(value)
+    }
+}
+
+impl Serialize for FixedString {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(self.as_str())
+    }
+}
+
+impl<'de> Deserialize<'de> for FixedString {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        struct FixedStringVisitor;
+
+        impl<'de> serde::de::Visitor<'de> for FixedStringVisitor {
+            type Value = FixedString;
+
+            fn expecting(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
+                f.write_str("a string that is at most 23 bytes long")
+            }
+
+            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                v.try_into().map_err(E::custom)
+            }
+        }
+
+        deserializer.deserialize_str(FixedStringVisitor)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use serde_plain;
+
+    #[test]
+    fn test_construct() {
+        let fixed_string = FixedString::new("").expect("empty string is valid");
+        assert_eq!("", fixed_string);
+
+        let fixed_string = FixedString::new("a").expect("valid string");
+        assert_eq!("a", fixed_string);
+
+        let fixed_string = FixedString::new("🌏🌏🌏🌏🌏").expect("valid string");
+        assert_eq!("🌏🌏🌏🌏🌏", fixed_string);
+
+        let fixed_string =
+            FixedString::new("aaaaaaaaaaaaaaaaaaaaaaa").expect("23 characters are allowed");
+        assert_eq!("aaaaaaaaaaaaaaaaaaaaaaa", fixed_string);
+
+        FixedString::new("🌏🌏🌏🌏🌏🌏").expect_err("string too long");
+        FixedString::new("aaaaaaaaaaaaaaaaaaaaaaaa").expect_err("string too long");
+    }
+
+    #[test]
+    fn test_serialize_deserialize() {
+        let valid_string = "aaaaaaaaaaaaaaaaaaaaaaa";
+
+        let fixed_string: FixedString =
+            serde_plain::from_str("aaaaaaaaaaaaaaaaaaaaaaa").expect("deserialization works");
+        assert_eq!(valid_string, fixed_string);
+
+        let serialized_string =
+            serde_plain::to_string(&fixed_string).expect("can be serialized into a string");
+        assert_eq!(valid_string, serialized_string);
+
+        serde_plain::from_str::<FixedString>("aaaaaaaaaaaaaaaaaaaaaaaa")
+            .expect_err("cannot deserialize string that is too long");
+    }
+
+    #[test]
+    fn test_ord() {
+        let fixed_string = FixedString::new("abc").expect("valid string");
+
+        assert!(fixed_string == fixed_string);
+        assert!(fixed_string >= fixed_string);
+        assert!(fixed_string <= fixed_string);
+
+        assert!("ab" < fixed_string);
+        assert!("abc" == fixed_string);
+        assert!("abcd" > fixed_string);
+
+        let larger_fixed_string = FixedString::new("abcde").expect("valid string");
+
+        assert!(larger_fixed_string > fixed_string);
+        assert!(fixed_string < larger_fixed_string);
+    }
+}
diff --git a/pve-api-types/src/types/mod.rs b/pve-api-types/src/types/mod.rs
index fe52a169..63209d84 100644
--- a/pve-api-types/src/types/mod.rs
+++ b/pve-api-types/src/types/mod.rs
@@ -17,6 +17,9 @@ pub mod array;
 pub mod stringlist;
 pub mod verifiers;
 
+mod fixed_string;
+pub use fixed_string::{FixedString, TooLongError};
+
 include!("../generated/types.rs");
 
 /// A PVE Upid, contrary to a PBS Upid, contains no 'task-id' number.
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH proxmox v2 2/4] pve-api-types: generate fallback variant for enums
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 1/4] pve-api-types: add FixedString type Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 3/4] pve-api-types: regenerate Stefan Hanreich
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

Generate a new variant, UnknownEnumValue, which is used as a fallback
in case the enum variant is unknown in PDM. This ensures
forwards-compatibility with newer Proxmox VE versions, in case they
introduce a new variant to an enum string type. Otherwise,
deserializing return values from the Proxmox VE API fails and can
cause severe issues in the PDM backend / frontend.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/generator-lib/Schema2Rust.pm | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pve-api-types/generator-lib/Schema2Rust.pm b/pve-api-types/generator-lib/Schema2Rust.pm
index f54b3683..ab1c1700 100644
--- a/pve-api-types/generator-lib/Schema2Rust.pm
+++ b/pve-api-types/generator-lib/Schema2Rust.pm
@@ -295,6 +295,9 @@ sub print_types : prototype($) {
                 print {$out} "    /// $orig.\n";
                 print {$out} "    $named,\n";
             };
+            print {$out} "    /// Unknown variants for forward compatibility.\n";
+            print {$out} "    #[serde(untagged)]\n";
+            print {$out} "    UnknownEnumValue(FixedString)\n";
             print {$out} "}\n";
             print {$out} "serde_plain::derive_display_from_serialize!($def->{name});\n";
             print {$out} "serde_plain::derive_fromstr_from_deserialize!($def->{name});\n";
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH proxmox v2 3/4] pve-api-types: regenerate
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 1/4] pve-api-types: add FixedString type Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 2/4] pve-api-types: generate fallback variant for enums Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 4/4] pve-api-types: sdn: handle fallback variant Stefan Hanreich
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/src/generated/types.rs | 210 +++++++++++++++++++++++++++
 1 file changed, 210 insertions(+)

diff --git a/pve-api-types/src/generated/types.rs b/pve-api-types/src/generated/types.rs
index e19b88ae..9c7041be 100644
--- a/pve-api-types/src/generated/types.rs
+++ b/pve-api-types/src/generated/types.rs
@@ -101,6 +101,9 @@ pub enum AptUpdateInfoArch {
     #[serde(rename = "s390x")]
     /// s390x.
     S390x,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(AptUpdateInfoArch);
 serde_plain::derive_fromstr_from_deserialize!(AptUpdateInfoArch);
@@ -444,6 +447,9 @@ pub enum ClusterMetricsDataType {
     #[serde(rename = "derive")]
     /// derive.
     Derive,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ClusterMetricsDataType);
 serde_plain::derive_fromstr_from_deserialize!(ClusterMetricsDataType);
@@ -549,6 +555,9 @@ pub enum ClusterNodeIndexResponseStatus {
     #[serde(rename = "offline")]
     /// offline.
     Offline,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ClusterNodeIndexResponseStatus);
 serde_plain::derive_fromstr_from_deserialize!(ClusterNodeIndexResponseStatus);
@@ -663,6 +672,9 @@ pub enum ClusterNodeStatusType {
     #[serde(rename = "node")]
     /// node.
     Node,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ClusterNodeStatusType);
 serde_plain::derive_fromstr_from_deserialize!(ClusterNodeStatusType);
@@ -1002,6 +1014,9 @@ pub enum ClusterResourceKind {
     #[serde(rename = "sdn")]
     /// sdn.
     Sdn,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ClusterResourceKind);
 serde_plain::derive_fromstr_from_deserialize!(ClusterResourceKind);
@@ -1016,6 +1031,9 @@ pub enum ClusterResourceNetworkType {
     #[serde(rename = "zone")]
     /// zone.
     Zone,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ClusterResourceNetworkType);
 serde_plain::derive_fromstr_from_deserialize!(ClusterResourceNetworkType);
@@ -1048,6 +1066,9 @@ pub enum ClusterResourceType {
     #[serde(rename = "network")]
     /// network.
     Network,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ClusterResourceType);
 serde_plain::derive_fromstr_from_deserialize!(ClusterResourceType);
@@ -1704,6 +1725,9 @@ pub enum IsRunning {
     #[serde(rename = "stopped")]
     /// stopped.
     Stopped,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(IsRunning);
 serde_plain::derive_fromstr_from_deserialize!(IsRunning);
@@ -1724,6 +1748,9 @@ pub enum ListControllersType {
     #[serde(rename = "isis")]
     /// isis.
     Isis,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ListControllersType);
 serde_plain::derive_fromstr_from_deserialize!(ListControllersType);
@@ -1774,6 +1801,9 @@ pub enum ListNetworksType {
     #[serde(rename = "include_sdn")]
     /// include_sdn.
     IncludeSdn,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ListNetworksType);
 serde_plain::derive_fromstr_from_deserialize!(ListNetworksType);
@@ -1825,6 +1855,9 @@ pub enum ListRealmTfa {
     #[serde(rename = "oath")]
     /// oath.
     Oath,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ListRealmTfa);
 serde_plain::derive_fromstr_from_deserialize!(ListRealmTfa);
@@ -2035,6 +2068,9 @@ pub enum ListTasksSource {
     #[serde(rename = "all")]
     /// all.
     All,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ListTasksSource);
 serde_plain::derive_fromstr_from_deserialize!(ListTasksSource);
@@ -2061,6 +2097,9 @@ pub enum ListZonesType {
     #[serde(rename = "vxlan")]
     /// vxlan.
     Vxlan,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(ListZonesType);
 serde_plain::derive_fromstr_from_deserialize!(ListZonesType);
@@ -2463,6 +2502,9 @@ pub enum LxcConfigArch {
     #[serde(rename = "riscv64")]
     /// riscv64.
     Riscv64,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(LxcConfigArch);
 serde_plain::derive_fromstr_from_deserialize!(LxcConfigArch);
@@ -2484,6 +2526,9 @@ pub enum LxcConfigCmode {
     #[default]
     /// tty.
     Tty,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(LxcConfigCmode);
 serde_plain::derive_fromstr_from_deserialize!(LxcConfigCmode);
@@ -2656,6 +2701,9 @@ pub enum LxcConfigLock {
     #[serde(rename = "snapshot-delete")]
     /// snapshot-delete.
     SnapshotDelete,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(LxcConfigLock);
 serde_plain::derive_fromstr_from_deserialize!(LxcConfigLock);
@@ -2917,6 +2965,9 @@ pub enum LxcConfigNetType {
     #[serde(rename = "veth")]
     /// veth.
     Veth,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(LxcConfigNetType);
 serde_plain::derive_fromstr_from_deserialize!(LxcConfigNetType);
@@ -2961,6 +3012,9 @@ pub enum LxcConfigOstype {
     #[serde(rename = "unmanaged")]
     /// unmanaged.
     Unmanaged,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(LxcConfigOstype);
 serde_plain::derive_fromstr_from_deserialize!(LxcConfigOstype);
@@ -4155,6 +4209,9 @@ pub enum NetworkInterfaceBondMode {
     #[serde(rename = "lacp-balance-tcp")]
     /// lacp-balance-tcp.
     LacpBalanceTcp,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NetworkInterfaceBondMode);
 serde_plain::derive_fromstr_from_deserialize!(NetworkInterfaceBondMode);
@@ -4173,6 +4230,9 @@ pub enum NetworkInterfaceBondXmitHashPolicy {
     #[serde(rename = "layer3+4")]
     /// layer3+4.
     Layer3_4,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NetworkInterfaceBondXmitHashPolicy);
 serde_plain::derive_fromstr_from_deserialize!(NetworkInterfaceBondXmitHashPolicy);
@@ -4187,6 +4247,9 @@ pub enum NetworkInterfaceFamilies {
     #[serde(rename = "inet6")]
     /// inet6.
     Inet6,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NetworkInterfaceFamilies);
 serde_plain::derive_fromstr_from_deserialize!(NetworkInterfaceFamilies);
@@ -4210,6 +4273,9 @@ pub enum NetworkInterfaceMethod {
     #[serde(rename = "auto")]
     /// auto.
     Auto,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NetworkInterfaceMethod);
 serde_plain::derive_fromstr_from_deserialize!(NetworkInterfaceMethod);
@@ -4254,6 +4320,9 @@ pub enum NetworkInterfaceType {
     #[serde(rename = "unknown")]
     /// unknown.
     Unknown,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NetworkInterfaceType);
 serde_plain::derive_fromstr_from_deserialize!(NetworkInterfaceType);
@@ -4268,6 +4337,9 @@ pub enum NetworkInterfaceVlanProtocol {
     #[serde(rename = "802.1q")]
     /// 802.1q.
     Ieee802_1q,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NetworkInterfaceVlanProtocol);
 serde_plain::derive_fromstr_from_deserialize!(NetworkInterfaceVlanProtocol);
@@ -4311,6 +4383,9 @@ pub enum NodeShellTermproxyCmd {
     #[serde(rename = "upgrade")]
     /// upgrade.
     Upgrade,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NodeShellTermproxyCmd);
 serde_plain::derive_fromstr_from_deserialize!(NodeShellTermproxyCmd);
@@ -4441,6 +4516,9 @@ pub enum NodeStatusBootInfoMode {
     #[serde(rename = "legacy-bios")]
     /// legacy-bios.
     LegacyBios,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NodeStatusBootInfoMode);
 serde_plain::derive_fromstr_from_deserialize!(NodeStatusBootInfoMode);
@@ -4711,6 +4789,9 @@ pub enum NodeSubscriptionInfoStatus {
     #[serde(rename = "suspended")]
     /// suspended.
     Suspended,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(NodeSubscriptionInfoStatus);
 serde_plain::derive_fromstr_from_deserialize!(NodeSubscriptionInfoStatus);
@@ -4823,6 +4904,9 @@ pub enum PveQemuSevFmtType {
     #[serde(rename = "snp")]
     /// snp.
     Snp,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQemuSevFmtType);
 serde_plain::derive_fromstr_from_deserialize!(PveQemuSevFmtType);
@@ -5413,6 +5497,9 @@ pub enum PveQmIdeAio {
     #[serde(rename = "io_uring")]
     /// io_uring.
     IoUring,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeAio);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeAio);
@@ -5436,6 +5523,9 @@ pub enum PveQmIdeCache {
     #[serde(rename = "directsync")]
     /// directsync.
     Directsync,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeCache);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeCache);
@@ -5450,6 +5540,9 @@ pub enum PveQmIdeDiscard {
     #[serde(rename = "on")]
     /// on.
     On,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeDiscard);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeDiscard);
@@ -5476,6 +5569,9 @@ pub enum PveQmIdeFormat {
     #[serde(rename = "cloop")]
     /// cloop.
     Cloop,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeFormat);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeFormat);
@@ -5491,6 +5587,9 @@ pub enum PveQmIdeMedia {
     #[default]
     /// disk.
     Disk,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeMedia);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeMedia);
@@ -5508,6 +5607,9 @@ pub enum PveQmIdeRerror {
     #[serde(rename = "stop")]
     /// stop.
     Stop,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeRerror);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeRerror);
@@ -5528,6 +5630,9 @@ pub enum PveQmIdeWerror {
     #[serde(rename = "stop")]
     /// stop.
     Stop,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmIdeWerror);
 serde_plain::derive_fromstr_from_deserialize!(PveQmIdeWerror);
@@ -5632,6 +5737,9 @@ pub enum PveQmRngSource {
     #[serde(rename = "/dev/hwrng")]
     /// /dev/hwrng.
     DevHwrng,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmRngSource);
 serde_plain::derive_fromstr_from_deserialize!(PveQmRngSource);
@@ -5755,6 +5863,9 @@ pub enum PveQmWatchdogAction {
     #[serde(rename = "none")]
     /// none.
     None,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmWatchdogAction);
 serde_plain::derive_fromstr_from_deserialize!(PveQmWatchdogAction);
@@ -5770,6 +5881,9 @@ pub enum PveQmWatchdogModel {
     #[serde(rename = "ib700")]
     /// ib700.
     Ib700,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveQmWatchdogModel);
 serde_plain::derive_fromstr_from_deserialize!(PveQmWatchdogModel);
@@ -6080,6 +6194,9 @@ pub enum PveVmCpuConfReportedModel {
     #[serde(rename = "Westmere-IBRS")]
     /// Westmere-IBRS.
     WestmereIbrs,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(PveVmCpuConfReportedModel);
 serde_plain::derive_fromstr_from_deserialize!(PveVmCpuConfReportedModel);
@@ -7132,6 +7249,9 @@ pub enum QemuConfigAgentType {
     #[serde(rename = "isa")]
     /// isa.
     Isa,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigAgentType);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigAgentType);
@@ -7146,6 +7266,9 @@ pub enum QemuConfigArch {
     #[serde(rename = "aarch64")]
     /// aarch64.
     Aarch64,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigArch);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigArch);
@@ -7183,6 +7306,9 @@ pub enum QemuConfigAudio0Device {
     #[serde(rename = "AC97")]
     /// AC97.
     Ac97,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigAudio0Device);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigAudio0Device);
@@ -7198,6 +7324,9 @@ pub enum QemuConfigAudio0Driver {
     #[serde(rename = "none")]
     /// none.
     None,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigAudio0Driver);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigAudio0Driver);
@@ -7213,6 +7342,9 @@ pub enum QemuConfigBios {
     #[serde(rename = "ovmf")]
     /// ovmf.
     Ovmf,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigBios);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigBios);
@@ -7232,6 +7364,9 @@ pub enum QemuConfigCitype {
     #[serde(rename = "opennebula")]
     /// opennebula.
     Opennebula,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigCitype);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigCitype);
@@ -7312,6 +7447,9 @@ pub enum QemuConfigEfidisk0Efitype {
     #[serde(rename = "4m")]
     /// 4m.
     Mb4,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigEfidisk0Efitype);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigEfidisk0Efitype);
@@ -7329,6 +7467,9 @@ pub enum QemuConfigHugepages {
     #[serde(rename = "1024")]
     /// 1024.
     Mb1024,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigHugepages);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigHugepages);
@@ -7438,6 +7579,9 @@ pub enum QemuConfigKeyboard {
     #[serde(rename = "tr")]
     /// tr.
     Tr,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigKeyboard);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigKeyboard);
@@ -7473,6 +7617,9 @@ pub enum QemuConfigLock {
     #[serde(rename = "suspended")]
     /// suspended.
     Suspended,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigLock);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigLock);
@@ -7547,6 +7694,9 @@ pub enum QemuConfigMachineViommu {
     #[serde(rename = "virtio")]
     /// virtio.
     Virtio,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigMachineViommu);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigMachineViommu);
@@ -7791,6 +7941,9 @@ pub enum QemuConfigNetModel {
     #[serde(rename = "vmxnet3")]
     /// vmxnet3.
     Vmxnet3,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigNetModel);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigNetModel);
@@ -7842,6 +7995,9 @@ pub enum QemuConfigNumaPolicy {
     #[serde(rename = "interleave")]
     /// interleave.
     Interleave,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigNumaPolicy);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigNumaPolicy);
@@ -7890,6 +8046,9 @@ pub enum QemuConfigOstype {
     #[serde(rename = "solaris")]
     /// solaris.
     Solaris,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigOstype);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigOstype);
@@ -8639,6 +8798,9 @@ pub enum QemuConfigScsihw {
     #[serde(rename = "pvscsi")]
     /// pvscsi.
     Pvscsi,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigScsihw);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigScsihw);
@@ -8682,6 +8844,9 @@ pub enum QemuConfigSpiceEnhancementsVideostreaming {
     #[serde(rename = "filter")]
     /// filter.
     Filter,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigSpiceEnhancementsVideostreaming);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigSpiceEnhancementsVideostreaming);
@@ -8741,6 +8906,9 @@ pub enum QemuConfigTpmstate0Version {
     #[serde(rename = "v2.0")]
     /// v2.0.
     V2_0,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigTpmstate0Version);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigTpmstate0Version);
@@ -8869,6 +9037,9 @@ pub enum QemuConfigVgaClipboard {
     #[serde(rename = "vnc")]
     /// vnc.
     Vnc,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigVgaClipboard);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigVgaClipboard);
@@ -8920,6 +9091,9 @@ pub enum QemuConfigVgaType {
     #[serde(rename = "vmware")]
     /// vmware.
     Vmware,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigVgaType);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigVgaType);
@@ -9340,6 +9514,9 @@ pub enum QemuConfigVirtiofsCache {
     #[serde(rename = "never")]
     /// never.
     Never,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuConfigVirtiofsCache);
 serde_plain::derive_fromstr_from_deserialize!(QemuConfigVirtiofsCache);
@@ -9537,6 +9714,9 @@ pub enum QemuMigratePreconditionsNotAllowedNodesBlockingHaResourcesCause {
     #[serde(rename = "resource-affinity")]
     /// resource-affinity.
     ResourceAffinity,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(
     QemuMigratePreconditionsNotAllowedNodesBlockingHaResourcesCause
@@ -10596,6 +10776,9 @@ pub enum QemuMoveDiskDisk {
     #[serde(rename = "unused255")]
     /// unused255.
     Unused255,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuMoveDiskDisk);
 serde_plain::derive_fromstr_from_deserialize!(QemuMoveDiskDisk);
@@ -10613,6 +10796,9 @@ pub enum QemuMoveDiskFormat {
     #[serde(rename = "vmdk")]
     /// vmdk.
     Vmdk,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuMoveDiskFormat);
 serde_plain::derive_fromstr_from_deserialize!(QemuMoveDiskFormat);
@@ -10838,6 +11024,9 @@ pub enum QemuResizeDisk {
     #[serde(rename = "tpmstate0")]
     /// tpmstate0.
     Tpmstate0,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(QemuResizeDisk);
 serde_plain::derive_fromstr_from_deserialize!(QemuResizeDisk);
@@ -11873,6 +12062,9 @@ pub enum SdnObjectState {
     #[serde(rename = "changed")]
     /// changed.
     Changed,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(SdnObjectState);
 serde_plain::derive_fromstr_from_deserialize!(SdnObjectState);
@@ -12033,6 +12225,9 @@ pub enum SdnVnetType {
     #[serde(rename = "vnet")]
     /// vnet.
     Vnet,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(SdnVnetType);
 serde_plain::derive_fromstr_from_deserialize!(SdnVnetType);
@@ -12307,6 +12502,9 @@ pub enum SdnZoneDhcp {
     #[serde(rename = "dnsmasq")]
     /// dnsmasq.
     Dnsmasq,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(SdnZoneDhcp);
 serde_plain::derive_fromstr_from_deserialize!(SdnZoneDhcp);
@@ -12782,6 +12980,9 @@ pub enum StartQemuMigrationType {
     #[serde(rename = "insecure")]
     /// insecure.
     Insecure,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(StartQemuMigrationType);
 serde_plain::derive_fromstr_from_deserialize!(StartQemuMigrationType);
@@ -12908,6 +13109,9 @@ pub enum StorageContent {
     #[serde(rename = "vztmpl")]
     /// vztmpl.
     Vztmpl,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(StorageContent);
 serde_plain::derive_fromstr_from_deserialize!(StorageContent);
@@ -13070,6 +13274,9 @@ pub enum StorageInfoFormatsDefault {
     #[serde(rename = "vmdk")]
     /// vmdk.
     Vmdk,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(StorageInfoFormatsDefault);
 serde_plain::derive_fromstr_from_deserialize!(StorageInfoFormatsDefault);
@@ -16637,6 +16844,9 @@ pub enum VersionResponseConsole {
     #[serde(rename = "xtermjs")]
     /// xtermjs.
     Xtermjs,
+    /// Unknown variants for forward compatibility.
+    #[serde(untagged)]
+    UnknownEnumValue(FixedString),
 }
 serde_plain::derive_display_from_serialize!(VersionResponseConsole);
 serde_plain::derive_fromstr_from_deserialize!(VersionResponseConsole);
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH proxmox v2 4/4] pve-api-types: sdn: handle fallback variant
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
                   ` (2 preceding siblings ...)
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 3/4] pve-api-types: regenerate Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/1] pve: qemu: handle fallback enum variants Stefan Hanreich
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

Add the fallback variant to as_str. The only current callers of this
method don't require 'static lifetime, so it is safe to remove here.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/src/sdn.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pve-api-types/src/sdn.rs b/pve-api-types/src/sdn.rs
index 8188c35f..97972275 100644
--- a/pve-api-types/src/sdn.rs
+++ b/pve-api-types/src/sdn.rs
@@ -33,10 +33,11 @@ impl SdnController {
 }
 
 impl ClusterResourceNetworkType {
-    pub fn as_str(&self) -> &'static str {
+    pub fn as_str(&self) -> &str {
         match self {
             ClusterResourceNetworkType::Fabric => "fabric",
             ClusterResourceNetworkType::Zone => "zone",
+            ClusterResourceNetworkType::UnknownEnumValue(value) => value.as_str(),
         }
     }
 }
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH proxmox-yew-comp v2 1/1] pve: qemu: handle fallback enum variants
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
                   ` (3 preceding siblings ...)
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 4/4] pve-api-types: sdn: handle fallback variant Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/1] tree-wide: handle new unknown " Stefan Hanreich
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

pve-api-types introduced a fallback variant for all enums returned by
the Proxmox VE API. Add code for handling those new variants to the
use sites of those enums.

For AMD SEV and BIOS it is sufficient to print the unknown values,
since the Comboboxes prevent the re-submission of unknown values
anyway, due to the force_selection property being set to true.

For the OS type, we cannot uniquely identify if an OS is Windows or
not, since it is possible for an unknown value to be either Windows or
something else. For that case, the respective input field (machine
type), that needs this information to render content, is simply
disabled.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 .../qemu_property/qemu_amd_sev_property.rs    |  1 +
 .../pve/qemu_property/qemu_bios_property.rs   |  1 +
 .../qemu_property/qemu_machine_property.rs    | 20 +++++++++++--------
 3 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/src/form/pve/qemu_property/qemu_amd_sev_property.rs b/src/form/pve/qemu_property/qemu_amd_sev_property.rs
index 72d2dfd..30be125 100644
--- a/src/form/pve/qemu_property/qemu_amd_sev_property.rs
+++ b/src/form/pve/qemu_property/qemu_amd_sev_property.rs
@@ -118,6 +118,7 @@ pub fn qemu_amd_sev_property(mobile: bool) -> EditableProperty {
                         PveQemuSevFmtType::Std => "AMD SEV",
                         PveQemuSevFmtType::Es => "AMD SEV-ES",
                         PveQemuSevFmtType::Snp => "AMD SEV-SNP",
+                        PveQemuSevFmtType::UnknownEnumValue(value) => &format!("unknown '{value}'"),
                     };
                     format!("{text} ({v})").into()
                 }
diff --git a/src/form/pve/qemu_property/qemu_bios_property.rs b/src/form/pve/qemu_property/qemu_bios_property.rs
index 1ae78b3..4acfda4 100644
--- a/src/form/pve/qemu_property/qemu_bios_property.rs
+++ b/src/form/pve/qemu_property/qemu_bios_property.rs
@@ -59,6 +59,7 @@ pub fn qemu_bios_property(mobile: bool) -> EditableProperty {
                 Ok(bios) => match bios {
                     QemuConfigBios::Seabios => "SeaBIOS".into(),
                     QemuConfigBios::Ovmf => "OVMF (UEFI)".into(),
+                    QemuConfigBios::UnknownEnumValue(value) => format!("unknown '{value}'").into(),
                 },
                 Err(_) => v.into(),
             },
diff --git a/src/form/pve/qemu_property/qemu_machine_property.rs b/src/form/pve/qemu_property/qemu_machine_property.rs
index 31ace00..02c1bdb 100644
--- a/src/form/pve/qemu_property/qemu_machine_property.rs
+++ b/src/form/pve/qemu_property/qemu_machine_property.rs
@@ -15,7 +15,7 @@ use crate::form::pve::QemuMachineVersionSelector;
 use crate::pve_api_types::QemuMachineType;
 use crate::{EditableProperty, PropertyEditorState, RenderPropertyInputPanelFn};
 
-fn ostype_is_windows(ostype: &QemuConfigOstype) -> bool {
+fn ostype_is_windows(ostype: &QemuConfigOstype) -> Option<bool> {
     match ostype {
         QemuConfigOstype::Wxp
         | QemuConfigOstype::W2k
@@ -25,11 +25,12 @@ fn ostype_is_windows(ostype: &QemuConfigOstype) -> bool {
         | QemuConfigOstype::Win7
         | QemuConfigOstype::Win8
         | QemuConfigOstype::Win10
-        | QemuConfigOstype::Win11 => true,
+        | QemuConfigOstype::Win11 => Some(true),
         QemuConfigOstype::L24
         | QemuConfigOstype::L26
         | QemuConfigOstype::Solaris
-        | QemuConfigOstype::Other => false,
+        | QemuConfigOstype::Other => Some(false),
+        QemuConfigOstype::UnknownEnumValue(_) => None,
     }
 }
 
@@ -91,12 +92,14 @@ fn input_panel(mobile: bool) -> RenderPropertyInputPanelFn {
         };
 
         let add_version_selector = |panel: &mut InputPanel, ty| {
-            let disabled = machine_type != ty;
+            let is_windows = ostype_is_windows(&ostype);
+            let disabled = machine_type != ty || is_windows.is_none();
             let name = get_version_prop_name(ty.to_string());
+
             let field = QemuMachineVersionSelector::new(ty)
                 .name(name)
-                .disabled(machine_type != ty)
-                .required(ostype_is_windows(&ostype))
+                .disabled(disabled)
+                .required(is_windows.unwrap_or_default())
                 .submit(false);
             panel.add_field_with_options(
                 FieldPosition::Left,
@@ -181,8 +184,9 @@ pub fn qemu_machine_property(mobile: bool) -> EditableProperty {
                 serde_json::from_value(record["ostype"].clone()).ok();
             let ostype = ostype.unwrap_or(QemuConfigOstype::Other);
             match (v.as_str(), ostype_is_windows(&ostype)) {
-                (None | Some("pc"), true) => "pc-i440fx-5.1".into(),
-                (Some("q35"), true) => "pc-q35-5.1".into(),
+                (_, None) => format!("unknown ostype: {v}").into(),
+                (None | Some("pc"), Some(true)) => "pc-i440fx-5.1".into(),
+                (Some("q35"), Some(true)) => "pc-q35-5.1".into(),
                 (Some(machine), _) => machine.into(),
                 (None, _) => placeholder().into(),
             }
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/1] tree-wide: handle new unknown enum variants
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
                   ` (4 preceding siblings ...)
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/1] pve: qemu: handle fallback enum variants Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 15:09 ` [pdm-devel] [PATCH pve-yew-mobile-gui v2 1/1] tree-wide: handle fallback enum values Stefan Hanreich
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

pve-api-types now generates a fallback enum variant in order to handle
encountering unknown values for enums more gracefully. Fix all
occurrences of enums that have changed that lead to compiler errors.

In the case of migrations an error is thrown, since it is impossible
to tell if it is okay to proceed migrating a guest with an unknown
state. In other cases simply log / print the unknown value.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 server/src/metric_collection/rrd_task.rs | 4 ++++
 ui/src/pve/utils.rs                      | 1 +
 ui/src/widget/migrate_window.rs          | 6 ++++++
 3 files changed, 11 insertions(+)

diff --git a/server/src/metric_collection/rrd_task.rs b/server/src/metric_collection/rrd_task.rs
index 507d6b2..f48968b 100644
--- a/server/src/metric_collection/rrd_task.rs
+++ b/server/src/metric_collection/rrd_task.rs
@@ -140,6 +140,10 @@ fn store_metric_pve(cache: &RrdCache, remote_name: &str, data_point: &ClusterMet
         ClusterMetricsDataType::Gauge => DataSourceType::Gauge,
         ClusterMetricsDataType::Counter => DataSourceType::Counter,
         ClusterMetricsDataType::Derive => DataSourceType::Derive,
+        ClusterMetricsDataType::UnknownEnumValue(value) => {
+            log::warn!("encountered unknown metric type: {value}");
+            return;
+        }
     };
 
     cache.update_value(
diff --git a/ui/src/pve/utils.rs b/ui/src/pve/utils.rs
index 5923855..ef536da 100644
--- a/ui/src/pve/utils.rs
+++ b/ui/src/pve/utils.rs
@@ -289,5 +289,6 @@ pub(crate) fn render_content_type(ty: &StorageContent) -> String {
         StorageContent::Snippets => tr!("Snippets"),
         StorageContent::Vztmpl => tr!("Container template"),
         StorageContent::None => tr!("None"),
+        StorageContent::UnknownEnumValue(value) => tr!("unknown content type ({0})", value),
     }
 }
diff --git a/ui/src/widget/migrate_window.rs b/ui/src/widget/migrate_window.rs
index cb12a15..5fe7278 100644
--- a/ui/src/widget/migrate_window.rs
+++ b/ui/src/widget/migrate_window.rs
@@ -122,6 +122,9 @@ impl PdmMigrateWindow {
                 match status.status {
                     pdm_client::types::IsRunning::Running => tr!("Online"),
                     pdm_client::types::IsRunning::Stopped => tr!("Offline"),
+                    pdm_client::types::IsRunning::UnknownEnumValue(value) => {
+                        bail!("encountered unknown guest status: {value}")
+                    }
                 }
             }
             crate::pve::GuestType::Lxc => {
@@ -131,6 +134,9 @@ impl PdmMigrateWindow {
                 match status.status {
                     pdm_client::types::IsRunning::Running => tr!("Restart"),
                     pdm_client::types::IsRunning::Stopped => tr!("Offline"),
+                    pdm_client::types::IsRunning::UnknownEnumValue(value) => {
+                        bail!("encountered unknown guest status: {value}")
+                    }
                 }
             }
         };
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] [PATCH pve-yew-mobile-gui v2 1/1] tree-wide: handle fallback enum values
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
                   ` (5 preceding siblings ...)
  2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/1] tree-wide: handle new unknown " Stefan Hanreich
@ 2025-11-13 15:09 ` Stefan Hanreich
  2025-11-13 20:33 ` [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Thomas Lamprecht
  2025-11-13 21:28 ` [pdm-devel] applied-series: " Thomas Lamprecht
  8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hanreich @ 2025-11-13 15:09 UTC (permalink / raw)
  To: pdm-devel

pve-api-types introduced a new fallback variant when encountering
unknown enum variants in the Proxmox VE API response.

For the resources overview, simply ignore any unknown resource type
and do nothing, as is alreadu the case with known resource types that
are not yet implemented.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/pages/page_resources.rs   | 4 +++-
 src/pages/page_task_status.rs | 7 +++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/pages/page_resources.rs b/src/pages/page_resources.rs
index 02b57cb..7302229 100644
--- a/src/pages/page_resources.rs
+++ b/src/pages/page_resources.rs
@@ -232,7 +232,8 @@ impl PvePageResources {
                     ClusterResourceType::Pool
                     | ClusterResourceType::Network
                     | ClusterResourceType::Sdn
-                    | ClusterResourceType::Openvz => {
+                    | ClusterResourceType::Openvz
+                    | ClusterResourceType::UnknownEnumValue(_) => {
                         /* ignore for now  */
                         None
                     }
@@ -345,6 +346,7 @@ fn type_ordering(ty: ClusterResourceType) -> usize {
         ClusterResourceType::Pool => 5,
         ClusterResourceType::Sdn => 6,
         ClusterResourceType::Network => 7,
+        ClusterResourceType::UnknownEnumValue(_) => 1000,
     }
 }
 
diff --git a/src/pages/page_task_status.rs b/src/pages/page_task_status.rs
index de702c1..9217e75 100644
--- a/src/pages/page_task_status.rs
+++ b/src/pages/page_task_status.rs
@@ -129,6 +129,13 @@ impl PvePageTaskStatus {
                         (IsRunning::Stopped, None) => {
                             format!("{} ({})", tr!("stopped"), tr!("unknown"))
                         }
+                        (IsRunning::UnknownEnumValue(value), msg) => {
+                            format!(
+                                "{} ({})",
+                                tr!("unknown status '{0}'", value),
+                                msg.as_ref().unwrap_or(&tr!("unknown"))
+                            )
+                        }
                     },
                 ));
 
-- 
2.47.3


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
                   ` (6 preceding siblings ...)
  2025-11-13 15:09 ` [pdm-devel] [PATCH pve-yew-mobile-gui v2 1/1] tree-wide: handle fallback enum values Stefan Hanreich
@ 2025-11-13 20:33 ` Thomas Lamprecht
  2025-11-13 21:28 ` [pdm-devel] applied-series: " Thomas Lamprecht
  8 siblings, 0 replies; 10+ messages in thread
From: Thomas Lamprecht @ 2025-11-13 20:33 UTC (permalink / raw)
  To: Proxmox Datacenter Manager development discussion, Stefan Hanreich

Am 13.11.25 um 16:09 schrieb Stefan Hanreich:
> ### Resubmitting unknown variants
> 
> I had some off list discussions with Dominik and Dietmar regarding how consumers
> of the API (mainly the UI) could handle encountering unknown values when there
> is the possibility of re-submission of this data. Two main options emerged:
> 
> * Resubmitting unknown values as-is, without preventing the user from doing so
> 
> This has the upside of not preventing the user from submitting perfectly fine
> values if they haven't been implemented in PDM. An argument in favor would be
> that the PVE API should act as the last safeguard against invalid objects in the
> configuration anyway, so we could rely on PVE validation only in the case of
> unknown enums.
> The downside is that we need a round-trip to the Proxmox VE API and can only
> show the resulting error in the UI, without any validation. It also does not
> take care of situations where setting a certain, new, enum potentially requires
> special behavior when submitting values.
> 
> * Showing the value, but preventing the user from re-submitting the value
> 
> This is the safest option, since it prevents any, potentially destructive,
> action from the user due to the consumer not being aware of special . It also
> gives the user immediate feedback in the UI, without requiring a round-trip to
> the Proxmox VE instance, but of course users lose the ability to submit
> perfectly valid configurations (without upgrading).
> 
> 
> The answer is, of course, it depends on the context - but in the general case it
> seems preferable to me to show the value to the user, but prevent the user from
> re-submitting the value, since the UI cannot know if there is some special
> handling required in case the new enum variant is used in the UI. If there is a
> good argument for letting the user re-submit certain fields, then that seems
> like a fine option too, but imo *iff* there's good reasoning attached to it.
> 
> What do you think? Any options that were missed?


For the ExtJS UI's we often edit what we know and resend the unknown rest as is,
or drop the unknown rest due to it not getting into any form field and thus
not being present on submit anymore. Depends a bit on the backend and if the
data changed is all in top-level properties or complex properties consisting
of format-strings. For the former it often won't matter (we seldomly have
conflicting values on that level) and for the latter most of our code should
use the generic print/parse property string helpers, which sends that along
again and quite often will do what one expects. It works surprisingly OK,
albeit besides PDM, which doesn't exist for that long, one basically only has
a real likelihood to run into issues when having a PVE cluster with mixed
versions, as otherwise the UI and the backend will closely match anyway.

For PDM I'd go for the "show but not edit" route for now, at least as default.
As you say, there can be exceptions, but that can be decided on a case by
case basis when needed.

Am 13.11.25 um 16:09 schrieb Stefan Hanreich:
> ### Storing unknown Variants
> 
> When only reading and displaying / storing data, then including unknown values
> is desired behavior imo (again, in the general case) - since it allows storing
> data (e.g. metrics) that isn't yet understood but could potentially be
> understood perfectly fine after an upgrade of PDM.
> 

Would not do anything with that, throw away and log as low-severity level,
one always can get send stuff by a (potentially malicious!) backend, if
one cannot understand it, doing nothing is best for the common case.

Am 13.11.25 um 16:09 schrieb Stefan Hanreich:
> ### Unsafe FixedString
> 
> I implemented as_bytes and as_str in FixedString via the unsafe variants of the
> methods and included tests that check for correct behavior of those methods.
> Using the safe variants and panicking here should be fine too imo if we don't
> want to go down that road. FixedString should only get used in rare, exceptional
> cases anyway - and even then not in a hot path. The adaption should be trivial.
> 

Ack.

Am 13.11.25 um 16:09 schrieb Stefan Hanreich:
> ## Adapting the existing code
> 
> One difficulty when adapting the UI was identifying places in which a struct
> gets returned directly from the API, deserialized (but the value isn't used
> anywhere in the frontend), then serialized again and sent back to the backend.
> Since the FixedString serializes indistinguishable from a String, this could
> introduce situations where we submit unkown enum values when instead an error
> should be shown and the user prevented from submitting a form.
> 
> I tried to look at each component in the backend / frontend and check if it uses
> and potentially re-submits am enum without checking its variants for unknown
> values. Since we model enums with checkboxes in the UI and have fixed values
> there, this should usually work and prevent users from submitting values that
> are unknown to the UI. Nevertheless, it's very much possible I missed some
> places, so here some group effort in trying to identify potentially problematic
> spots would be nice.

This is limited to cases where there is an unknown variant/value in the first
place, which is not something that will happen everyday, might even start to
level off for non-major releases in the future (after most important PDM features
got implemented).



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pdm-devel] applied-series:  [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types
  2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
                   ` (7 preceding siblings ...)
  2025-11-13 20:33 ` [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Thomas Lamprecht
@ 2025-11-13 21:28 ` Thomas Lamprecht
  8 siblings, 0 replies; 10+ messages in thread
From: Thomas Lamprecht @ 2025-11-13 21:28 UTC (permalink / raw)
  To: Proxmox Datacenter Manager development discussion, Stefan Hanreich

Am 13.11.25 um 16:09 schrieb Stefan Hanreich:
> ## Introduction
> 
> This patch series adds a new fallback variant to all enums that are
> auto-generated in pve-api-types. This ensures that the addition of new enum
> variants in the Proxmox VE API does not break the deserialization of Proxmox VE
> API types in PDM. Rather, the unknown enum variant is now populated with the
> parsed enum variant name. For that matter this patch series introduces a new
> string type, FixedString. It can store up to 23 characters and is Copy, which is
> the main reason for introducing this custom string type. This lets us keep Copy
> in our generated enums.
applied, thanks!

Not set in stone and rolling this out now gives use more experience before
first stable PDM, where this will actually start to matter a lot.


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2025-11-13 21:27 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-13 15:09 [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 1/4] pve-api-types: add FixedString type Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 2/4] pve-api-types: generate fallback variant for enums Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 3/4] pve-api-types: regenerate Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox v2 4/4] pve-api-types: sdn: handle fallback variant Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/1] pve: qemu: handle fallback enum variants Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/1] tree-wide: handle new unknown " Stefan Hanreich
2025-11-13 15:09 ` [pdm-devel] [PATCH pve-yew-mobile-gui v2 1/1] tree-wide: handle fallback enum values Stefan Hanreich
2025-11-13 20:33 ` [pdm-devel] [RFC proxmox{, -yew-comp, -datacenter-manager}/yew-mobile-gui v2 0/7] Add fallback variant to enum properties in Proxmox VE Rust API types Thomas Lamprecht
2025-11-13 21:28 ` [pdm-devel] applied-series: " Thomas Lamprecht

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal