From 9f6c3c4d29ec552756dd1e3d59d87e52e86db702 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 14 Jun 2023 21:06:00 +0200 Subject: [PATCH 1/3] Suggest valid message or constructor name, when misspelled --- Cargo.lock | 1 + crates/transcode/Cargo.toml | 1 + crates/transcode/src/lib.rs | 43 +++++++++++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46c9cc061..b3299927a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,6 +800,7 @@ dependencies = [ "serde_json", "sp-core 18.0.0", "sp-keyring", + "strsim", "thiserror", "tracing", ] diff --git a/crates/transcode/Cargo.toml b/crates/transcode/Cargo.toml index 50ce6d8a6..d1fa587a1 100644 --- a/crates/transcode/Cargo.toml +++ b/crates/transcode/Cargo.toml @@ -36,6 +36,7 @@ scale-info = { version = "2.4.0", default-features = false, features = ["derive" serde = { version = "1.0.159", default-features = false, features = ["derive"] } serde_json = "1.0.95" thiserror = "1.0.40" +strsim = "0.10.0" [dev-dependencies] assert_matches = "1.5.0" diff --git a/crates/transcode/src/lib.rs b/crates/transcode/src/lib.rs index e803ec81e..62322902d 100644 --- a/crates/transcode/src/lib.rs +++ b/crates/transcode/src/lib.rs @@ -127,6 +127,7 @@ use ink_metadata::{ InkProject, MessageSpec, }; +use itertools::Itertools; use scale::{ Compact, Decode, @@ -140,6 +141,7 @@ use scale_info::{ Field, }; use std::{ + cmp::Ordering, fmt::Debug, path::Path, }; @@ -151,6 +153,24 @@ pub struct ContractMessageTranscoder { transcoder: Transcoder, } +/// Find strings from an iterable of `possible_values` similar to a given value `v` +/// Returns a Vec of all possible values that exceed a similarity threshold +/// sorted by ascending similarity, most similar comes last +/// Extracted from https://github.com/clap-rs/clap/blob/v4.3.4/clap_builder/src/parser/features/suggestions.rs#L11-L26 +pub(crate) fn did_you_mean(v: &str, possible_values: I) -> Vec +where + T: AsRef, + I: IntoIterator, +{ + let mut candidates: Vec<(f64, String)> = possible_values + .into_iter() + .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned())) + .filter(|(confidence, _)| *confidence > 0.7) + .collect(); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.into_iter().map(|(_, pv)| pv).collect() +} + impl ContractMessageTranscoder { pub fn new(metadata: InkProject) -> Self { let transcoder = TranscoderBuilder::new(metadata.registry()) @@ -200,9 +220,18 @@ impl ContractMessageTranscoder { )) } (None, None) => { + let constructors = self.constructors().map(|c| c.label()); + let messages = self.messages().map(|c| c.label()); + let possible_values: Vec<_> = constructors.chain(messages).collect(); + let help_txt = did_you_mean(name, possible_values.clone()) + .first() + .map(|suggestion| format!("Did you mean '{}'", suggestion)) + .unwrap_or_else(|| { + format!("Should be one of: {}", possible_values.iter().join(", ")) + }); + return Err(anyhow::anyhow!( - "No constructor or message with the name '{}' found", - name + "No constructor or message with the name '{name}' found.\n{help_txt}", )) } }; @@ -538,6 +567,16 @@ mod tests { Ok(()) } + #[test] + fn encode_misspelled_arg() { + let metadata = generate_metadata(); + let transcoder = ContractMessageTranscoder::new(metadata); + assert_eq!( + transcoder.encode("fip", ["true"]).unwrap_err().to_string(), + "No constructor or message with the name 'fip' found.\nDid you mean 'flip'" + ); + } + #[test] fn encode_mismatching_args_length() { let metadata = generate_metadata(); From 47c331589cd7c42dca8373c7d48b27ef412a67e9 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 14 Jun 2023 21:15:22 +0200 Subject: [PATCH 2/3] add question mark --- crates/transcode/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/transcode/src/lib.rs b/crates/transcode/src/lib.rs index 62322902d..3b5cfdd6f 100644 --- a/crates/transcode/src/lib.rs +++ b/crates/transcode/src/lib.rs @@ -225,7 +225,7 @@ impl ContractMessageTranscoder { let possible_values: Vec<_> = constructors.chain(messages).collect(); let help_txt = did_you_mean(name, possible_values.clone()) .first() - .map(|suggestion| format!("Did you mean '{}'", suggestion)) + .map(|suggestion| format!("Did you mean '{}'?", suggestion)) .unwrap_or_else(|| { format!("Should be one of: {}", possible_values.iter().join(", ")) }); @@ -573,7 +573,7 @@ mod tests { let transcoder = ContractMessageTranscoder::new(metadata); assert_eq!( transcoder.encode("fip", ["true"]).unwrap_err().to_string(), - "No constructor or message with the name 'fip' found.\nDid you mean 'flip'" + "No constructor or message with the name 'fip' found.\nDid you mean 'flip'?" ); } From 57f4ffe4f3a7127233dced3bd1d327e1319fc3b4 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 14 Jun 2023 21:24:40 +0200 Subject: [PATCH 3/3] remove pub --- crates/transcode/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/transcode/src/lib.rs b/crates/transcode/src/lib.rs index 3b5cfdd6f..defeae145 100644 --- a/crates/transcode/src/lib.rs +++ b/crates/transcode/src/lib.rs @@ -157,7 +157,7 @@ pub struct ContractMessageTranscoder { /// Returns a Vec of all possible values that exceed a similarity threshold /// sorted by ascending similarity, most similar comes last /// Extracted from https://github.com/clap-rs/clap/blob/v4.3.4/clap_builder/src/parser/features/suggestions.rs#L11-L26 -pub(crate) fn did_you_mean(v: &str, possible_values: I) -> Vec +fn did_you_mean(v: &str, possible_values: I) -> Vec where T: AsRef, I: IntoIterator,