From e46da40f207a5c15fc0a32d19f43e0fa2101d76b Mon Sep 17 00:00:00 2001 From: Harshil Date: Sun, 12 Jul 2026 20:58:00 +0530 Subject: [PATCH 1/2] feat: new builtin remove for deleting saved requests --- docs/commands.md | 1 + src/builtin.rs | 5 +++++ src/help.rs | 1 + src/helper.rs | 4 ++-- src/parser.rs | 22 +++++++++++++++++++++- src/state.rs | 24 ++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 7f80d25..cad46de 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -12,6 +12,7 @@ Beyond standard HTTP methods (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, ` | `unset` | `unset ` | Remove a variable. | | `unset header` | `unset header ` | Remove a global header. | | `save` | `save ` | Save the last executed request to memory. | +| `remove` | `remove ` | Delete a saved request by name. | | `run` | `run ` | Execute a saved request. | | `vars` | `vars` | List all session variables. | | `headers` | `headers` | List all global headers. | diff --git a/src/builtin.rs b/src/builtin.rs index 7919cd2..97902f1 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -21,6 +21,7 @@ pub enum Builtin { Headers, Vars, Requests, + Remove(String), Clear, Timeout(u64), } @@ -66,6 +67,10 @@ pub fn handle( } } + Builtin::Remove(name) => { + ctx.remove_request(&name)?; + } + Builtin::Save(name) => { ctx.save_request(name)?; } diff --git a/src/help.rs b/src/help.rs index 874bf72..679397a 100644 --- a/src/help.rs +++ b/src/help.rs @@ -32,6 +32,7 @@ pub fn help_text() -> String { unset unset header save + remove run requests vars diff --git a/src/helper.rs b/src/helper.rs index 0512173..cef74b4 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -12,7 +12,7 @@ use crate::state::ShellState; const BUILTINS: &[&str] = &[ "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "base", "header", "exit", "help", "history", "rerun", "set", "timeout", "unset", "save", "run", "vars", "headers", "requests", - "clear", + "clear", "remove", ]; pub struct ShellHelper { @@ -56,7 +56,7 @@ impl Completer for ShellHelper { let parts: Vec<&str> = before_cursor.split_whitespace().collect(); match parts[0] { - "run" => { + "run" | "remove" => { let state = self.state.borrow(); let matches: Vec = state .get_all_requests() diff --git a/src/parser.rs b/src/parser.rs index 47c8b04..d4c58ac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ pub fn parse(input: String) -> Result { } "base" | "set" | "unset" | "header" | "headers" | "vars" | "requests" | "save" | "run" - | "help" | "history" | "rerun" | "clear" | "timeout" => { + | "help" | "history" | "rerun" | "clear" | "timeout" | "remove" => { let result = parse_builtin(input)?; Ok(Parsed::Builtin(result)) } @@ -185,6 +185,13 @@ fn parse_builtin(line: String) -> Result { Ok(Builtin::Timeout(secs)) } } + "remove" => { + if tokens.len() != 2 { + Err("usage: remove ".to_string()) + } else { + Ok(Builtin::Remove(tokens[1].to_string())) + } + } "history" => Ok(Builtin::History), "rerun" => { if tokens.len() != 2 { @@ -269,6 +276,19 @@ mod tests { )); } + #[test] + fn parse_remove_builtin() { + let result = parse("remove my-req".to_string()); + assert!(result.is_ok()); + assert!(matches!( + result.unwrap(), + Parsed::Builtin(Builtin::Remove(name)) if name == "my-req" + )); + + let result = parse("remove".to_string()); + assert!(result.is_err()); + } + #[test] fn parse_unset_variable() { let result = parse("unset foo".to_string()); diff --git a/src/state.rs b/src/state.rs index 051b7b9..a24cdfe 100644 --- a/src/state.rs +++ b/src/state.rs @@ -84,6 +84,13 @@ impl ShellState { &self.saved_requests } + pub fn remove_request(&mut self, name: &str) -> Result<(), String> { + self.saved_requests + .remove(name) + .ok_or_else(|| format!("no saved request: {name}"))?; + Ok(()) + } + pub fn get_base_url(&self) -> Option<&str> { self.base_url.as_deref() } @@ -217,6 +224,23 @@ mod test { assert_eq!(saved.unwrap().path, "/test"); } + #[test] + fn remove_request_deletes_it() { + use crate::request::Method; + let mut state = ShellState::new(); + let req = crate::request::Request::new(Method::GET, "/test".to_string()); + state.set_last_request(req); + state.save_request("myreq".to_string()).unwrap(); + assert!(state.remove_request("myreq").is_ok()); + assert!(state.get_request("myreq").is_none()); + } + + #[test] + fn remove_request_missing_returns_error() { + let mut state = ShellState::new(); + assert!(state.remove_request("nonexistent").is_err()); + } + #[test] fn save_and_load_state_roundtrip() { use crate::request::Method; From 43ae2c5a80cdd1620efcd6aecce2484c94d19b10 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 13 Jul 2026 16:21:53 +0530 Subject: [PATCH 2/2] feat: rename command added for renaming saved requests --- docs/commands.md | 1 + src/builtin.rs | 5 +++++ src/help.rs | 1 + src/helper.rs | 4 ++-- src/parser.rs | 28 +++++++++++++++++++++++++++- src/state.rs | 27 +++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index cad46de..284a92e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -13,6 +13,7 @@ Beyond standard HTTP methods (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, ` | `unset header` | `unset header ` | Remove a global header. | | `save` | `save ` | Save the last executed request to memory. | | `remove` | `remove ` | Delete a saved request by name. | +| `rename` | `rename ` | Rename a saved request | | `run` | `run ` | Execute a saved request. | | `vars` | `vars` | List all session variables. | | `headers` | `headers` | List all global headers. | diff --git a/src/builtin.rs b/src/builtin.rs index 97902f1..776796d 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -22,6 +22,7 @@ pub enum Builtin { Vars, Requests, Remove(String), + Rename(String, String), Clear, Timeout(u64), } @@ -71,6 +72,10 @@ pub fn handle( ctx.remove_request(&name)?; } + Builtin::Rename(existing_name, new_name) => { + ctx.rename_request(&existing_name, new_name)?; + } + Builtin::Save(name) => { ctx.save_request(name)?; } diff --git a/src/help.rs b/src/help.rs index 679397a..0f0c4d4 100644 --- a/src/help.rs +++ b/src/help.rs @@ -33,6 +33,7 @@ pub fn help_text() -> String { unset header save remove + rename run requests vars diff --git a/src/helper.rs b/src/helper.rs index cef74b4..d139d2b 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -12,7 +12,7 @@ use crate::state::ShellState; const BUILTINS: &[&str] = &[ "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "base", "header", "exit", "help", "history", "rerun", "set", "timeout", "unset", "save", "run", "vars", "headers", "requests", - "clear", "remove", + "clear", "remove", "rename", ]; pub struct ShellHelper { @@ -56,7 +56,7 @@ impl Completer for ShellHelper { let parts: Vec<&str> = before_cursor.split_whitespace().collect(); match parts[0] { - "run" | "remove" => { + "run" | "remove" | "rename" if parts.len() == 1 => { let state = self.state.borrow(); let matches: Vec = state .get_all_requests() diff --git a/src/parser.rs b/src/parser.rs index d4c58ac..93ea091 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ pub fn parse(input: String) -> Result { } "base" | "set" | "unset" | "header" | "headers" | "vars" | "requests" | "save" | "run" - | "help" | "history" | "rerun" | "clear" | "timeout" | "remove" => { + | "help" | "history" | "rerun" | "clear" | "timeout" | "remove" | "rename" => { let result = parse_builtin(input)?; Ok(Parsed::Builtin(result)) } @@ -192,6 +192,16 @@ fn parse_builtin(line: String) -> Result { Ok(Builtin::Remove(tokens[1].to_string())) } } + "rename" => { + if tokens.len() != 3 { + Err("usage: rename ".to_string()) + } else { + Ok(Builtin::Rename( + tokens[1].to_string(), + tokens[2].to_string(), + )) + } + } "history" => Ok(Builtin::History), "rerun" => { if tokens.len() != 2 { @@ -337,4 +347,20 @@ mod tests { let result = parse("header key".to_string()); assert!(result.is_err()); } + + #[test] + fn parse_rename_builtin() { + let result = parse("rename old-name new-name".to_string()); + assert!(result.is_ok()); + assert!(matches!( + result.unwrap(), + Parsed::Builtin(Builtin::Rename(old, new)) if old == "old-name" && new == "new-name" + )); + + let result = parse("rename".to_string()); + assert!(result.is_err()); + + let result = parse("rename only-one".to_string()); + assert!(result.is_err()); + } } diff --git a/src/state.rs b/src/state.rs index a24cdfe..bc7e0ef 100644 --- a/src/state.rs +++ b/src/state.rs @@ -91,6 +91,15 @@ impl ShellState { Ok(()) } + pub fn rename_request(&mut self, existing_name: &str, new_name: String) -> Result<(), String> { + if let Some(value) = self.saved_requests.remove(existing_name) { + self.saved_requests.insert(new_name, value); + Ok(()) + } else { + Err(format!("no saved request: {existing_name}")) + } + } + pub fn get_base_url(&self) -> Option<&str> { self.base_url.as_deref() } @@ -241,6 +250,24 @@ mod test { assert!(state.remove_request("nonexistent").is_err()); } + #[test] + fn rename_request_renames_it() { + use crate::request::Method; + let mut state = ShellState::new(); + let req = crate::request::Request::new(Method::GET, "/test".to_string()); + state.set_last_request(req); + state.save_request("old-name".to_string()).unwrap(); + assert!(state.rename_request("old-name", "new-name".to_string()).is_ok()); + assert!(state.get_request("old-name").is_none()); + assert!(state.get_request("new-name").is_some()); + } + + #[test] + fn rename_request_missing_returns_error() { + let mut state = ShellState::new(); + assert!(state.rename_request("nonexistent", "new".to_string()).is_err()); + } + #[test] fn save_and_load_state_roundtrip() { use crate::request::Method;