diff --git a/docs/commands.md b/docs/commands.md index 7f80d25..284a92e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -12,6 +12,8 @@ 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. | +| `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 7919cd2..776796d 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -21,6 +21,8 @@ pub enum Builtin { Headers, Vars, Requests, + Remove(String), + Rename(String, String), Clear, Timeout(u64), } @@ -66,6 +68,14 @@ pub fn handle( } } + Builtin::Remove(name) => { + 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 874bf72..0f0c4d4 100644 --- a/src/help.rs +++ b/src/help.rs @@ -32,6 +32,8 @@ pub fn help_text() -> String { unset unset header save + remove + rename run requests vars diff --git a/src/helper.rs b/src/helper.rs index 0512173..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", + "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" => { + "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 47c8b04..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" => { + | "help" | "history" | "rerun" | "clear" | "timeout" | "remove" | "rename" => { let result = parse_builtin(input)?; Ok(Parsed::Builtin(result)) } @@ -185,6 +185,23 @@ 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())) + } + } + "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 { @@ -269,6 +286,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()); @@ -317,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 051b7b9..bc7e0ef 100644 --- a/src/state.rs +++ b/src/state.rs @@ -84,6 +84,22 @@ 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 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() } @@ -217,6 +233,41 @@ 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 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;