From 7278b8fc533eac897fb6e8601ded125d5ae014d4 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Wed, 8 Jun 2022 13:52:10 +1000 Subject: [PATCH] chore: Upgrade clap to 3.0.x --- Cargo.lock | 70 ++++++++------ Cargo.toml | 2 +- src/main.rs | 271 +++++++++++++++++++++++++--------------------------- src/test.rs | 89 +++++++++++++++-- 4 files changed, 254 insertions(+), 178 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcf166e..5102638 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,17 +201,26 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ - "ansi_term", "atty", "bitflags", - "strsim 0.8.0", + "clap_lex", + "indexmap", + "strsim", + "termcolor", "textwrap", - "unicode-width", - "vec_map", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -314,7 +323,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", + "strsim", "syn", ] @@ -1027,6 +1036,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + [[package]] name = "output_vt100" version = "0.1.3" @@ -1827,12 +1842,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -1890,14 +1899,20 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ - "unicode-width", + "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "thiserror" version = "1.0.31" @@ -2268,12 +2283,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - [[package]] name = "untrusted" version = "0.7.1" @@ -2319,12 +2328,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -2475,6 +2478,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7f61522..992c812 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ exclude = [ ] [dependencies] -clap = "2.34.0" +clap = { version = "3.1.18", features = ["env"] } serde = "1.0.137" serde_json = "1.0.81" pact_matching = "0.12.8" diff --git a/src/main.rs b/src/main.rs index b45ae34..b7a3c08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,8 +68,7 @@ use std::process::ExitCode; use std::str::FromStr; use base64::encode; -use clap::{App, AppSettings, Arg, ArgMatches, ErrorKind}; -use clap::crate_version; +use clap::{Command, Arg, ArgMatches, ErrorKind}; use futures::stream::*; use maplit::*; use pact_models::pact::{load_pact_from_json, read_pact}; @@ -140,23 +139,24 @@ impl From for PactError { #[tokio::main] async fn main() -> Result<(), ExitCode> { - match handle_command_args().await { + let args: Vec = env::args().collect(); + match handle_command_args(args).await { Ok(_) => Ok(()), Err(err) => Err(ExitCode::from(err)) } } fn print_version() { - println!("\npact stub server version : v{}", crate_version!()); + println!("pact stub server version : v{}", env!("CARGO_PKG_VERSION")); println!("pact specification version: v{}", PactSpecification::V4.version_str()); } -fn integer_value(v: String) -> Result<(), String> { +fn integer_value(v: &str) -> Result<(), String> { v.parse::().map(|_| ()).map_err(|e| format!("'{}' is not a valid port value: {}", v, e) ) } -fn regex_value(v: String) -> Result<(), String> { - Regex::new(v.as_str()).map(|_| ()).map_err(|e| format!("'{}' is not a valid regular expression: {}", v, e) ) +fn regex_value(v: &str) -> Result<(), String> { + Regex::new(v).map(|_| ()).map_err(|e| format!("'{}' is not a valid regular expression: {}", v, e) ) } /// Source for loading pacts @@ -298,201 +298,192 @@ async fn load_pacts( }).flatten().collect().await } -async fn handle_command_args() -> Result<(), u8> { - let args: Vec = env::args().collect(); +async fn handle_command_args(args: Vec) -> Result<(), u8> { let program = args[0].clone(); + let version = format!("v{}", env!("CARGO_PKG_VERSION")); + let app = build_args(program.as_str(), version.as_str()); + match app.try_get_matches_from(args) { + Ok(ref matches) => { + let level = matches.value_of("loglevel").unwrap_or("info"); + setup_logger(level); + let sources = pact_source(matches); + + let pacts = load_pacts(sources, matches.is_present("insecure-tls"), + matches.value_of("ext")).await; + if pacts.iter().any(|p| p.is_err()) { + error!("There were errors loading the pact files."); + for error in pacts.iter() + .filter(|p| p.is_err()) + .map(|e| match e { + Err(err) => err.clone(), + _ => panic!("Internal Code Error - was expecting an error but was not") + }) { + error!(" - {}", error); + } + Err(3) + } else { + let port = matches.value_of("port").unwrap_or("0").parse::().unwrap(); + let provider_state = matches.value_of("provider-state") + .map(|filter| Regex::new(filter).unwrap()); + let provider_state_header_name = matches.value_of("provider-state-header-name") + .map(String::from); + let empty_provider_states = matches.is_present("empty-provider-state"); + let pacts = pacts.iter() + .map(|result| { + // Currently, as_v4_pact won't fail as it upgrades older formats to V4, so is safe to unwrap + result.as_ref().unwrap().as_v4_pact().unwrap() + }) + .collect(); + let auto_cors = matches.is_present("cors"); + let referer = matches.is_present("cors-referer"); + let server_handler = ServerHandler::new( + pacts, + auto_cors, + referer, + provider_state, + provider_state_header_name, + empty_provider_states); + tokio::task::spawn_blocking(move || { + server_handler.start_server(port) + }).await.unwrap() + } + }, + Err(ref err) => { + match err.kind() { + ErrorKind::DisplayHelp => { + println!("{}", err); + Ok(()) + }, + ErrorKind::DisplayVersion => { + print_version(); + println!(); + Ok(()) + }, + _ => err.exit() + } + } + } +} - let version = format!("v{}", crate_version!()); - let app = App::new(program) - .version(version.as_str()) +fn build_args<'a>(program: &'a str, version: &'a str) -> Command<'a> { + Command::new(program) + .version(version) .about("Pact Stub Server") - .version_short("v") - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::ColoredHelp) - .arg(Arg::with_name("loglevel") - .short("l") + .arg_required_else_help(true) + .mut_arg("version", |a| a.short('v')) + .arg(Arg::new("loglevel") + .short('l') .long("loglevel") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .possible_values(&["error", "warn", "info", "debug", "trace", "none"]) .help("Log level (defaults to info)")) - .arg(Arg::with_name("file") - .short("f") + .arg(Arg::new("file") + .short('f') .long("file") - .required_unless_one(&["dir", "url", "broker-url"]) + .required_unless_present_any(&["dir", "url", "broker-url"]) .takes_value(true) - .use_delimiter(false) - .multiple(true) + .use_value_delimiter(false) + .multiple_occurrences(true) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .help("Pact file to load (can be repeated)")) - .arg(Arg::with_name("dir") - .short("d") + .arg(Arg::new("dir") + .short('d') .long("dir") - .required_unless_one(&["file", "url", "broker-url"]) + .required_unless_present_any(&["file", "url", "broker-url"]) .takes_value(true) - .use_delimiter(false) - .multiple(true) + .use_value_delimiter(false) + .multiple_occurrences(true) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .help("Directory of pact files to load (can be repeated)")) - .arg(Arg::with_name("ext") - .short("e") + .arg(Arg::new("ext") + .short('e') .long("extension") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .requires("dir") .help("File extension to use when loading from a directory (default is json)")) - .arg(Arg::with_name("url") - .short("u") + .arg(Arg::new("url") + .short('u') .long("url") - .required_unless_one(&["file", "dir", "broker-url"]) + .required_unless_present_any(&["file", "dir", "broker-url"]) .takes_value(true) - .use_delimiter(false) - .multiple(true) + .use_value_delimiter(false) + .multiple_occurrences(true) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .help("URL of pact file to fetch (can be repeated)")) - .arg(Arg::with_name("broker-url") - .short("b") + .arg(Arg::new("broker-url") + .short('b') .long("broker-url") .env("PACT_BROKER_BASE_URL") - .required_unless_one(&["file", "dir", "url"]) + .required_unless_present_any(&["file", "dir", "url"]) .takes_value(true) - .use_delimiter(false) - .multiple(false) + .use_value_delimiter(false) + .multiple_occurrences(false) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .help("URL of the pact broker to fetch pacts from")) - .arg(Arg::with_name("user") + .arg(Arg::new("user") .long("user") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .conflicts_with("token") .help("User and password to use when fetching pacts from URLS or Pact Broker in user:password form")) - .arg(Arg::with_name("token") - .short("t") + .arg(Arg::new("token") + .short('t') .long("token") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .conflicts_with("user") .help("Bearer token to use when fetching pacts from URLS or Pact Broker")) - .arg(Arg::with_name("port") - .short("p") + .arg(Arg::new("port") + .short('p') .long("port") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .help("Port to run on (defaults to random port assigned by the OS)") .validator(integer_value)) - .arg(Arg::with_name("cors") - .short("o") + .arg(Arg::new("cors") + .short('o') .long("cors") - .takes_value(false) - .use_delimiter(false) .help("Automatically respond to OPTIONS requests and return default CORS headers")) - .arg(Arg::with_name("cors-referer") + .arg(Arg::new("cors-referer") .long("cors-referer") - .takes_value(false) - .use_delimiter(false) .requires("cors") .help("Set the CORS Access-Control-Allow-Origin header to the Referer")) - .arg(Arg::with_name("insecure-tls") + .arg(Arg::new("insecure-tls") .long("insecure-tls") - .takes_value(false) - .use_delimiter(false) .help("Disables TLS certificate validation")) - .arg(Arg::with_name("provider-state") - .short("s") + .arg(Arg::new("provider-state") + .short('s') .long("provider-state") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .validator(regex_value) .help("Provider state regular expression to filter the responses by")) - .arg(Arg::with_name("provider-state-header-name") + .arg(Arg::new("provider-state-header-name") .long("provider-state-header-name") .takes_value(true) - .use_delimiter(false) + .use_value_delimiter(false) .number_of_values(1) - .empty_values(false) + .forbid_empty_values(true) .help("Name of the header parameter containing the provider state to be used in case \ multiple matching interactions are found")) - .arg(Arg::with_name("empty-provider-state") + .arg(Arg::new("empty-provider-state") .long("empty-provider-state") - .takes_value(false) - .use_delimiter(false) .requires("provider-state") - .help("Include empty provider states when filtering with --provider-state")); - - let matches = app.get_matches_safe(); - match matches { - Ok(ref matches) => { - let level = matches.value_of("loglevel").unwrap_or("info"); - setup_logger(level); - let sources = pact_source(matches); - - let pacts = load_pacts(sources, matches.is_present("insecure-tls"), - matches.value_of("ext")).await; - if pacts.iter().any(|p| p.is_err()) { - error!("There were errors loading the pact files."); - for error in pacts.iter() - .filter(|p| p.is_err()) - .map(|e| match e { - Err(err) => err.clone(), - _ => panic!("Internal Code Error - was expecting an error but was not") - }) { - error!(" - {}", error); - } - Err(3) - } else { - let port = matches.value_of("port").unwrap_or("0").parse::().unwrap(); - let provider_state = matches.value_of("provider-state") - .map(|filter| Regex::new(filter).unwrap()); - let provider_state_header_name = matches.value_of("provider-state-header-name") - .map(String::from); - let empty_provider_states = matches.is_present("empty-provider-state"); - let pacts = pacts.iter() - .map(|result| { - // Currently, as_v4_pact won't fail as it upgrades older formats to V4, so is safe to unwrap - result.as_ref().unwrap().as_v4_pact().unwrap() - }) - .collect(); - let auto_cors = matches.is_present("cors"); - let referer = matches.is_present("cors-referer"); - let server_handler = ServerHandler::new( - pacts, - auto_cors, - referer, - provider_state, - provider_state_header_name, - empty_provider_states); - tokio::task::spawn_blocking(move || { - server_handler.start_server(port) - }).await.unwrap() - } - }, - Err(ref err) => { - match err.kind { - ErrorKind::HelpDisplayed => { - println!("{}", err.message); - Ok(()) - }, - ErrorKind::VersionDisplayed => { - print_version(); - println!(); - Ok(()) - }, - _ => err.exit() - } - } - } + .help("Include empty provider states when filtering with --provider-state")) } fn setup_logger(level: &str) { diff --git a/src/test.rs b/src/test.rs index dee264f..f20cb31 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,8 +1,11 @@ -use quickcheck::{TestResult, quickcheck}; +use clap::ErrorKind; +use expectest::prelude::*; +use quickcheck::{quickcheck, TestResult}; use rand::Rng; + +use crate::build_args; + use super::{integer_value, regex_value}; -use expectest::prelude::*; -use pact_matching::s; #[test] fn validates_integer_value() { @@ -11,7 +14,7 @@ fn validates_integer_value() { if rng.gen() && s.chars().any(|ch| !ch.is_numeric()) { TestResult::discard() } else { - let validation = integer_value(s.clone()); + let validation = integer_value(s.as_str()); match validation { Ok(_) => TestResult::from_bool(!s.is_empty() && s.chars().all(|ch| ch.is_numeric() )), Err(_) => TestResult::from_bool(s.is_empty() || s.chars().find(|ch| !ch.is_numeric() ).is_some()) @@ -20,12 +23,82 @@ fn validates_integer_value() { } quickcheck(prop as fn(_) -> _); - expect!(integer_value(s!("1234"))).to(be_ok()); - expect!(integer_value(s!("1234x"))).to(be_err()); + expect!(integer_value("1234")).to(be_ok()); + expect!(integer_value("1234x")).to(be_err()); } #[test] fn validates_regex_value() { - expect!(regex_value(s!("1234"))).to(be_ok()); - expect!(regex_value(s!("["))).to(be_err()); + expect!(regex_value("1234")).to(be_ok()); + expect!(regex_value("[")).to(be_err()); +} + +#[test] +fn test_default_args() { + let args = vec!["test".to_string(), "--help".to_string()]; + let app = build_args("test", "0.0.0"); + let result = app.try_get_matches_from(args).unwrap_err(); + pretty_assertions::assert_eq!(result.kind(), ErrorKind::DisplayHelp); + + let expected_help = r#"test 0.0.0 +Pact Stub Server + +USAGE: + test [OPTIONS] + +OPTIONS: + -b, --broker-url + URL of the pact broker to fetch pacts from [env: PACT_BROKER_BASE_URL=] + + --cors-referer + Set the CORS Access-Control-Allow-Origin header to the Referer + + -d, --dir + Directory of pact files to load (can be repeated) + + -e, --extension + File extension to use when loading from a directory (default is json) + + --empty-provider-state + Include empty provider states when filtering with --provider-state + + -f, --file + Pact file to load (can be repeated) + + -h, --help + Print help information + + --insecure-tls + Disables TLS certificate validation + + -l, --loglevel + Log level (defaults to info) [possible values: error, warn, info, debug, trace, none] + + -o, --cors + Automatically respond to OPTIONS requests and return default CORS headers + + -p, --port + Port to run on (defaults to random port assigned by the OS) + + --provider-state-header-name + Name of the header parameter containing the provider state to be used in case multiple + matching interactions are found + + -s, --provider-state + Provider state regular expression to filter the responses by + + -t, --token + Bearer token to use when fetching pacts from URLS or Pact Broker + + -u, --url + URL of pact file to fetch (can be repeated) + + --user + User and password to use when fetching pacts from URLS or Pact Broker in user:password + form + + -v, --version + Print version information +"#; + pretty_assertions::assert_eq!(result.to_string(), expected_help); }