diff --git a/.gitignore b/.gitignore index 69be759..d7da0b5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .vs testapi.txt /postman -*.json diff --git a/imgs/saved_request.png b/imgs/saved_request.png index edee01b..b033733 100644 Binary files a/imgs/saved_request.png and b/imgs/saved_request.png differ diff --git a/src/app.rs b/src/app.rs index c261732..be037e8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -67,12 +67,18 @@ impl<'a> Default for App<'a> { state: None, current_screen: Screen::Home, response: None, - db: Box::new(DB::new().unwrap()), + db: Box::new(DB::new().expect("Failed to create database")), } } } impl<'a> App<'a> { + fn new_test_db() -> Self { + Self { + db: Box::new(DB::new_test().expect("Failed to create database")), + ..Default::default() + } + } pub fn new() -> Self { Self::default() } @@ -185,7 +191,7 @@ impl<'a> App<'a> { } Some( Screen::InputMenu(_) - | Screen::CmdMenu(_) + | Screen::CmdMenu { .. } | Screen::ColMenu(_) | Screen::KeysMenu(_), ) => self.go_back_screen(), @@ -335,8 +341,9 @@ impl<'a> App<'a> { match collection { Ok(collection) => { let name = collection.info.name.clone(); + let description = collection.info.description.clone(); let cmds: Vec = collection.into(); - self.db.add_collection(&name, cmds.as_slice()) + self.db.add_collection(&name, &description, cmds.as_slice()) } Err(e) => Err(e.into()), } @@ -375,7 +382,7 @@ impl<'a> App<'a> { pub fn delete_item(&mut self, ind: i32) -> Result<(), rusqlite::Error> { match self.current_screen { - Screen::CmdMenu(_) => self.db.as_ref().delete_command(ind), + Screen::CmdMenu { .. } => self.db.as_ref().delete_command(ind), Screen::KeysMenu(_) => self.db.as_ref().delete_key(ind), Screen::ViewSavedCollections => self.db.as_ref().delete_collection(ind), _ => Ok(()), @@ -453,7 +460,7 @@ impl<'a> App<'a> { #[cfg(test)] pub mod tests { use super::App; - use crate::request::curl::AuthKind; + use crate::request::curl::{AuthKind, Curl}; #[test] fn test_basic_get_method() { @@ -767,4 +774,32 @@ pub mod tests { std::fs::remove_file("test.txt").unwrap(); let _ = std::fs::remove_file("cookie-jar"); } + #[test] + fn test_import_postman_collection() { + let mut app = App::new_test_db(); + let path = "test_collection.json"; + let res = app.import_postman_collection(path); + let file = std::fs::File::open(path).unwrap(); + let collection: crate::database::postman::PostmanCollection = + serde_json::from_reader(file).unwrap(); + assert_eq!(collection.info.name.clone().as_str(), "Test Collection"); + let cmds: Vec = collection.into(); + let db = app.db.as_ref(); + let collections = db.get_collections().unwrap(); + let commands = db.get_commands(None).unwrap(); + let command = commands[0].clone(); + let curl: Curl = serde_json::from_str(command.get_curl_json()).unwrap(); + assert!(res.is_ok()); + assert_eq!(collections.len(), 1); + assert_eq!(commands.len(), cmds.len()); + assert_eq!(curl.get_method().to_string(), "POST"); + assert_eq!(curl.get_url(), "https://echo.getpostman.com/post"); + assert_eq!( + curl.headers, + Some(vec![ + String::from("Content-Type: application/json"), + String::from("Host: echo.getpostman.com") + ]) + ); + } } diff --git a/src/database/db.rs b/src/database/db.rs index 1338203..2d50a0a 100644 --- a/src/database/db.rs +++ b/src/database/db.rs @@ -11,16 +11,20 @@ use std::{ #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct SavedCommand { - id: i32, + pub id: i32, command: String, + pub description: Option, + pub label: Option, curl_json: String, - collection_id: Option, + pub collection_id: Option, + pub collection_name: Option, } #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct SavedCollection { id: i32, - name: String, + pub name: String, + pub description: Option, } impl Display for SavedCollection { @@ -42,6 +46,23 @@ pub struct DB { } impl DB { + pub fn new_test() -> Result { + let conn = Connection::open_in_memory()?; + conn.execute( + "CREATE TABLE commands (id INTEGER PRIMARY KEY, command TEXT, label TEXT, description TEXT, curl_json TEXT, collection_id INT);", + params![], + )?; + conn.execute( + "CREATE TABLE keys (id INTEGER PRIMARY KEY, key TEXT, label TEXT);", + params![], + )?; + conn.execute( + "CREATE TABLE collections (id INTEGER PRIMARY KEY, name TEXT, description TEXT);", + params![], + )?; + Ok(DB { conn }) + } + pub fn new() -> Result { let mut _path: PathBuf = PathBuf::new(); if std::env::var("CUTE_DB_PATH").is_ok() { @@ -79,7 +100,7 @@ impl DB { conn.execute("BEGIN;", params![])?; // collection_id needs to be nullable conn.execute( - "CREATE TABLE IF NOT EXISTS commands (id INTEGER PRIMARY KEY, command TEXT, curl_json TEXT, collection_id INT NULL);", + "CREATE TABLE IF NOT EXISTS commands (id INTEGER PRIMARY KEY, label TEXT, description TEXT, command TEXT, curl_json TEXT, collection_id INT);", params![], )?; @@ -89,7 +110,7 @@ impl DB { )?; conn.execute( - "CREATE TABLE IF NOT EXISTS collections (id INTEGER PRIMARY KEY, name TEXT);", + "CREATE TABLE IF NOT EXISTS collections (id INTEGER PRIMARY KEY, name TEXT, description TEXT);", params![], )?; @@ -106,6 +127,18 @@ impl DB { Ok(()) } + pub fn set_collection_description( + &self, + id: i32, + description: &str, + ) -> Result<(), rusqlite::Error> { + let mut stmt = self + .conn + .prepare("UPDATE collections SET description = ? WHERE id = ?")?; + stmt.execute(params![description, id])?; + Ok(()) + } + pub fn get_number_of_commands_in_collection(&self, id: i32) -> Result { let mut stmt = self .conn @@ -115,45 +148,48 @@ impl DB { } #[rustfmt::skip] - pub fn add_collection(&self, name: &str, commands: &[SavedCommand]) -> Result<(), Box> { + pub fn add_collection(&self, name: &str, desc: &str, commands: &[SavedCommand]) -> Result<(), Box> { let mut stmt = self .conn - .prepare("INSERT INTO collections (name) VALUES (?1)")?; - let _ = stmt.execute(params![name])?; - let mut stmt = self - .conn - .prepare("SELECT id FROM collections WHERE name = ?1")?; - let id: i32 = stmt.query_row(params![name], |row| row.get(0))?; + .prepare("INSERT INTO collections (name, description) VALUES (?1, ?2)")?; + let id = stmt.insert(params![name, desc])?; for command in commands { - self.add_command_from_collection(&command.command, &command.curl_json, id)?; + self.add_command_from_collection(&command.command, command.label.as_deref(), command.description.as_deref(), &command.curl_json, id as i32)?; } Ok(()) } pub fn get_command_by_id(&self, id: i32) -> Result { - let mut stmt = self - .conn - .prepare("SELECT id, command, curl_json, collection_id from commands WHERE id = ?1")?; + let mut stmt = self.conn.prepare( + "SELECT cmd.id, cmd.command, cmd.label, cmd.description, cmd.curl_json, cmd.collection_id, col.name as collection_name FROM commands cmd LEFT JOIN collections col ON cmd.collection_id = col.id WHERE cmd.id = ?" + )?; stmt.query_row(params![id], |row| { Ok(SavedCommand { id: row.get(0)?, command: row.get(1)?, - curl_json: row.get(2)?, - collection_id: row.get(3)?, + label: row.get(2)?, + description: row.get(3)?, + curl_json: row.get(4)?, + collection_id: row.get(5)?, + collection_name: row.get(6)?, }) }) } - pub fn get_collection_by_id(&self, id: i32) -> Result { + pub fn get_collection_by_id(&self, id: i32) -> Result { let mut stmt = self .conn - .prepare("SELECT id, name FROM collections WHERE id = ?")?; - let collection = stmt.query_row(params![id], |row| { - Ok(SavedCollection { - id: row.get(0)?, - name: row.get(1)?, + .prepare("SELECT id, name, description FROM collections WHERE id = ?") + .map_err(|_| "No Collection".to_string())?; + let collection = stmt + .query_row(params![id], |row| { + Ok(SavedCollection { + id: row.get(0)?, + name: row.get(1)?, + description: row.get(2)?, + }) }) - })?; + .map_err(|_| ("No Collection".to_string()))?; Ok(collection) } @@ -166,11 +202,14 @@ impl DB { } pub fn get_collections(&self) -> Result> { - let mut stmt = self.conn.prepare("SELECT id, name FROM collections")?; + let mut stmt = self + .conn + .prepare("SELECT id, name, description FROM collections")?; let rows = stmt.query_map(params![], |row| { Ok(SavedCollection { id: row.get(0)?, name: row.get(1)?, + description: row.get(2)?, }) })?; Ok(rows @@ -188,25 +227,55 @@ impl DB { &self, command: &str, json_str: String, - col_name: Option, + col_id: Option, ) -> Result<(), rusqlite::Error> { let mut stmt = self.conn.prepare( "INSERT INTO commands (command, curl_json, collection_id) VALUES (?1, ?2, ?3)", )?; - let _ = stmt.execute(params![command, &json_str, col_name])?; + let _ = stmt.execute(params![command, &json_str, col_id])?; Ok(()) } + pub fn set_command_description( + &self, + id: i32, + description: &str, + ) -> Result, rusqlite::Error> { + let mut stmt = self + .conn + .prepare("UPDATE commands SET description = ?1 WHERE id = ?2")?; + stmt.execute(params![description, id])?; + let mut stmt = self + .conn + .prepare("SELECT collection_id FROM commands WHERE id = ?")?; + let collection_id: Option = stmt.query_row(params![id], |row| row.get(0))?; + Ok(collection_id) + } + + pub fn set_command_label(&self, id: i32, label: &str) -> Result, rusqlite::Error> { + let mut stmt = self + .conn + .prepare("UPDATE commands SET label = ?1 WHERE id = ?2")?; + stmt.execute(params![label, id])?; + let mut stmt = self + .conn + .prepare("SELECT collection_id FROM commands WHERE id = ?")?; + let collection_id: Option = stmt.query_row(params![id], |row| row.get(0))?; + Ok(collection_id) + } + pub fn add_command_from_collection( &self, command: &str, + label: Option<&str>, + desc: Option<&str>, json_str: &str, collection_id: i32, ) -> Result<(), rusqlite::Error> { let mut stmt = self.conn.prepare( - "INSERT INTO commands (command, curl_json, collection_id) VALUES (?1, ?2, ?3)", + "INSERT INTO commands (command, label, description, curl_json, collection_id) VALUES (?1, ?2, ?3, ?4, ?5)", )?; - let _ = stmt.execute(params![command, &json_str, collection_id])?; + let _ = stmt.execute(params![command, label, desc, json_str, collection_id])?; Ok(()) } @@ -236,26 +305,32 @@ impl DB { if let Some(id) = id { let mut stmt = self .conn - .prepare("SELECT id, command, curl_json, collection_id FROM commands WHERE collection_id = ?")?; + .prepare("SELECT cmd.id, cmd.command, cmd.label, cmd.description, cmd.curl_json, cmd.collection_id, col.name as collection_name FROM commands cmd LEFT JOIN collections col ON cmd.collection_id = col.id WHERE cmd.collection_id = ?")?; let rows = stmt.query_map(params![id], |row| { Ok(SavedCommand { id: row.get(0)?, command: row.get(1)?, - curl_json: row.get(2)?, - collection_id: row.get(3)?, + label: row.get(2)?, + description: row.get(3)?, + curl_json: row.get(4)?, + collection_id: row.get(5)?, + collection_name: row.get(6)?, }) })?; - return Ok(rows.into_iter().map(|row| row.unwrap()).collect()); + return Ok(rows.into_iter().filter_map(|row| row.ok()).collect()); } let mut stmt = self .conn - .prepare("SELECT id, command, curl_json FROM commands")?; + .prepare("SELECT cmd.id, cmd.command, cmd.label, cmd.description, cmd.curl_json, cmd.collection_id, col.name FROM commands cmd LEFT JOIN collections col ON cmd.collection_id = col.id")?; let rows = stmt.query_map(params![], |row| { Ok(SavedCommand { id: row.get(0)?, command: row.get(1)?, - curl_json: row.get(2)?, - collection_id: None, + label: row.get(2)?, + description: row.get(3)?, + curl_json: row.get(4)?, + collection_id: row.get(5)?, + collection_name: row.get(6)?, }) })?; let mut commands = Vec::new(); @@ -328,11 +403,19 @@ impl SavedCommand { Ok(serde_json::to_string(&self).expect("Failed to serialize")) } - pub fn new(command: &str, curl_json: &str, collection_id: Option) -> Self { + pub fn new( + command: &str, + label: Option, + description: Option, + curl_json: &str, + collection_id: Option, + ) -> Self { SavedCommand { command: command.to_string(), + label, curl_json: curl_json.to_string(), collection_id, + description, ..Default::default() } } diff --git a/src/database/postman.rs b/src/database/postman.rs index 191f4f1..faa4aa6 100644 --- a/src/database/postman.rs +++ b/src/database/postman.rs @@ -1,8 +1,8 @@ use super::db::SavedCommand; -use crate::request::curl::Curl; +use crate::request::curl::{Curl, Method}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; #[derive(Serialize, Debug, Deserialize)] pub struct PostmanCollection { @@ -13,41 +13,42 @@ pub struct PostmanCollection { impl From for Vec { fn from(collection: PostmanCollection) -> Vec { let mut saved_commands = Vec::new(); - let mut curl_cmd = Curl::new(); collection.item.iter().for_each(|item| { + let mut curl_cmd = Curl::new_serializing(); + let mut cmd_name: Option = None; + let mut description: Option = None; + if let Some(name) = item.get("name") { + if let Some(name) = name.as_str() { + cmd_name = Some(name.to_string()); + } + } if let Some(request) = item.get("request") { if let Some(request) = request.as_str() { // this means its a get request curl_cmd.set_url(request); curl_cmd.set_get_method(); } else if let Some(request) = request.as_object() { - if let Some(method) = request.get("method") { - if let Some(method) = method.as_str() { - match method { - "GET" => { - curl_cmd.set_get_method(); - } - "POST" => { - curl_cmd.set_post_method(); - } - "PUT" => { - curl_cmd.set_put_method(); - } - "DELETE" => { - curl_cmd.set_delete_method(); - } - "PATCH" => { - curl_cmd.set_patch_method(); - } - "HEAD" => { - curl_cmd.set_head_method(); - } - _ => { - curl_cmd.set_get_method(); + if let Some(desc) = request.get("description") { + if let Some(desc) = desc.as_str() { + description = Some(desc.to_string()); + } + } + if let Some(url) = request.get("url") { + if let Some(str_url) = url.as_str() { + curl_cmd.set_url(str_url); + } else if let Some(url) = url.as_object() { + if let Some(raw) = url.get("raw") { + if let Some(raw_str) = raw.as_str() { + curl_cmd.set_url(raw_str); } } } } + if let Some(method) = request.get("method") { + if let Some(method) = method.as_str() { + curl_cmd.set_method(Method::from_str(method).unwrap_or_default()); + } + } if let Some(headers) = request.get("header") { if let Some(headers) = headers.as_array() { headers.iter().for_each(|hdr| { @@ -143,37 +144,40 @@ impl From for Vec { } } } - } else if let Some(url) = request.get("url") { - if let Some(url) = url.as_object() { - if let Some(url) = url.get("raw") { - if let Some(url) = url.as_str() { - curl_cmd.set_url(url); - } - } - } - } - if let Some(cookie) = request.get("cookie") { - if let Some(cookie) = cookie.as_array() { - cookie.iter().for_each(|ck| { - if let Some(ck) = ck.as_object() { - if let Some(key) = ck.get("key") { - if let Some(key) = key.as_str() { - if let Some(value) = ck.get("value") { - if let Some(value) = value.as_str() { - curl_cmd.add_cookie(&format!("{}: {}", key, value)); + if let Some(cookie) = request.get("cookie") { + if let Some(cookie) = cookie.as_array() { + cookie.iter().for_each(|ck| { + if let Some(ck) = ck.as_object() { + if let Some(key) = ck.get("key") { + if let Some(key) = key.as_str() { + if let Some(value) = ck.get("value") { + if let Some(value) = value.as_str() { + curl_cmd + .add_cookie(&format!("{}: {}", key, value)); + } } } } } - } - }); + }); + } } } } + if cmd_name.is_none() { + cmd_name = Some(String::from(curl_cmd.get_url())); + } + let cmd = curl_cmd.get_command_string(); + let curl_json: String = serde_json::to_string(&curl_cmd).unwrap_or_default(); + saved_commands.push(SavedCommand::new( + &cmd, + cmd_name, + description, + &curl_json, + None, + )); }); - let cmd = curl_cmd.get_command_string(); - let curl_json: String = serde_json::to_string(&curl_cmd).unwrap_or_default(); - saved_commands.push(SavedCommand::new(&cmd, &curl_json, None)); + saved_commands } } @@ -182,4 +186,5 @@ impl From for Vec { pub struct Info { pub name: String, schema: String, + pub description: String, } diff --git a/src/display/inputopt.rs b/src/display/inputopt.rs index ba437c3..f5a116b 100644 --- a/src/display/inputopt.rs +++ b/src/display/inputopt.rs @@ -26,6 +26,9 @@ pub enum InputOpt { CaPath, CaCert, KeyLabel(i32), + CmdLabel(i32), + CmdDescription(i32), + CollectionDescription(i32), ImportCollection, RenameCollection(i32), RequestError(String), @@ -70,6 +73,9 @@ impl Display for InputOpt { InputOpt::Method(method) => write!(f, "| Method: {}", method), InputOpt::CookieJar => write!(f, "| Cookie Jar"), InputOpt::AlertMessage(msg) => write!(f, "| Alert: {}", msg), + InputOpt::CmdLabel(_) => write!(f, "| Command Label"), + InputOpt::CmdDescription(_) => write!(f, "| Command Description"), + InputOpt::CollectionDescription(_) => write!(f, "| Collection Description"), } } } diff --git a/src/display/menuopts.rs b/src/display/menuopts.rs index 3b60906..a18bef8 100644 --- a/src/display/menuopts.rs +++ b/src/display/menuopts.rs @@ -70,7 +70,6 @@ pub const CUTE_LOGO2: &str = " pub const DISPLAY_OPT_VERBOSE: &str = " Verbose"; pub const DISPLAY_OPT_COMMAND_SAVED: &str = " Request will be saved  "; pub const DISPLAY_OPT_HEADERS: &str = " Response headers included 󱈝 "; -pub const DISPLAY_OPT_PROGRESS_BAR: &str = " Enable Progress Bar 󰦖 "; pub const DISPLAY_OPT_FAIL_ON_ERROR: &str = " Fail on error  "; pub const DISPLAY_OPT_TOKEN_SAVED: &str = " Token will be saved  "; pub const DISPLAY_OPT_FOLLOW_REDIRECTS: &str = " Follow redirects 󱀀 "; @@ -107,10 +106,12 @@ pub const SAVE_AUTH_ERROR: &str = pub const VALID_COMMAND_ERROR: &str = "Error: Invalid command.\n You must add either a URL or Unix Socket to execute a command"; -pub const CMD_MENU_OPTIONS: [&str; 4] = [ +pub const CMD_MENU_OPTIONS: [&str; 6] = [ "Execute  ", + "Add a label 󰈮 ", + "Add a description 󰈮 ", "Delete  ", - "Copy to Clipboard 󰅎 ", + "Copy CLI command to clipboard 󰅎 ", "Cancel  ", ]; pub const KEY_MENU_OPTIONS: [&str; 4] = [ @@ -132,8 +133,9 @@ pub const MAIN_MENU_OPTIONS: [&str; 4] = [ "View or Import Postman Collections", "View Saved API keys 󱂛  ", ]; -pub const COLLECTION_ALERT_MENU_OPTS: [&str; 4] = [ +pub const COLLECTION_ALERT_MENU_OPTS: [&str; 5] = [ "View Requests in this collection", + "Add a description", "Rename this collection", "Delete this collection", "Cancel", @@ -189,7 +191,7 @@ pub const AUTHENTICATION_MENU_OPTIONS: [&str; 6] = [ "Ntlm", "SPNEGO", ]; -pub const MORE_FLAGS_MENU: [&str; 12] = [ +pub const MORE_FLAGS_MENU: [&str; 11] = [ "Follow Redirects 󱀀 ", "Specify Max redirects 󱀀 ", "Enable HTTP Proxy-Tunnel 󱠾 ", @@ -197,7 +199,6 @@ pub const MORE_FLAGS_MENU: [&str; 12] = [ "Specify Referrer 󰆽 ", "Specify SSL Certificate path 󰄤 ", "Request Certificate Info 󰄤 ", - "Add Progress Bar 󰦖 ", "Fail on Error  ", "Match wildcard 󰛄 ", "Specify User-Agent 󰖟 ", diff --git a/src/display/mod.rs b/src/display/mod.rs index aff07cd..c1f15f9 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -5,10 +5,9 @@ use self::menuopts::{ DISPLAY_OPT_COMMAND_SAVED, DISPLAY_OPT_CONTENT_HEADERS, DISPLAY_OPT_COOKIE, DISPLAY_OPT_COOKIE_JAR, DISPLAY_OPT_FAIL_ON_ERROR, DISPLAY_OPT_FOLLOW_REDIRECTS, DISPLAY_OPT_HEADERS, DISPLAY_OPT_MATCH_WILDCARD, DISPLAY_OPT_MAX_REDIRECTS, - DISPLAY_OPT_OUTFILE, DISPLAY_OPT_PROGRESS_BAR, DISPLAY_OPT_PROXY_TUNNEL, DISPLAY_OPT_REFERRER, - DISPLAY_OPT_TCP_KEEPALIVE, DISPLAY_OPT_TOKEN_SAVED, DISPLAY_OPT_UNIX_SOCKET, - DISPLAY_OPT_UNRESTRICTED_AUTH, DISPLAY_OPT_UPLOAD, DISPLAY_OPT_URL, DISPLAY_OPT_USERAGENT, - DISPLAY_OPT_VERBOSE, + DISPLAY_OPT_OUTFILE, DISPLAY_OPT_PROXY_TUNNEL, DISPLAY_OPT_REFERRER, DISPLAY_OPT_TCP_KEEPALIVE, + DISPLAY_OPT_TOKEN_SAVED, DISPLAY_OPT_UNIX_SOCKET, DISPLAY_OPT_UNRESTRICTED_AUTH, + DISPLAY_OPT_UPLOAD, DISPLAY_OPT_URL, DISPLAY_OPT_USERAGENT, DISPLAY_OPT_VERBOSE, }; use crate::request::curl::AuthKind; use std::fmt::{Display, Formatter}; @@ -62,7 +61,6 @@ pub enum AppOptions { CookiePath(String), EnableHeaders, ContentHeaders(HeaderKind), - ProgressBar, FailOnError, ProxyTunnel, CaPath(String), @@ -100,7 +98,6 @@ impl AppOptions { Self::FollowRedirects => "-L".to_string(), Self::UnixSocket(ref socket) => format!("--unix-socket {socket}"), Self::MatchWildcard => "-g".to_string(), - Self::ProgressBar => "--progress-bar".to_string(), Self::Auth(ref kind) => match kind { AuthKind::Basic(ref login) => { format!("-u {login}") @@ -131,7 +128,6 @@ impl AppOptions { self, Self::Verbose | Self::EnableHeaders - | Self::ProgressBar | Self::FailOnError | Self::ProxyTunnel | Self::CertInfo @@ -215,7 +211,6 @@ impl AppOptions { } AppOptions::NewCookie(cookie) => format!("{}{}", DISPLAY_OPT_COOKIE, cookie.clone()), AppOptions::EnableHeaders => DISPLAY_OPT_HEADERS.to_string(), - AppOptions::ProgressBar => String::from(DISPLAY_OPT_PROGRESS_BAR), AppOptions::FailOnError => String::from(DISPLAY_OPT_FAIL_ON_ERROR), AppOptions::ProxyTunnel => DISPLAY_OPT_PROXY_TUNNEL.to_string(), AppOptions::UserAgent(ua) => format!("{}{}", DISPLAY_OPT_USERAGENT, ua), diff --git a/src/request/curl.rs b/src/request/curl.rs index d14f7e5..67d89d1 100644 --- a/src/request/curl.rs +++ b/src/request/curl.rs @@ -42,15 +42,15 @@ pub struct Curl { auth: AuthKind, // The final cli command string cmd: String, - headers: Option>, + pub headers: Option>, url: String, - // Build this on the App struct and pass it here to store for serialization pub opts: Vec, resp: Option, upload_file: Option, outfile: Option, // Whether to save the (command, auth/key) to DB after execution save: (bool, bool), + ser: bool, } impl Default for CurlHandler { @@ -143,6 +143,7 @@ impl Clone for Curl { upload_file: self.upload_file.clone(), outfile: self.outfile.clone(), save: self.save, + ser: self.ser, } } } @@ -159,6 +160,7 @@ impl PartialEq for Curl { && self.upload_file == other.upload_file && self.outfile == other.outfile && self.save == other.save + && self.ser == other.ser } } @@ -218,6 +220,7 @@ impl Default for Curl { upload_file: None, outfile: None, save: (false, false), + ser: false, } } } @@ -225,6 +228,12 @@ impl Curl { pub fn new() -> Self { Self::default() } + pub fn new_serializing() -> Self { + Self { + ser: true, + ..Self::default() + } + } pub fn get_url(&self) -> &str { &self.url } @@ -280,6 +289,10 @@ impl Curl { } pub fn set_url(&mut self, url: &str) { + if self.ser { + // if we're serializing, we need to store the URL in the opts + self.opts.push(AppOptions::URL(url.to_string())); + } self.url = String::from(url.trim()); self.curl.url(url).unwrap(); } @@ -293,13 +306,22 @@ impl Curl { } pub fn set_cookie_path(&mut self, path: &str) { + if self.ser { + self.opts.push(AppOptions::CookieJar(path.to_string())); + } self.curl.cookie_file(path).unwrap(); } pub fn set_cookie_jar(&mut self, path: &str) { + if self.ser { + self.opts.push(AppOptions::CookieJar(path.to_string())); + } self.curl.cookie_jar(path).unwrap(); } pub fn reset_cookie_session(&mut self) { + if self.ser { + self.opts.push(AppOptions::NewCookieSession); + } self.curl.cookie_session(true).unwrap(); } @@ -389,6 +411,9 @@ impl Curl { } pub fn set_auth(&mut self, auth: AuthKind) { + if self.ser { + self.opts.push(AppOptions::Auth(auth.clone())); + } match auth { AuthKind::Basic(ref info) => self.set_basic_auth(info), AuthKind::Ntlm => self.set_ntlm_auth(), @@ -412,34 +437,51 @@ impl Curl { } pub fn set_cert_info(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::CertInfo); + } self.curl.certinfo(opt).unwrap(); } pub fn set_referrer(&mut self, referrer: &str) { + if self.ser { + self.opts.push(AppOptions::Referrer(String::from(referrer))); + } self.curl.referer(referrer).unwrap(); } pub fn set_proxy_tunnel(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::ProxyTunnel); + } self.curl.http_proxy_tunnel(opt).unwrap(); } pub fn set_verbose(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::Verbose); + } self.curl.verbose(opt).unwrap(); } pub fn set_fail_on_error(&mut self, fail: bool) { + if self.ser { + self.opts.push(AppOptions::FailOnError); + } self.curl.fail_on_error(fail).unwrap(); } pub fn set_unix_socket(&mut self, socket: &str) { + if self.ser { + self.opts.push(AppOptions::UnixSocket(String::from(socket))); + } self.curl.unix_socket(socket).unwrap(); } - pub fn enable_progress_bar(&mut self, on: bool) { - self.curl.progress(on).unwrap(); - } - pub fn set_content_header(&mut self, kind: &HeaderKind) { + if self.ser { + self.opts.push(AppOptions::ContentHeaders(kind.clone())); + } if let Some(ref mut headers) = self.headers { headers.push(kind.to_string()); } else { @@ -452,6 +494,9 @@ impl Curl { } pub fn add_headers(&mut self, headers: &str) { + if self.ser { + self.opts.push(AppOptions::Headers(headers.to_string())); + } if self.headers.is_some() { self.headers.as_mut().unwrap().push(headers.to_string()); } else { @@ -476,52 +521,82 @@ impl Curl { } } pub fn match_wildcard(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::MatchWildcard); + } self.curl.wildcard_match(opt).unwrap(); } pub fn set_unrestricted_auth(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::UnrestrictedAuth); + } self.curl.unrestricted_auth(opt).unwrap(); } pub fn set_user_agent(&mut self, ua: &str) { + if self.ser { + self.opts.push(AppOptions::UserAgent(ua.to_string())); + } self.curl.useragent(ua).unwrap(); } pub fn set_max_redirects(&mut self, redirects: usize) { + if self.ser { + self.opts.push(AppOptions::MaxRedirects(redirects)); + } self.curl .max_redirections(redirects as u32) .unwrap_or_default(); } pub fn set_ca_path(&mut self, ca_path: &str) { + if self.ser { + self.opts.push(AppOptions::CaPath(ca_path.to_string())); + } self.curl.cainfo(ca_path).unwrap_or_default(); } pub fn set_tcp_keepalive(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::TcpKeepAlive); + } self.curl.tcp_keepalive(opt).unwrap_or_default(); } pub fn set_request_body(&mut self, body: &str) { + if self.ser { + self.opts.push(AppOptions::RequestBody(body.to_string())); + } + self.opts.push(AppOptions::RequestBody(body.to_string())); self.curl .post_fields_copy(body.as_bytes()) .unwrap_or_default(); } pub fn set_follow_redirects(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::FollowRedirects); + } self.curl.follow_location(opt).unwrap_or_default(); } pub fn add_cookie(&mut self, cookie: &str) { + if self.ser { + self.opts.push(AppOptions::NewCookie(cookie.to_string())); + } self.curl.cookie(cookie).unwrap_or_default(); } pub fn set_upload_file(&mut self, file: &str) { + if self.ser { + self.opts.push(AppOptions::UploadFile(file.to_string())); + } self.upload_file = Some(file.to_string()); self.curl.upload(true).unwrap_or_default(); } pub fn write_output(&mut self) -> Result<(), std::io::Error> { - println!("{}", self.outfile.as_ref().unwrap().clone()); match self.outfile { Some(ref mut outfile) => { let mut file = match std::fs::File::create(outfile) { @@ -546,6 +621,9 @@ impl Curl { } } pub fn enable_response_headers(&mut self, opt: bool) { + if self.ser { + self.opts.push(AppOptions::EnableHeaders); + } self.curl.show_header(opt).unwrap_or_default(); } @@ -555,6 +633,7 @@ impl Curl { } pub fn easy_from_opts(&mut self) { + self.ser = true; self.build_command_string(); self.curl.url(&self.url).unwrap(); self.apply_method(); @@ -563,11 +642,18 @@ impl Curl { self.add_option(opt); } } + pub fn set_any_auth(&mut self) { + if self.ser { + self.opts.push(AppOptions::Auth(AuthKind::None)); + } let _ = self.curl.http_auth(&Auth::new()); } pub fn set_basic_auth(&mut self, login: &str) { + if self.ser { + self.opts.push(AppOptions::Auth(AuthKind::Basic(login.to_string()))); + } self.auth = AuthKind::Basic(String::from(login)); } @@ -577,14 +663,23 @@ impl Curl { } pub fn set_digest_auth(&mut self, login: &str) { + if self.ser { + self.opts.push(AppOptions::Auth(AuthKind::Digest(login.to_string()))); + } self.auth = AuthKind::Digest(String::from(login)); } pub fn set_aws_sigv4_auth(&mut self) { + if self.ser { + self.opts.push(AppOptions::Auth(AuthKind::AwsSigv4)); + } self.auth = AuthKind::AwsSigv4; } pub fn set_spnego_auth(&mut self) { + if self.ser { + self.opts.push(AppOptions::Auth(AuthKind::Spnego)); + } self.auth = AuthKind::Spnego; } @@ -627,6 +722,9 @@ impl Curl { } pub fn show_headers(&mut self) { + if self.ser { + self.opts.push(AppOptions::EnableHeaders); + } self.curl.show_header(true).unwrap(); } @@ -669,6 +767,7 @@ impl Curl { } pub fn url_encode(&mut self, data: &str) { - self.url = self.curl.url_encode(data.as_bytes()); + let encoded = self.curl.url_encode(data.as_bytes()); + self.opts.push(AppOptions::RequestBody(encoded)); } } diff --git a/src/request/mod.rs b/src/request/mod.rs index b9a4765..9327381 100644 --- a/src/request/mod.rs +++ b/src/request/mod.rs @@ -18,7 +18,6 @@ impl ExecuteOption for Curl { AppOptions::Outfile(ref file) => self.set_outfile(file), AppOptions::UploadFile(ref file) => self.set_upload_file(file), AppOptions::UnixSocket(ref file) => self.set_unix_socket(file), - AppOptions::ProgressBar => self.enable_progress_bar(true), AppOptions::FailOnError => self.set_fail_on_error(true), AppOptions::Verbose => self.set_verbose(true), AppOptions::Response(ref resp) => self.set_response(resp), @@ -51,7 +50,6 @@ impl ExecuteOption for Curl { AppOptions::Outfile(_) => self.set_outfile(""), AppOptions::UploadFile(_) => self.set_upload_file(""), AppOptions::UnixSocket(_) => self.set_unix_socket(""), - AppOptions::ProgressBar => self.enable_progress_bar(false), AppOptions::FailOnError => self.set_fail_on_error(false), AppOptions::Verbose => self.set_verbose(false), AppOptions::Response(_) => self.set_response(""), diff --git a/src/screens/collections.rs b/src/screens/collections.rs index a81e269..61117a6 100644 --- a/src/screens/collections.rs +++ b/src/screens/collections.rs @@ -137,12 +137,15 @@ pub fn handle_collection_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i id: Some(cmd), opt: None, }), + Some(1) => app.goto_screen(&Screen::SavedCollections(Some( + InputOpt::CollectionDescription(selected.get_id()), + ))), // Rename Collection - Some(1) => app.goto_screen(&Screen::SavedCollections(Some(InputOpt::RenameCollection( + Some(2) => app.goto_screen(&Screen::SavedCollections(Some(InputOpt::RenameCollection( selected.get_id(), )))), // delete collection - Some(2) => { + Some(3) => { if let Err(e) = app.db.as_ref().delete_collection(selected.get_id()) { app.goto_screen(&Screen::SavedCollections(Some(InputOpt::RequestError( format!("Error: {e}"), @@ -153,7 +156,7 @@ pub fn handle_collection_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i )))); } // cancel - Some(3) => { + Some(4) => { app.goto_screen(&Screen::ViewSavedCollections); } _ => {} diff --git a/src/screens/input/input_screen.rs b/src/screens/input/input_screen.rs index 676c02f..e828dd4 100644 --- a/src/screens/input/input_screen.rs +++ b/src/screens/input/input_screen.rs @@ -45,31 +45,25 @@ pub fn get_input_prompt(opt: InputOpt) -> Text<'static> { pub fn handle_default_input_screen(app: &mut App, frame: &mut Frame<'_>, opt: InputOpt) { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints(vec![Constraint::Percentage(15), Constraint::Percentage(85)]) + .constraints(vec![ + Constraint::Percentage(10), + Constraint::Percentage(10), + Constraint::Percentage(80), + ]) + .horizontal_margin(6) .split(frame.size()); - let input_textbox = chunks[0]; - let text_chunk = Block::default().borders(Borders::ALL).style( - Style::default() - .bg(app.config.get_bg_color()) - .fg(Color::LightBlue) - .add_modifier(tui::style::Modifier::BOLD), - ); - frame.render_widget(text_chunk, input_textbox); - let bottom_box = centered_rect(chunks[1], crate::screens::ScreenArea::Top); + let input_textbox = chunks[1]; + let bottom_box = centered_rect(chunks[2], crate::screens::ScreenArea::Top); let prompt = get_input_prompt(opt.clone()); frame.render_widget( - Paragraph::new(prompt).style( - Style::default() - .fg(Color::LightBlue) - .add_modifier(Modifier::BOLD), - ), + Paragraph::new(prompt).style(Style::default().add_modifier(Modifier::BOLD)), centered_rect(bottom_box, crate::screens::ScreenArea::Top), ); let top_box = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref()) .split(input_textbox); - let width = top_box[0].width.max(3) - 3; + let width = top_box[1].width.max(3) - 3; let scroll = app.input.visual_scroll(width as usize); match opt { InputOpt::URL => { @@ -161,8 +155,8 @@ pub fn handle_default_input_screen(app: &mut App, frame: &mut Frame<'_>, opt: In } let input = Paragraph::new(app.input.value()) .style(match app.input_mode { - InputMode::Normal => Style::default().fg(app.config.get_body_color()), - InputMode::Editing => Style::default().fg(Color::White), + InputMode::Normal => Style::default().fg(Color::Blue), + InputMode::Editing => Style::default().fg(Color::Yellow), }) .block(Block::default().borders(Borders::ALL).title("Input")); let (msg, style) = match app.input_mode { @@ -265,6 +259,24 @@ pub fn parse_input(message: String, opt: InputOpt, app: &mut App) { InputOpt::NewCookie => { app.goto_screen(&Screen::RequestMenu(Some(InputOpt::CookieValue(message)))); } + InputOpt::CmdDescription(id) => { + let coll_id = app + .db + .set_command_description(id, &message) + .unwrap_or_default(); + app.goto_screen(&Screen::SavedCommands { + id: coll_id, + opt: Some(InputOpt::RequestError(String::from("Description Updated"))), + }); + } + InputOpt::CollectionDescription(id) => { + app.db + .set_collection_description(id, &message) + .unwrap_or_default(); + app.goto_screen(&Screen::SavedCollections(Some(InputOpt::RequestError( + String::from("Description Updated"), + )))); + } InputOpt::CookieValue(ref name) => { let cookie = format!("{}={};", name, message); app.goto_screen(&Screen::RequestMenu(Some(InputOpt::CookieExpires(cookie)))); @@ -340,9 +352,13 @@ pub fn parse_input(message: String, opt: InputOpt, app: &mut App) { } InputOpt::ImportCollection => { if let Err(e) = app.import_postman_collection(&message) { - app.goto_screen(&Screen::Error(e.to_string())); + app.goto_screen(&Screen::SavedCollections(Some(InputOpt::AlertMessage( + e.to_string(), + )))); } else { - app.goto_screen(&Screen::Success); + app.goto_screen(&Screen::SavedCollections(Some(InputOpt::AlertMessage( + String::from("Collection Imported"), + )))); } } InputOpt::KeyLabel(id) => match app.db.set_key_label(id, &message) { @@ -354,6 +370,16 @@ pub fn parse_input(message: String, opt: InputOpt, app: &mut App) { e ))))), }, + InputOpt::CmdLabel(id) => match app.db.set_command_label(id, &message) { + Ok(collection_id) => app.goto_screen(&Screen::SavedCommands { + id: collection_id, + opt: Some(InputOpt::AlertMessage(String::from("Label Updated"))), + }), + Err(e) => app.goto_screen(&Screen::SavedCommands { + id: None, + opt: Some(InputOpt::RequestError(format!("Error: {}", e))), + }), + }, InputOpt::Auth(auth) => { parse_auth(auth, app, &message); } diff --git a/src/screens/more_flags.rs b/src/screens/more_flags.rs index 7c1ed0f..a56503a 100644 --- a/src/screens/more_flags.rs +++ b/src/screens/more_flags.rs @@ -24,16 +24,14 @@ pub fn handle_more_flags_screen(app: &mut App, frame: &mut Frame<'_>) { Some(6) => app.goto_screen(&Screen::RequestMenu(Some(InputOpt::CaPath))), // Request certificate info Some(7) => app.add_app_option(AppOptions::CertInfo), - // add progress bar - Some(8) => app.add_app_option(AppOptions::ProgressBar), // fail on error - Some(9) => app.add_app_option(AppOptions::FailOnError), + Some(8) => app.add_app_option(AppOptions::FailOnError), // wildcard match - Some(10) => app.add_app_option(AppOptions::MatchWildcard), + Some(9) => app.add_app_option(AppOptions::MatchWildcard), // user agent - Some(11) => app.goto_screen(&Screen::RequestMenu(Some(InputOpt::UserAgent))), + Some(10) => app.goto_screen(&Screen::RequestMenu(Some(InputOpt::UserAgent))), // enable tcp keepalive - Some(12) => app.add_app_option(AppOptions::TcpKeepAlive), + Some(11) => app.add_app_option(AppOptions::TcpKeepAlive), _ => {} } } diff --git a/src/screens/render.rs b/src/screens/render.rs index ecb23ea..4dbfcd5 100644 --- a/src/screens/render.rs +++ b/src/screens/render.rs @@ -175,8 +175,8 @@ pub fn handle_screen(app: &mut App, frame: &mut Frame<'_>, screen: Screen) { Screen::SavedKeys(opt) => { saved_keys::handle_saved_keys_screen(app, frame, opt); } - Screen::CmdMenu(cmd) => { - saved_commands::handle_alert_menu(app, frame, cmd); + Screen::CmdMenu { id, opt } => { + saved_commands::handle_alert_menu(app, frame, id, opt); } Screen::CookieOptions => { cookies::handle_cookies_menu(app, frame); diff --git a/src/screens/saved_commands.rs b/src/screens/saved_commands.rs index 6201008..cf4c94e 100644 --- a/src/screens/saved_commands.rs +++ b/src/screens/saved_commands.rs @@ -1,3 +1,4 @@ +use super::input::input_screen::handle_default_input_screen; use super::render::render_header_paragraph; use super::{centered_rect, error_alert_box, Screen, ScreenArea}; use crate::app::App; @@ -5,7 +6,9 @@ use crate::display::inputopt::InputOpt; use crate::display::menuopts::{CMD_MENU_OPTIONS, SAVED_COMMANDS_PARAGRAPH, SAVED_COMMANDS_TITLE}; use tui::prelude::{Constraint, Direction, Layout, Margin}; use tui::style::{Color, Modifier, Style}; -use tui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph}; +use tui::text::{Line, Span}; + +use tui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}; use tui::Frame; pub fn handle_saved_commands_screen( @@ -20,9 +23,12 @@ pub fn handle_saved_commands_screen( .iter() .map(|x| { format!( - "Request: {} Collection: {:?}", - x.get_command(), - x.get_collection_id() + "Request: {} | Collection: {:?}", + x.label.clone().unwrap_or(String::from("No label")), + match x.collection_name.clone() { + Some(name) => name, + None => "No Collection".to_string(), + } ) }) .collect::>(), @@ -48,14 +54,23 @@ pub fn handle_saved_commands_screen( if let Some(selected) = app.selected { let cmd = commands.get(selected); if let Some(cmd) = cmd { - app.goto_screen(&Screen::CmdMenu(cmd.get_id())); + app.goto_screen(&Screen::CmdMenu { + id: cmd.get_id(), + opt: None, + }); } else { - app.goto_screen(&Screen::Error("No commands found".to_string())); + app.goto_screen(&Screen::SavedCommands { + id: None, + opt: Some(InputOpt::RequestError("No commands found".to_string())), + }); } } } -pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32) { +pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32, opt: Option) { + if let Some(opt) = opt { + handle_default_input_screen(app, frame, opt); + } let layout = Layout::default() .direction(Direction::Vertical) .constraints( @@ -68,15 +83,9 @@ pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32) { ) .horizontal_margin(5) .split(frame.size()); - // Render the alert box - let alert_box = layout[1]; - let alert_text_chunk = Block::default() - .borders(Borders::ALL) - .style(Style::default().bg(Color::Black).fg(Color::LightGreen)) - .title("Alert"); let options_box = layout[1].inner(&Margin { vertical: 1, - horizontal: 1, + horizontal: 25, }); let mut list_state = ListState::with_selected(ListState::default(), Some(app.cursor)); app.state = Some(list_state.clone()); @@ -88,7 +97,7 @@ pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32) { .block(Block::default()) .highlight_style( Style::default() - .bg(Color::White) + .bg(Color::Blue) .fg(Color::Black) .add_modifier(Modifier::BOLD), ) @@ -96,22 +105,81 @@ pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32) { let cmd_str = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(alert_box)[1]; + .split(layout[1])[1]; if let Ok(command) = app.db.as_ref().get_command_by_id(cmd) { - let paragraph = Paragraph::new(format!("{:?}", command)) - .block(Block::default().borders(Borders::ALL).title("Command")) - .alignment(tui::layout::Alignment::Center); - frame.render_widget(paragraph, cmd_str); - frame.render_widget(alert_text_chunk, alert_box); + let collection_name = match app + .db + .as_ref() + .get_collection_by_id(command.collection_id.unwrap_or(0)) + { + Ok(collection) => collection.name, + Err(_) => "No Collection".to_string(), + }; + let alert_text = vec![ + Line::raw("\n"), + Line::default().spans(vec![ + Span::styled("Label: ", Style::default().fg(Color::LightGreen)), + Span::styled( + command.label.clone().unwrap_or("None".to_string()), + Style::default().fg(Color::White), + ), + ]), + Line::default().spans(vec![ + Span::styled("Description: ", Style::default().fg(Color::LightGreen)), + Span::styled( + command.description.clone().unwrap_or("None".to_string()), + Style::default().fg(Color::White), + ), + ]), + Line::default().spans(vec![ + Span::styled("Collection: ", Style::default().fg(Color::LightGreen)), + Span::styled(collection_name, Style::default().fg(Color::White)), + ]), + Line::default().spans(vec![ + Span::styled("ID: ", Style::default().fg(Color::LightGreen)), + Span::styled(command.id.to_string(), Style::default().fg(Color::White)), + ]), + ]; + let alert_text = List::new(alert_text) + .block( + Block::default() + .borders(Borders::ALL) + .title("Command Details"), + ) + .style(Style::default().fg(Color::Blue)) + .highlight_style(Style::default().fg(Color::LightGreen)); frame.render_stateful_widget(list, options_box, &mut list_state); + frame.render_widget(alert_text, layout[0]); + let header = Block::default().borders(Borders::ALL).title("* Request *"); + frame.render_widget(header, layout[0]); + let paragraph = Paragraph::new(command.get_command()) + .block(Block::default().borders(Borders::ALL).title("* Command *")) + .alignment(tui::layout::Alignment::Center) + .centered() + .wrap(Wrap::default()); + frame.render_widget(paragraph, cmd_str); match app.selected { // execute saved command Some(0) => { app.execute_saved_command(command.get_curl_json()); app.goto_screen(&Screen::Response(app.response.clone().unwrap())); } - // delete item + // add a label Some(1) => { + app.goto_screen(&Screen::CmdMenu { + id: cmd, + opt: Some(InputOpt::CmdLabel(cmd)), + }); + } + // add a description + Some(2) => { + app.goto_screen(&Screen::CmdMenu { + id: cmd, + opt: Some(InputOpt::CmdDescription(cmd)), + }); + } + // delete item + Some(3) => { if let Err(e) = app.delete_item(command.get_id()) { app.goto_screen(&Screen::SavedCommands { id: None, @@ -127,7 +195,7 @@ pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32) { } } // copy to clipboard - Some(2) => { + Some(4) => { if let Err(e) = app.copy_to_clipboard(command.get_command()) { app.goto_screen(&Screen::Error(e.to_string())); } @@ -139,7 +207,7 @@ pub fn handle_alert_menu(app: &mut App, frame: &mut Frame<'_>, cmd: i32) { }); } // cancel - Some(3) => { + Some(5) => { app.goto_screen(&Screen::SavedCommands { id: None, opt: None, diff --git a/src/screens/screen.rs b/src/screens/screen.rs index 7aef4d7..2665487 100644 --- a/src/screens/screen.rs +++ b/src/screens/screen.rs @@ -33,7 +33,10 @@ pub enum Screen { ViewBody, MoreFlags, Headers, - CmdMenu(i32), + CmdMenu { + id: i32, + opt: Option, + }, KeysMenu(usize), RequestBodyInput, CookieOptions, @@ -47,6 +50,7 @@ impl Screen { Screen::SavedKeys(opt) => opt.is_some(), Screen::RequestBodyInput => true, Screen::SavedCollections(opt) => opt.is_some(), + Screen::CmdMenu { opt, .. } => opt.is_some(), _ => false, } } @@ -69,7 +73,7 @@ impl Display for Screen { Screen::ViewBody => "ViewBody", Screen::MoreFlags => "MoreFlags", Screen::Headers => "Headers", - Screen::CmdMenu(_) => "CmdMenu", + Screen::CmdMenu { .. } => "CmdMenu", Screen::KeysMenu(_) => "KeysMenu", Screen::RequestBodyInput => "RequestBodyInput", Screen::SavedCollections(_) => "Saved Collections", @@ -165,7 +169,7 @@ impl<'a> Screen { Screen::RequestBodyInput => { vec![ListItem::new("Request Body Input").style(Style::default().fg(Color::Green))] } - Screen::CmdMenu(_) => CMD_MENU_OPTIONS + Screen::CmdMenu { .. } => CMD_MENU_OPTIONS .iter() .map(|i| ListItem::new(format!("{i}{}", NEWLINE))) .collect(), diff --git a/test_collection.json b/test_collection.json new file mode 100644 index 0000000..f1c3c6b --- /dev/null +++ b/test_collection.json @@ -0,0 +1,40 @@ +{ + "info": { + "name": "Test Collection", + "version": "v2.1.0", + "description": "This is a demo collection.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/" + }, + "item": [ + { + "id": "my-first-item", + "name": "My First Item", + "description": "This is an Item that contains a single HTTP GET request. It doesn't really do much yet!", + "request": { + "description": "This is a sample POST request", + "url": "https://echo.getpostman.com/post", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Host", + "value": "echo.getpostman.com" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "my-body-variable", + "value": "Something Awesome!" + } + ] + } + }, + "response": [] + } + ] +}