diff --git a/Cargo.lock b/Cargo.lock index 41bf0f8..d7f944a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -311,7 +311,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.9.3", - "syn", + "syn 1.0.98", ] [[package]] @@ -322,7 +322,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -345,7 +345,7 @@ dependencies = [ "derive_builder_core", "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -357,7 +357,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -555,6 +555,7 @@ dependencies = [ "serde", "serde_yaml", "skim", + "thiserror", ] [[package]] @@ -700,7 +701,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.98", "version_check", ] @@ -723,18 +724,18 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.42" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -865,7 +866,7 @@ checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -975,7 +976,7 @@ dependencies = [ "quote", "serde", "serde_derive", - "syn", + "syn 1.0.98", ] [[package]] @@ -991,7 +992,7 @@ dependencies = [ "serde_derive", "serde_json", "sha1", - "syn", + "syn 1.0.98", ] [[package]] @@ -1029,6 +1030,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "term" version = "0.7.0" @@ -1072,22 +1084,22 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1145,7 +1157,7 @@ dependencies = [ "proc-macro2", "quote", "standback", - "syn", + "syn 1.0.98", ] [[package]] @@ -1270,7 +1282,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.98", "wasm-bindgen-shared", ] @@ -1292,7 +1304,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index f001cc8..c7ab169 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ serde_yaml = "0.9.3" serde = { version = "1.0.141", features = ["derive"] } lazy_static = "1.4.0" skim = "0.9.4" +thiserror = "1.0.56" [dev-dependencies] assert_cmd = "2.0" diff --git a/src/commands.rs b/src/commands.rs index dd34a64..8d961f2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,8 +1,6 @@ -use crate::model::KubeConfig; +use crate::{error::SetContextError, model::KubeConfig}; use crate::config; -use core::fmt; -use std::process; use std::{ io::Cursor, process::{Command, Stdio}, @@ -66,25 +64,21 @@ pub fn get_current_context() -> String { String::from_utf8(output.stdout).unwrap().trim().to_owned() } -pub fn selectable_list(input: Vec) -> String { +/// Prompts the user to select an item from a list. +/// Returns the selected item or `None` if no item was selected +pub fn selectable_list(input: Vec) -> Option { let input: Vec = input.into_iter().rev().collect(); let options = SkimOptionsBuilder::default().multi(false).build().unwrap(); let item_reader = SkimItemReader::default(); let items = item_reader.of_bufread(Cursor::new(input.join("\n"))); - let selected_items = Skim::run_with(&options, Some(items)) - .map(|out| match out.final_key { - Key::Enter => out.selected_items, - _ => Vec::new(), + Skim::run_with(&options, Some(items)) + .and_then(|out| match out.final_key { + Key::Enter => Some(out.selected_items), + _ => None, }) - .unwrap_or_default(); - - if selected_items.is_empty() { - eprintln!("No item selected"); - process::exit(1); - } - - selected_items[0].output().to_string() + .filter(|selected_items| !selected_items.is_empty()) + .map(|selected_items| selected_items[0].output().to_string()) } pub fn set_namespace(ctx: &str, selection: &str, temp_dir: &str, config: &KubeConfig) { @@ -97,21 +91,6 @@ pub fn set_context(ctx: &str, temp_dir: &str, config: &KubeConfig) -> Result<(), config::write(choice, None, temp_dir); Ok(()) } else { - Err(SetContextError::ContextNotFound{ctx: ctx.to_owned()}) - } -} - -#[derive(Debug, Clone)] -pub enum SetContextError { - ContextNotFound { - ctx : String - }, -} - -impl fmt::Display for SetContextError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SetContextError::ContextNotFound{ctx} => write!(f, "no context exists with the name: \"{}\"", ctx), - } + Err(SetContextError::KubeContextNotFound{ctx: ctx.to_owned()}) } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..53c430d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("failed to set context: {0}")] + SetContext(#[source] SetContextError), + #[error("no item selected when prompted to select {prompt}")] + NoItemSelected { + prompt: &'static str + }, +} + +#[derive(Error, Debug)] +pub enum SetContextError { + #[error("no context exists with the name {ctx}")] + KubeContextNotFound { + ctx: String + }, +} diff --git a/src/main.rs b/src/main.rs index 4b76cf0..a516cc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,13 @@ mod commands; mod config; mod model; mod modes; +mod error; +use crate::error::Error; use clap::Parser; use std::env; use std::io; +use std::process; #[macro_use] extern crate lazy_static; @@ -68,14 +71,21 @@ enum Mode { } impl Mode { - fn invoke(&self) { + fn invoke(&self) -> Result <(), Error> { + let args = Cli::parse(); match self { - Mode::Namespace => modes::namespace(Cli::parse()), - Mode::Context => modes::context(Cli::parse()), - Mode::DefaultContext => modes::default_context(Cli::parse()), - Mode::DefaultNamespace => modes::default_namespace(Cli::parse()), - Mode::CompletionContext => modes::completion_context(Cli::parse()), - Mode::CompletionNamespace => modes::completion_namespace(Cli::parse()), + Mode::Namespace => modes::namespace(args), + Mode::Context => modes::context(args), + Mode::DefaultContext => modes::default_context(args), + Mode::DefaultNamespace => modes::default_namespace(args), + Mode::CompletionContext => { + modes::completion_context(args); + Ok(()) + }, + Mode::CompletionNamespace => { + modes::completion_namespace(args); + Ok(()) + } } } } @@ -83,7 +93,10 @@ impl Mode { fn main() -> Result<(), io::Error> { let args = Cli::parse(); - Mode::invoke(&args.mode); + if let Err(err) = Mode::invoke(&args.mode) { + eprintln!("error: {}", err); + process::exit(1); + } Ok(()) } diff --git a/src/modes.rs b/src/modes.rs index c36eb5d..eca8aaf 100644 --- a/src/modes.rs +++ b/src/modes.rs @@ -1,81 +1,73 @@ -use std::process; +use crate::{commands::{self}, config, error::Error, Cli, DEST, KUBECONFIG}; -use crate::{commands::{self}, config, Cli, DEST, KUBECONFIG}; - -fn selection(value: Option, callback: fn() -> String) -> String { - match value { - None => callback(), - Some(x) => x.trim().to_string(), - } -} - -pub fn default_context(args: Cli) { +pub fn default_context(args: Cli) -> Result<(), Error> { let config = config::get(); if args.current { println!("{}", config.current_context); - return; + return Ok(()); } let ctx = match args.value { None => { - let mut options = Vec::new(); - for context in &config.contexts { - options.push(context.name.to_string()); - } + let options : Vec = config.contexts.iter() + .map(|context| context.name.to_string()) + .collect(); commands::selectable_list(options) + .ok_or(Error::NoItemSelected{prompt: "context" })? } Some(x) => x.trim().to_string(), }; commands::set_default_context(&ctx); - match commands::set_context(&ctx, &DEST, &config) { - Ok(()) => println!("{}", KUBECONFIG.as_str()), - Err(e) => { - eprintln!("error: {}", e); - process::exit(1); - } + + let set_context_result = commands::set_context(&ctx, &DEST, &config) + .map_err(Error::SetContext); + + if set_context_result.is_ok() { + println!("{}", KUBECONFIG.as_str()); } + + set_context_result } -pub fn context(args: Cli) { +pub fn context(args: Cli) -> Result<(), Error> { if args.current { let config = config::get_current_session(); println!("{}", config.current_context); - return; + return Ok(()); } let config = config::get(); let ctx = match args.value { None => { - let mut options = Vec::new(); - for context in &config.contexts { - options.push(context.name.to_string()); - } + let options : Vec = config.contexts.iter() + .map(|context| context.name.to_string()) + .collect(); commands::selectable_list(options) + .ok_or(Error::NoItemSelected{prompt: "context"})? } Some(x) => x.trim().to_string(), }; - match commands::set_context(&ctx, &DEST, &config) { - Ok(()) => { - println!( - "{}/{}:{}", - &DEST.as_str(), - str::replace(&ctx, ":", "_"), - *KUBECONFIG - ); - }, - Err(e) => { - eprintln!("error: {}", e); - process::exit(1); - } + let set_context_result = commands::set_context(&ctx, &DEST, &config) + .map_err(Error::SetContext); + + if set_context_result.is_ok() { + println!( + "{}/{}:{}", + &DEST.as_str(), + str::replace(&ctx, ":", "_"), + *KUBECONFIG + ); } + + set_context_result } -pub fn namespace(args: Cli) { +pub fn namespace(args: Cli) -> Result<(), Error> { let config = config::get_current_session(); if args.current { let ctx = config @@ -93,13 +85,17 @@ pub fn namespace(args: Cli) { } None => println!("default"), } - return; + return Ok(()); } - let ns = selection(args.value, || -> String { - let namespaces = commands::get_namespaces(); - commands::selectable_list(namespaces) - }); + let ns = match args.value { + None => { + let namespaces : Vec = commands::get_namespaces(); + commands::selectable_list(namespaces) + .ok_or(Error::NoItemSelected{prompt: "namespace"})? + } + Some(x) => x.trim().to_string(), + }; commands::set_namespace(&config.current_context, &ns, &DEST, &config); @@ -109,9 +105,10 @@ pub fn namespace(args: Cli) { str::replace(&config.current_context, ":", "_"), *KUBECONFIG ); + Ok(()) } -pub fn default_namespace(args: Cli) { +pub fn default_namespace(args: Cli) -> Result<(), Error> { let config = config::get(); let ctx = commands::get_current_context(); @@ -132,16 +129,21 @@ pub fn default_namespace(args: Cli) { None => println!("default"), } - return; + return Ok(()); } - let ns = selection(args.value, || -> String { - let namespaces = commands::get_namespaces(); - commands::selectable_list(namespaces) - }); + let ns = match args.value { + None => { + let namespaces : Vec = commands::get_namespaces(); + commands::selectable_list(namespaces) + .ok_or(Error::NoItemSelected{prompt: "namespace"})? + } + Some(x) => x.trim().to_string(), + }; commands::set_default_namespace(&ns, &ctx); commands::set_namespace(&ctx, &ns, &DEST, &config); + Ok(()) } pub fn completion_context(args: Cli) {