Add an api submodule to the section config module, that provides the
types that are intended to be returned and accepted by the Perl API in
Proxmox VE. This allows us to decouple the format returned in the API
from the configuration format.

This is particularly relevant in the case of the NodeSection type.
While the section config stores the composite ID of the node as the ID
of the section in the section config (and therefore as a single string
/ property), we want to be able to return them as independent fields
from the API, to avoid having to parse the ID everywhere else we want
to use it. Thanks to the generic NodeSection type we only have to
define the conversion from / to the API type once, while the
protocol-specific types can stay the same.

For the fabrics, we simply re-use the section_config types for now,
but by re-exporting them as type alias we are more flexible in
possibly changing the API types or the underlying section config types
later on.

Co-authored-by: Gabriel Goller <g.gol...@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com>
---
 .../src/sdn/fabric/section_config/fabric.rs   |   5 +
 .../src/sdn/fabric/section_config/node.rs     | 159 ++++++++++++++++++
 2 files changed, 164 insertions(+)

diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs 
b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
index 8ecf725..75a3093 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
@@ -233,3 +233,8 @@ pub enum FabricDeletableProperties<T> {
     #[serde(untagged)]
     Protocol(T),
 }
+
+pub mod api {
+    pub type Fabric = super::Fabric;
+    pub type FabricUpdater = super::FabricUpdater;
+}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs 
b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
index bd5ffea..b1b2034 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
@@ -223,3 +223,162 @@ impl From<NodeSection<OspfNodeProperties>> for Node {
         Self::Ospf(value)
     }
 }
+
+/// API types for SDN fabric node configurations.
+///
+/// This module provides specialized types that are used for API interactions 
when retrieving,
+/// creating, or updating fabric/node configurations. These types serialize 
differently than their
+/// section-config configuration counterparts to be nicer client-side.
+///
+/// The module includes:
+/// - [NodeData<T>]: API-friendly version of [NodeSection<T>] that flattens 
the node identifier
+///   into separate `fabric_id` and `node_id` fields
+/// - [Node]: API-version of [super::Node]
+/// - [NodeDataUpdater]
+/// - [NodeDeletableProperties]
+///
+/// These types include conversion methods to transform between API 
representations and internal 
+/// configuration objects.
+pub mod api {
+    use serde::{Deserialize, Serialize};
+
+    use proxmox_schema::{Updater, UpdaterType};
+
+    use crate::sdn::fabric::section_config::protocol::{
+        openfabric::{
+            OpenfabricNodeDeletableProperties, OpenfabricNodeProperties,
+            OpenfabricNodePropertiesUpdater,
+        },
+        ospf::{OspfNodeDeletableProperties, OspfNodeProperties, 
OspfNodePropertiesUpdater},
+    };
+
+    use super::*;
+
+    /// API-equivalent to [NodeSection<T>].
+    ///
+    /// The difference is that instead of serializing fabric_id and node_id 
into a single string
+    /// (`{fabric_id}_{node_id}`), are serialized normally as two distinct 
properties. This
+    /// prevents us from needing to parse the node_id in the frontend using 
`split("_")`.
+    #[derive(Debug, Clone, Serialize, Deserialize)]
+    pub struct NodeData<T> {
+        fabric_id: FabricId,
+        node_id: NodeId,
+
+        /// IPv4 for this node in the Ospf fabric
+        #[serde(skip_serializing_if = "Option::is_none")]
+        ip: Option<Ipv4Addr>,
+
+        /// IPv6 for this node in the Ospf fabric
+        #[serde(skip_serializing_if = "Option::is_none")]
+        ip6: Option<Ipv6Addr>,
+
+        #[serde(flatten)]
+        properties: T,
+    }
+
+    impl<T> From<NodeSection<T>> for NodeData<T> {
+        fn from(value: NodeSection<T>) -> Self {
+            Self {
+                fabric_id: value.id.fabric_id,
+                node_id: value.id.node_id,
+                ip: value.ip,
+                ip6: value.ip6,
+                properties: value.properties,
+            }
+        }
+    }
+
+    impl<T> From<NodeData<T>> for NodeSection<T> {
+        fn from(value: NodeData<T>) -> Self {
+            let id = NodeSectionId::new(value.fabric_id, value.node_id);
+
+            Self {
+                id,
+                ip: value.ip,
+                ip6: value.ip6,
+                properties: value.properties,
+            }
+        }
+    }
+
+    /// API-equivalent to [super::Node].
+    #[derive(Debug, Clone, Serialize, Deserialize)]
+    #[serde(rename_all = "snake_case", tag = "protocol")]
+    pub enum Node {
+        Openfabric(NodeData<OpenfabricNodeProperties>),
+        Ospf(NodeData<OspfNodeProperties>),
+    }
+
+    impl From<super::Node> for Node {
+        fn from(value: super::Node) -> Self {
+            match value {
+                super::Node::Openfabric(node_section) => 
Self::Openfabric(node_section.into()),
+                super::Node::Ospf(node_section) => 
Self::Ospf(node_section.into()),
+            }
+        }
+    }
+
+    impl From<Node> for super::Node {
+        fn from(value: Node) -> Self {
+            match value {
+                Node::Openfabric(node_section) => 
Self::Openfabric(node_section.into()),
+                Node::Ospf(node_section) => Self::Ospf(node_section.into()),
+            }
+        }
+    }
+
+    impl UpdaterType for NodeData<OpenfabricNodeProperties> {
+        type Updater =
+            NodeDataUpdater<OpenfabricNodePropertiesUpdater, 
OpenfabricNodeDeletableProperties>;
+    }
+
+    impl UpdaterType for NodeData<OspfNodeProperties> {
+        type Updater = NodeDataUpdater<OspfNodePropertiesUpdater, 
OspfNodeDeletableProperties>;
+    }
+
+    #[derive(Debug, Clone, Serialize, Deserialize)]
+    pub struct NodeDataUpdater<T, D> {
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub(crate) ip: Option<Ipv4Addr>,
+
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub(crate) ip6: Option<Ipv6Addr>,
+
+        #[serde(flatten)]
+        pub(crate) properties: T,
+
+        #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
+        pub(crate) delete: Vec<NodeDeletableProperties<D>>,
+    }
+
+    impl<T: UpdaterType + Updater, D> UpdaterType for NodeDataUpdater<T, D> {
+        type Updater = NodeDataUpdater<T::Updater, D>;
+    }
+
+    impl<T: Updater, D> Updater for NodeDataUpdater<T, D> {
+        fn is_empty(&self) -> bool {
+            T::is_empty(&self.properties)
+                && self.ip.is_none()
+                && self.ip6.is_none()
+                && self.delete.is_empty()
+        }
+    }
+
+    #[derive(Debug, Clone, Serialize, Deserialize)]
+    #[serde(rename_all = "snake_case", tag = "protocol")]
+    pub enum NodeUpdater {
+        Openfabric(
+            NodeDataUpdater<OpenfabricNodePropertiesUpdater, 
OpenfabricNodeDeletableProperties>,
+        ),
+        Ospf(NodeDataUpdater<OspfNodePropertiesUpdater, 
OspfNodeDeletableProperties>),
+    }
+
+    #[derive(Debug, Clone, Serialize, Deserialize)]
+    #[serde(rename_all = "snake_case")]
+    pub enum NodeDeletableProperties<T> {
+        Ip,
+        Ip6,
+        #[serde(untagged)]
+        Protocol(T),
+    }
+}
-- 
2.39.5


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

Reply via email to