public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Lukas Wagner" <l.wagner@proxmox.com>
To: "Dominik Csapak" <d.csapak@proxmox.com>, <pdm-devel@lists.proxmox.com>
Subject: Re: [PATCH datacenter-manager v2 1/9] lib: api types: add new ResourceView type and move accessors there
Date: Thu, 20 Aug 2026 13:07:20 +0200	[thread overview]
Message-ID: <DKTQ1PNHRLZ3.2TH9QV54RP7F1@proxmox.com> (raw)
In-Reply-To: <20260818133000.3793412-2-d.csapak@proxmox.com>

Hi Dominik,

looking good - some of the code is missing doc-strings that could be
added, but that's just a minor nit-pick.

On Tue Aug 18, 2026 at 3:25 PM CEST, Dominik Csapak wrote:
> This is intended as a view type for Resource and PveResource, which will
> always share some enum type. This way, we can reuse all accessors
> cheaply for PveResource values without cloning or consuming.
>
> This makes it easier to share code for both of them, for instance in the
> UI where we want to render properties consistently for resources.
>
> Introduces also a AsResourceView trait to make converting more
> ergonomic.
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
>  lib/pdm-api-types/src/resource.rs | 261 +++++++++++++++++++++---------
>  1 file changed, 184 insertions(+), 77 deletions(-)
>
> diff --git a/lib/pdm-api-types/src/resource.rs b/lib/pdm-api-types/src/resource.rs
> index 303ac3cb..4447aedb 100644
> --- a/lib/pdm-api-types/src/resource.rs
> +++ b/lib/pdm-api-types/src/resource.rs
> @@ -45,102 +45,31 @@ impl Resource {
>      /// Returns the local ID, not a globally unique one, e.g.
>      /// `qemu/<vmid>`
>      pub fn id(&self) -> String {
> -        match self {
> -            Resource::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage),
> -            Resource::PveQemu(r) => format!("qemu/{}", r.vmid),
> -            Resource::PveLxc(r) => format!("lxc/{}", r.vmid),
> -            Resource::PveNode(r) => format!("node/{}", r.node),
> -            Resource::PveNetwork(r) => {
> -                if let PveNetworkResource::Zone(z) = r {
> -                    if z.legacy {
> -                        return format!("sdn/{}/{}", r.node(), r.name());
> -                    }
> -                }
> -
> -                format!("network/{}/{}/{}", r.node(), r.network_type(), r.name())
> -            }
> -            Resource::PbsNode(r) => format!("node/{}", r.name),
> -            Resource::PbsDatastore(r) => r.name.clone(),
> -        }
> +        self.as_resource_view().id()
>      }
>  
>      /// Returns the PDM global ID for the resource, e.g.
>      /// `remote/<remote-id>/guest/<vmid>`
>      pub fn global_id(&self) -> &str {
> -        match self {
> -            Resource::PveStorage(r) => r.id.as_str(),
> -            Resource::PveQemu(r) => r.id.as_str(),
> -            Resource::PveLxc(r) => r.id.as_str(),
> -            Resource::PveNode(r) => r.id.as_str(),
> -            Resource::PveNetwork(r) => r.id(),
> -            Resource::PbsNode(r) => r.id.as_str(),
> -            Resource::PbsDatastore(r) => r.id.as_str(),
> -        }
> +        self.as_resource_view().global_id()
>      }
>  
>      /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or
>      /// the hostname for nodes
>      pub fn name(&self) -> &str {
> -        match self {
> -            Resource::PveStorage(r) => r.storage.as_str(),
> -            Resource::PveQemu(r) => r.name.as_str(),
> -            Resource::PveLxc(r) => r.name.as_str(),
> -            Resource::PveNode(r) => r.node.as_str(),
> -            Resource::PveNetwork(r) => r.name(),
> -            Resource::PbsNode(r) => r.name.as_str(),
> -            Resource::PbsDatastore(r) => r.name.as_str(),
> -        }
> +        self.as_resource_view().name()
>      }
>  

While at it, you could add some doc strings here.

>      pub fn resource_type(&self) -> ResourceType {
> -        match self {
> -            Resource::PveStorage(_) => ResourceType::PveStorage,
> -            Resource::PveQemu(_) => ResourceType::PveQemu,
> -            Resource::PveLxc(_) => ResourceType::PveLxc,
> -            Resource::PveNetwork(_) => ResourceType::PveNetwork,
> -            Resource::PveNode(_) | Resource::PbsNode(_) => ResourceType::Node,
> -            Resource::PbsDatastore(_) => ResourceType::PbsDatastore,
> -        }
> +        self.as_resource_view().resource_type()
>      }
>  
While at it, you could add some doc strings here.

>      pub fn status(&self) -> &str {
> -        match self {
> -            Resource::PveStorage(r) => r.status.as_str(),
> -            Resource::PveQemu(r) => r.status.as_str(),
> -            Resource::PveLxc(r) => r.status.as_str(),
> -            Resource::PveNode(r) => r.status.as_str(),
> -            Resource::PveNetwork(r) => r.status(),
> -            Resource::PbsNode(r) => {
> -                if r.uptime > 0 {
> -                    "online"
> -                } else {
> -                    "offline"
> -                }
> -            }
> -            Resource::PbsDatastore(r) => {
> -                if r.maintenance.is_none() {
> -                    "online"
> -                } else {
> -                    "under-maintenance"
> -                }
> -            }
> -        }
> +        self.as_resource_view().status()
>      }
>  
While at it, you could add some doc strings here.

>      pub fn properties(&self) -> String {
> -        let mut properties = Vec::new();
> -        if let Resource::PbsDatastore(r) = self {
> -            if let Some(backend_type) = &r.backend_type {
> -                properties.push(backend_type.to_string());
> -            }
> -            if r.backing_device.is_some() {
> -                properties.push("removable".to_string());
> -            }
> -            if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD {
> -                properties.push("high-usage".to_string());
> -            }
> -        }
> -        properties.join(",")
> +        self.as_resource_view().properties()
>      }
>  }
>  
> @@ -229,6 +158,184 @@ pub enum PveResource {
>      Network(PveNetworkResource),
>  }
>  
> +/// A borrowed view of a resource that can come from a [Resource] or a [PveResource]
> +/// This is useful for having a single implementation of field access regardless which
> +/// of the types is used.
> +#[derive(Clone, Debug, PartialEq)]
> +pub enum ResourceView<'a> {
> +    /// Storage resource in PVE.
> +    PveStorage(&'a PveStorageResource),
> +    /// QEMU guest resource in PVE.
> +    PveQemu(&'a PveQemuResource),
> +    /// LXC guest resource in PVE.
> +    PveLxc(&'a PveLxcResource),
> +    /// A PVE node.
> +    PveNode(&'a PveNodeResource),
> +    /// Network in PVE.
> +    PveNetwork(&'a PveNetworkResource),
> +    /// A PBS node.
> +    PbsNode(&'a PbsNodeResource),
> +    /// Datastore on a PBS node.
> +    PbsDatastore(&'a PbsDatastoreResource),
> +}
> +
> +impl<'a> ResourceView<'a> {
> +    /// Returns the local ID, not a globally unique one, e.g.
> +    /// `qemu/<vmid>`
> +    pub fn id(&self) -> String {
> +        match self {
> +            ResourceView::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage),
> +            ResourceView::PveQemu(r) => format!("qemu/{}", r.vmid),
> +            ResourceView::PveLxc(r) => format!("lxc/{}", r.vmid),
> +            ResourceView::PveNode(r) => format!("node/{}", r.node),
> +            ResourceView::PveNetwork(r) => {
> +                if let PveNetworkResource::Zone(z) = r {
> +                    if z.legacy {
> +                        return format!("sdn/{}/{}", r.node(), r.name());
> +                    }
> +                }
> +
> +                format!("network/{}/{}/{}", r.node(), r.network_type(), r.name())
> +            }
> +            ResourceView::PbsNode(r) => format!("node/{}", r.name),
> +            ResourceView::PbsDatastore(r) => r.name.clone(),
> +        }
> +    }
> +
> +    /// Returns the PDM global ID for the resource, e.g.
> +    /// `remote/<remote-id>/guest/<vmid>`
> +    pub fn global_id(&self) -> &'a str {
> +        match self {
> +            ResourceView::PveStorage(r) => r.id.as_str(),
> +            ResourceView::PveQemu(r) => r.id.as_str(),
> +            ResourceView::PveLxc(r) => r.id.as_str(),
> +            ResourceView::PveNode(r) => r.id.as_str(),
> +            ResourceView::PveNetwork(r) => r.id(),
> +            ResourceView::PbsNode(r) => r.id.as_str(),
> +            ResourceView::PbsDatastore(r) => r.id.as_str(),
> +        }
> +    }
> +
> +    /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or
> +    /// the hostname for nodes
> +    pub fn name(&self) -> &'a str {
> +        match self {
> +            ResourceView::PveStorage(r) => r.storage.as_str(),
> +            ResourceView::PveQemu(r) => r.name.as_str(),
> +            ResourceView::PveLxc(r) => r.name.as_str(),
> +            ResourceView::PveNode(r) => r.node.as_str(),
> +            ResourceView::PveNetwork(r) => r.name(),
> +            ResourceView::PbsNode(r) => r.name.as_str(),
> +            ResourceView::PbsDatastore(r) => r.name.as_str(),
> +        }
> +    }
> +

Missing doc strings here.
> +    pub fn resource_type(&self) -> ResourceType {
> +        match self {
> +            ResourceView::PveStorage(_) => ResourceType::PveStorage,
> +            ResourceView::PveQemu(_) => ResourceType::PveQemu,
> +            ResourceView::PveLxc(_) => ResourceType::PveLxc,
> +            ResourceView::PveNetwork(_) => ResourceType::PveNetwork,
> +            ResourceView::PveNode(_) | ResourceView::PbsNode(_) => ResourceType::Node,
> +            ResourceView::PbsDatastore(_) => ResourceType::PbsDatastore,
> +        }
> +    }
> +

here as well
> +    pub fn status(&self) -> &'a str {
> +        match self {
> +            ResourceView::PveStorage(r) => r.status.as_str(),
> +            ResourceView::PveQemu(r) => r.status.as_str(),
> +            ResourceView::PveLxc(r) => r.status.as_str(),
> +            ResourceView::PveNode(r) => r.status.as_str(),
> +            ResourceView::PveNetwork(r) => r.status(),
> +            ResourceView::PbsNode(r) => {
> +                if r.uptime > 0 {
> +                    "online"
> +                } else {
> +                    "offline"
> +                }
> +            }
> +            ResourceView::PbsDatastore(r) => {
> +                if r.maintenance.is_none() {
> +                    "online"
> +                } else {
> +                    "under-maintenance"
> +                }
> +            }
> +        }
> +    }
> +

here as well
> +    pub fn properties(&self) -> String {
> +        let mut properties = Vec::new();
> +        if let ResourceView::PbsDatastore(r) = self {
> +            if let Some(backend_type) = &r.backend_type {
> +                properties.push(backend_type.to_string());
> +            }
> +            if r.backing_device.is_some() {
> +                properties.push("removable".to_string());
> +            }
> +            if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD {
> +                properties.push("high-usage".to_string());
> +            }
> +        }
> +        properties.join(",")
> +    }
> +}
> +
> +impl<'a> From<&'a Resource> for ResourceView<'a> {
> +    fn from(value: &'a Resource) -> Self {
> +        match value {
> +            Resource::PveStorage(r) => ResourceView::PveStorage(r),
> +            Resource::PveQemu(r) => ResourceView::PveQemu(r),
> +            Resource::PveLxc(r) => ResourceView::PveLxc(r),
> +            Resource::PveNode(r) => ResourceView::PveNode(r),
> +            Resource::PveNetwork(r) => ResourceView::PveNetwork(r),
> +            Resource::PbsNode(r) => ResourceView::PbsNode(r),
> +            Resource::PbsDatastore(r) => ResourceView::PbsDatastore(r),
> +        }
> +    }
> +}
> +
> +impl<'a> From<&'a PveResource> for ResourceView<'a> {
> +    fn from(value: &'a PveResource) -> Self {
> +        match value {
> +            PveResource::Storage(r) => ResourceView::PveStorage(r),
> +            PveResource::Qemu(r) => ResourceView::PveQemu(r),
> +            PveResource::Lxc(r) => ResourceView::PveLxc(r),
> +            PveResource::Node(r) => ResourceView::PveNode(r),
> +            PveResource::Network(r) => ResourceView::PveNetwork(r),
> +        }
> +    }
> +}
> +

missing doc string here

> +pub trait AsResourceView {
> +    fn as_resource_view(&self) -> ResourceView<'_>;
> +}
> +
> +impl AsResourceView for Resource {
> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
> +        ResourceView::from(self)
> +    }
> +}
> +
> +impl AsResourceView for &Resource {
> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
> +        ResourceView::from(*self)
> +    }
> +}
> +
> +impl AsResourceView for PveResource {
> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
> +        ResourceView::from(self)
> +    }
> +}
> +
> +impl AsResourceView for &PveResource {
> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
> +        ResourceView::from(*self)
> +    }
> +}
> +
>  #[api(
>      properties: {
>          tags: {





  reply	other threads:[~2026-08-20 11:07 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-18 13:25 [PATCH datacenter-manager v2 0/9] refactor and partially fix #7371 Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 1/9] lib: api types: add new ResourceView type and move accessors there Dominik Csapak
2026-08-20 11:07   ` Lukas Wagner [this message]
2026-08-20 11:20     ` Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 2/9] lib: api types: resource: add 'node' helper to ResourceView Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 3/9] lib: api types: add guest specific getter " Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 4/9] ui: pve: factor out the pve-manager version extraction Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 5/9] ui: renderer: use ResourceView for rendering Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 6/9] ui: pve: tree: reuse `PveResource` for `PveTreeNode` Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 7/9] ui: pve: show ha maintenance mode for nodes Dominik Csapak
2026-08-20 11:07   ` Lukas Wagner
2026-08-20 11:20     ` Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 8/9] ui: pve: node selector: show maintenance badge with node name Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 9/9] ui: pve: node: show HA maintenance badge Dominik Csapak
2026-08-20 11:08 ` [PATCH datacenter-manager v2 0/9] refactor and partially fix #7371 Lukas Wagner
2026-08-21 12:51 ` superseded: " Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DKTQ1PNHRLZ3.2TH9QV54RP7F1@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=d.csapak@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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