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: {
next prev parent 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