]> git.proxmox.com Git - pve-installer.git/commitdiff
tui: introduce `CidrAddress` type for ip address + mask tuples
authorChristoph Heiss <c.heiss@proxmox.com>
Mon, 5 Jun 2023 12:05:04 +0000 (14:05 +0200)
committerChristoph Heiss <c.heiss@proxmox.com>
Wed, 14 Jun 2023 08:39:56 +0000 (10:39 +0200)
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
proxmox-tui-installer/src/main.rs
proxmox-tui-installer/src/options.rs
proxmox-tui-installer/src/utils.rs [new file with mode: 0644]
proxmox-tui-installer/src/views/mod.rs

index 15d7ca0f430b6487a25389407f50ec0f7345e38a..43f4bf7b9cfb16784318687f26c6871e2cde9c47 100644 (file)
@@ -1,6 +1,7 @@
 #![forbid(unsafe_code)]
 
 mod options;
+mod utils;
 mod views;
 
 use crate::options::*;
@@ -408,7 +409,7 @@ fn network_dialog(siv: &mut Cursive) -> InstallerView {
         ))
         .child(FormInputView::new(
             "IP address (CIDR)",
-            CidrAddressEditView::new().content(options.ip_addr, options.cidr_mask),
+            CidrAddressEditView::new().content(options.address),
         ))
         .child(FormInputView::new(
             "Gateway address",
@@ -434,13 +435,10 @@ fn network_dialog(siv: &mut Cursive) -> InstallerView {
                         .get_value()
                 }
 
-                let (ip_addr, cidr_mask) = get_val::<CidrAddressEditView, _>(view, 2)?;
-
                 Some(NetworkOptions {
                     ifname: get_val::<SelectView, _>(view, 0)?,
                     fqdn: get_val::<EditView, _>(view, 1)?,
-                    ip_addr,
-                    cidr_mask,
+                    address: get_val::<CidrAddressEditView, _>(view, 2)?,
                     gateway: get_val::<EditView, _>(view, 3).and_then(|s| s.parse().ok())?,
                     dns_server: get_val::<EditView, _>(view, 3).and_then(|s| s.parse().ok())?,
                 })
index d4c68d7292be14094b52e4e9a310ff77d3944a0b..7f5f48748d2051025cce556535144cfceb3ea6f8 100644 (file)
@@ -1,4 +1,4 @@
-use crate::SummaryOption;
+use crate::{utils::CidrAddress, SummaryOption};
 use std::{
     fmt, iter,
     net::{IpAddr, Ipv4Addr},
@@ -120,8 +120,7 @@ impl Default for PasswordOptions {
 pub struct NetworkOptions {
     pub ifname: String,
     pub fqdn: String,
-    pub ip_addr: IpAddr,
-    pub cidr_mask: usize,
+    pub address: CidrAddress,
     pub gateway: IpAddr,
     pub dns_server: IpAddr,
 }
@@ -132,8 +131,8 @@ impl Default for NetworkOptions {
         Self {
             ifname: String::new(),
             fqdn: "pve.example.invalid".to_owned(),
-            ip_addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
-            cidr_mask: 0,
+            // Safety: The provided mask will always be valid.
+            address: CidrAddress::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0).unwrap(),
             gateway: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
             dns_server: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
         }
@@ -166,10 +165,7 @@ impl InstallerOptions {
             SummaryOption::new("Administator email:", &self.password.email),
             SummaryOption::new("Management interface:", &self.network.ifname),
             SummaryOption::new("Hostname:", &self.network.fqdn),
-            SummaryOption::new(
-                "Host IP (CIDR):",
-                format!("{}/{}", self.network.ip_addr, self.network.cidr_mask),
-            ),
+            SummaryOption::new("Host IP (CIDR):", self.network.address.to_string()),
             SummaryOption::new("Gateway", self.network.gateway.to_string()),
             SummaryOption::new("DNS:", self.network.dns_server.to_string()),
         ]
diff --git a/proxmox-tui-installer/src/utils.rs b/proxmox-tui-installer/src/utils.rs
new file mode 100644 (file)
index 0000000..9a2e6f5
--- /dev/null
@@ -0,0 +1,66 @@
+use std::{
+    fmt,
+    net::{AddrParseError, IpAddr},
+    num::ParseIntError,
+    str::FromStr,
+};
+
+#[derive(Debug)]
+pub enum CidrAddressParseError {
+    NoDelimiter,
+    InvalidAddr(AddrParseError),
+    InvalidMask(Option<ParseIntError>),
+}
+
+#[derive(Clone, Debug)]
+pub struct CidrAddress {
+    addr: IpAddr,
+    mask: usize,
+}
+
+impl CidrAddress {
+    pub fn new(addr: IpAddr, mask: usize) -> Result<Self, CidrAddressParseError> {
+        if mask > 32 {
+            Err(CidrAddressParseError::InvalidMask(None))
+        } else {
+            Ok(Self { addr, mask })
+        }
+    }
+
+    pub fn addr(&self) -> IpAddr {
+        self.addr
+    }
+
+    pub fn mask(&self) -> usize {
+        self.mask
+    }
+}
+
+impl FromStr for CidrAddress {
+    type Err = CidrAddressParseError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let (addr, mask) = s
+            .split_once('/')
+            .ok_or(CidrAddressParseError::NoDelimiter)?;
+
+        let mask = mask
+            .parse()
+            .map_err(|err| CidrAddressParseError::InvalidMask(Some(err)))?;
+
+        if mask > 32 {
+            Err(CidrAddressParseError::InvalidMask(None))
+        } else {
+            Ok(Self {
+                addr: addr.parse().map_err(CidrAddressParseError::InvalidAddr)?,
+                mask,
+            })
+        }
+    }
+}
+
+impl fmt::Display for CidrAddress {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}/{}", self.addr, self.mask)
+    }
+}
index 419e7ed41814dd9aee98f69583bf3c77e669f206..01782123671c7e5642c28046c4366fb4b978170a 100644 (file)
@@ -6,7 +6,9 @@ use cursive::{
     views::{DummyView, EditView, LinearLayout, ResizedView, SelectView, TextView},
     View,
 };
-use std::{marker::PhantomData, net::IpAddr, rc::Rc, str::FromStr};
+use std::{marker::PhantomData, rc::Rc, str::FromStr};
+
+use crate::utils::CidrAddress;
 
 pub use self::table_view::*;
 
@@ -179,8 +181,8 @@ impl FormInputViewGetValue<String> for FormInputView<SelectView> {
     }
 }
 
-impl FormInputViewGetValue<(IpAddr, usize)> for FormInputView<CidrAddressEditView> {
-    fn get_value(&self) -> Option<(IpAddr, usize)> {
+impl FormInputViewGetValue<CidrAddress> for FormInputView<CidrAddressEditView> {
+    fn get_value(&self) -> Option<CidrAddress> {
         self.inner_input().and_then(|v| v.get_values())
     }
 }
@@ -203,13 +205,15 @@ impl CidrAddressEditView {
         Self { view }
     }
 
-    pub fn content(mut self, addr: IpAddr, mask: usize) -> Self {
+    pub fn content(mut self, cidr: CidrAddress) -> Self {
         if let Some(view) = self
             .view
             .get_child_mut(0)
             .and_then(|v| v.downcast_mut::<ResizedView<EditView>>())
         {
-            *view = EditView::new().content(addr.to_string()).full_width();
+            *view = EditView::new()
+                .content(cidr.addr().to_string())
+                .full_width();
         }
 
         if let Some(view) = self
@@ -217,7 +221,7 @@ impl CidrAddressEditView {
             .get_child_mut(2)
             .and_then(|v| v.downcast_mut::<ResizedView<IntegerEditView>>())
         {
-            *view = Self::mask_edit_view(mask);
+            *view = Self::mask_edit_view(cidr.mask());
         }
 
         self
@@ -231,8 +235,8 @@ impl CidrAddressEditView {
             .fixed_width(3)
     }
 
-    fn get_values(&self) -> Option<(IpAddr, usize)> {
-        let ip_addr = self
+    fn get_values(&self) -> Option<CidrAddress> {
+        let addr = self
             .view
             .get_child(0)?
             .downcast_ref::<ResizedView<EditView>>()?
@@ -249,7 +253,7 @@ impl CidrAddressEditView {
             .get_content()
             .ok()?;
 
-        Some((ip_addr, mask))
+        CidrAddress::new(addr, mask).ok()
     }
 }