refactor, added ip validation
Some checks failed
/ Check (push) Successful in 1m33s
/ Format (push) Successful in 1m23s
/ Clippy (push) Has started running
/ Build (push) Has been cancelled

This commit is contained in:
Phani Pavan K
2025-08-29 10:40:24 +05:30
parent 4eab7aeaac
commit ebd66bfaf6
8 changed files with 90 additions and 60 deletions

39
Cargo.lock generated
View File

@@ -17,6 +17,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.21"
@@ -459,6 +468,35 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "regex"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.26" version = "0.1.26"
@@ -585,6 +623,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"color-eyre", "color-eyre",
"ratatui", "ratatui",
"regex",
"serde", "serde",
"serde_json", "serde_json",
] ]

View File

@@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
color-eyre = "0.6.5" color-eyre = "0.6.5"
ratatui = "0.29.0" ratatui = "0.29.0"
regex = "1.11.2"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"

View File

@@ -1,10 +1,7 @@
use crate::app::status::EntryCreation;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::fmt::Display;
error::Error,
fmt::Display,
io::ErrorKind,
num::{IntErrorKind, ParseIntError},
};
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Entry { pub struct Entry {
@@ -25,18 +22,26 @@ impl Display for Entry {
} }
impl Entry { impl Entry {
pub fn new(fromIP: String, toIP: String, fromPort: String, toPort: String) -> Option<Self> { pub fn new(
if fromPort.parse::<i32>().is_ok_and(|a| a > 1 && a < 65535) fromIP: String,
&& toPort.parse::<i32>().is_ok_and(|a| a > 1 && a < 65535) toIP: String,
fromPort: String,
toPort: String,
) -> Result<Self, EntryCreation> {
let ip = Regex::new("/^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}$/").unwrap();
if !fromPort.parse::<i32>().is_ok_and(|a| a > 1 && a < 65535)
|| !toPort.parse::<i32>().is_ok_and(|a| a > 1 && a < 65535)
{ {
Some(Entry { Err(EntryCreation::PortValidationError)
} else if !ip.is_match(&fromIP) || !ip.is_match(&toIP) {
Err(EntryCreation::IPValidationError)
} else {
Ok(Entry {
fromIP, fromIP,
toIP, toIP,
fromPort, fromPort,
toPort, toPort,
}) })
} else {
None
} }
} }
} }

View File

@@ -15,8 +15,6 @@ pub struct AppState {
pub toIP: String, pub toIP: String,
pub toPort: String, pub toPort: String,
pub screen: CurrentScreen, pub screen: CurrentScreen,
pub field: Option<EditingField>,
// pub current: Option<Entry>,
pub currentlyEditing: Option<EditingField>, pub currentlyEditing: Option<EditingField>,
pub entries: Vec<Entry>, pub entries: Vec<Entry>,
pub confDir: String, pub confDir: String,
@@ -32,7 +30,6 @@ impl AppState {
toPort: String::new(), toPort: String::new(),
currentlyEditing: None, currentlyEditing: None,
screen: CurrentScreen::Main, screen: CurrentScreen::Main,
field: None,
entries: settings.entries, entries: settings.entries,
confDir: confDir, confDir: confDir,
} }
@@ -45,7 +42,7 @@ impl AppState {
self.fromPort.clone(), self.fromPort.clone(),
self.toPort.clone(), self.toPort.clone(),
) { ) {
Some(entry) => { Ok(entry) => {
self.entries.push(entry); self.entries.push(entry);
self.fromIP = String::new(); self.fromIP = String::new();
self.toIP = String::new(); self.toIP = String::new();
@@ -59,17 +56,6 @@ impl AppState {
} }
} }
pub fn startEditing(&mut self) {
if let Some(currentField) = &self.currentlyEditing {
match currentField {
EditingField::ToIP => {}
_ => self.currentlyEditing = Some(EditingField::FromIP),
}
} else {
self.currentlyEditing = Some(EditingField::FromIP);
}
}
pub fn nextField(&mut self) { pub fn nextField(&mut self) {
if let Some(currentField) = &self.currentlyEditing { if let Some(currentField) = &self.currentlyEditing {
self.currentlyEditing = match currentField { self.currentlyEditing = match currentField {

View File

@@ -25,7 +25,7 @@ impl Settings {
Err(_) => { Err(_) => {
let newSet = Settings { entries: vec![] }; let newSet = Settings { entries: vec![] };
let payload = serde_json::to_string_pretty(&newSet).unwrap(); let payload = serde_json::to_string_pretty(&newSet).unwrap();
std::fs::write(config, payload); let _ = std::fs::write(config, payload);
newSet newSet
} }
} }
@@ -33,6 +33,6 @@ impl Settings {
pub fn save(&self, config: &String) { pub fn save(&self, config: &String) {
let payload = serde_json::to_string(self).unwrap(); let payload = serde_json::to_string(self).unwrap();
std::fs::write(config, payload); let _ = std::fs::write(config, payload);
} }
} }

View File

@@ -1,4 +1,4 @@
#[allow(non_snake_case)] #![allow(non_snake_case)]
mod app; mod app;
mod ui; mod ui;
@@ -114,7 +114,7 @@ fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<
(KeyModifiers::NONE, KeyCode::Tab) => app.nextField(), (KeyModifiers::NONE, KeyCode::Tab) => app.nextField(),
(KeyModifiers::SHIFT, KeyCode::Tab) => app.prevField(), (KeyModifiers::SHIFT, KeyCode::Tab) => app.prevField(),
(m, KeyCode::Char(v)) => { (_, KeyCode::Char(v)) => {
if let Some(e) = &app.currentlyEditing { if let Some(e) = &app.currentlyEditing {
let mut isIP = false; let mut isIP = false;
let opField = match e { let opField = match e {

23
src/ui/centeredRect.rs Normal file
View File

@@ -0,0 +1,23 @@
use ratatui::layout::{Constraint, Direction, Layout, Rect};
pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
// Cut the given rectangle into three vertical pieces
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
// Then cut the middle vertical piece into three width-wise pieces
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1] // Return the middle chunk
}

View File

@@ -1,18 +1,17 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
mod centeredRect;
use ratatui::{ use ratatui::{
Frame, Frame,
layout::{Constraint, Layout}, layout::{Constraint, Direction, Layout},
style::{Color, Style}, style::{Color, Style},
text::{Line, Span, Text}, text::{Line, Span, Text},
widgets::{Block, Borders, List, ListItem, Paragraph}, widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
};
use ratatui::{
layout::{Direction, Rect},
widgets::Wrap,
}; };
use crate::app::status::CurrentScreen; use crate::app::status::CurrentScreen;
use crate::app::{AppState, status::EditingField}; use crate::app::{AppState, status::EditingField};
use crate::ui::centeredRect::centered_rect;
pub fn ui(frame: &mut Frame, app: &AppState) { pub fn ui(frame: &mut Frame, app: &AppState) {
let chunks = Layout::default() let chunks = Layout::default()
@@ -131,7 +130,6 @@ pub fn ui(frame: &mut Frame, app: &AppState) {
EditingField::ToIP => "To IP", EditingField::ToIP => "To IP",
EditingField::FromPort => "From Port", EditingField::FromPort => "From Port",
EditingField::ToPort => "To Port", EditingField::ToPort => "To Port",
_ => "Something ",
}; };
Span::styled( Span::styled(
format!("Editing: {curEdit}"), format!("Editing: {curEdit}"),
@@ -170,25 +168,3 @@ pub fn ui(frame: &mut Frame, app: &AppState) {
frame.render_widget(helpFooter, footerChunks[0]); frame.render_widget(helpFooter, footerChunks[0]);
frame.render_widget(keyBindFooter, footerChunks[1]); frame.render_widget(keyBindFooter, footerChunks[1]);
} }
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
// Cut the given rectangle into three vertical pieces
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
// Then cut the middle vertical piece into three width-wise pieces
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1] // Return the middle chunk
}