From e2f33d7cd9619775b33b3c7fd8c79dcd0138b0cf Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 23 Oct 2023 06:04:45 +1100 Subject: [PATCH] examples: Add error output demonstration Add an `errors` example that creates and prints various error types from the crate. This is useful for two main purposes: - Helps us catch mistakes by exercising error paths. - Helps us check that error output is useful for debugging. - Helps us check that output for "alloc" builds is good/useful (no-std) Run with appropriate features to verify the output, e.g., `cargo run --example errors --no-default-features --features=alloc` --- Cargo.toml | 4 ++ examples/errors.rs | 133 +++++++++++++++++++++++++++++++++++++++ src/primitives/decode.rs | 3 +- 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 examples/errors.rs diff --git a/Cargo.toml b/Cargo.toml index 264e55b09..2ca5d6f34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,7 @@ alloc = [] [target.'cfg(mutate)'.dev-dependencies] mutagen = { git = "https://github.com/llogiq/mutagen" } + +[[example]] +name = "errors" +required-features = [] diff --git a/examples/errors.rs b/examples/errors.rs new file mode 100644 index 000000000..6d88ab6a1 --- /dev/null +++ b/examples/errors.rs @@ -0,0 +1,133 @@ +//! Demonstrate output from the various crate errors. + +use core::fmt; +use std::error::Error; + +use bech32::{Fe32, Hrp}; + +fn main() { + crate_decode(); + // crate::encode only returns `fmt::Error` errors. + + crate_segwit_decode(); + crate_segwit_encode(); + + // TODO: Do the other primitives modules. + primitives_hrp(); +} + +/// Demonstrates `bech32::decode` errors. +fn crate_decode() { + use bech32::decode; + let function = "bech32::decode"; + + // The arguments to pass to `function`. + let strings = vec!["1qqq", "hrp1abc"]; + + for s in strings { + let err = decode(s).unwrap_err(); + println!("\n\n* Call `{}(\"{}\")` -> {:?}", function, s, err); + println!("\n------------"); + print_source(&err); + println!("------------"); + } +} + +/// Demonstrates the `Hrp::Error` variants. +fn primitives_hrp() { + use bech32::primitives::hrp::Error::*; + + println!("\n\n* All errors when parsing an invalid HRP"); + let errs = vec![ + TooLong(99), + Empty, + NonAsciiChar(char::from('\u{e9}')), + InvalidAsciiByte(200), + MixedCase, + ]; + println!("\n------------"); + + let last = errs.len() - 1; + for (i, e) in errs.iter().enumerate() { + println!("Debug: {:?}\nError: {}", e.clone(), e); + if i != last { + println!(""); + } + } + println!("------------"); +} + +// TODO: Generate address strings to trigger: +// - Padding(PaddingError) +// - WitnessLength(WitnessLengthError) +/// Demonstrates `bech32::segwit::decode` errors. +fn crate_segwit_decode() { + use bech32::segwit::decode; + let function = "bech32::segwit::decode"; + + // The arguments to pass to `function`. + let strings = vec!["1qpppppp", "bc1qabc", "bc1", "bc1mpppppp", "bc1qppppp"]; + + for s in strings { + let err = decode(s).unwrap_err(); + println!("\n\n* Call `{}(\"{}\")` -> {:?}", function, s, err); + println!("\n------------"); + print_source(&err); + println!("------------"); + } +} + +/// Demonstrates `bech32::segwit::encode` errors. +fn crate_segwit_encode() { + use bech32::segwit::encode; + let function = "bech32::segwit::encode"; + + // The arguments to pass to `function`. + let hrp = Hrp::parse("bc").expect("a valid HRP string"); + + let invalid_witness_version = Fe32::M; + + let valid_witness_program_segwit_v1 = [0x00, 0x01]; + let invalid_witness_program_too_short = [0x00]; + let invalid_witness_program_too_long = [0x00; 50]; + + let print = |err| { + println!("\n\n* Call `{}({}, [])` -> {:?}", function, hrp, err); + println!("\n------------"); + print_source(&err); + println!("------------"); + }; + + let err = encode(hrp, invalid_witness_version, &valid_witness_program_segwit_v1).unwrap_err(); + print(err); + + let err = encode(hrp, Fe32::P, &invalid_witness_program_too_short).unwrap_err(); + print(err); + + let err = encode(hrp, Fe32::P, &invalid_witness_program_too_long).unwrap_err(); + print(err); + + let err = encode(hrp, Fe32::Q, &valid_witness_program_segwit_v1).unwrap_err(); + print(err); +} + +/// Prints `e` in a similar fashion to the output created by `anyhow`. +#[cfg(feature = "std")] +fn print_source(mut e: &dyn Error) { + println!("Error: {}", e); + + if e.source().is_some() { + let mut counter = 0; + println!("\nCaused by: "); + + while e.source().is_some() { + let inner = e.source().unwrap(); + println!("\t{}: {}", counter, inner); + e = e.source().unwrap(); + counter += 1; + } + } +} + +#[cfg(not(feature = "std"))] +fn print_source(e: &dyn fmt::Display) { println!("{}", e) } diff --git a/src/primitives/decode.rs b/src/primitives/decode.rs index bff223e1e..7998154e4 100644 --- a/src/primitives/decode.rs +++ b/src/primitives/decode.rs @@ -955,12 +955,13 @@ mod tests { fn $test_name() { let res = SegwitHrpstring::new($address); if res.is_ok() { - panic!("{} sting should not be valid: {}", $address, $reason); + panic!("{} string should not be valid: {}", $address, $reason); } } )* } } + // TODO: We are not asserting on the error value, at least one of these is not failing for the reason stated. check_invalid_segwit_addresses! { invalid_segwit_address_0, "missing hrp", "1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; invalid_segwit_address_1, "missing data-checksum", "91111";