use std::io::{Read, Write};
use pbech32::{Bech32, Encoder, Hrp, Scheme};
const DEFAULT_SCHEME: Scheme = Scheme::Bech32m;
const DEFAULT_HRP: &str = "example";
#[derive(Clone, Debug, Eq, PartialEq)]
struct EncodeConfig {
scheme: Scheme,
hrp: Hrp,
}
impl EncodeConfig {
pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
use std::env::{VarError, var};
let scheme = match var("BECH32_SCHEME") {
Ok(s) if !s.is_empty() => match s.as_ref() {
"bech32" => Scheme::Bech32,
"bech32m" => Scheme::Bech32m,
_ => return Err(format!("unknown scheme: {}", s).into()),
},
Ok(_) | Err(VarError::NotPresent) => DEFAULT_SCHEME,
Err(err) => return Err(Box::new(err)),
};
let hrp = match var("BECH32_HRP") {
Ok(s) if !s.is_empty() => s.parse::<Hrp>()?,
Ok(_) | Err(VarError::NotPresent) => DEFAULT_HRP.parse::<Hrp>()?,
Err(err) => return Err(Box::new(err)),
};
Ok(Self { scheme, hrp })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum Action {
Encode(EncodeConfig),
Decode,
Help,
}
impl Action {
const HELP: &str = "Usage: bech32 [encode|decode|help]\n";
pub fn run<R: Read, W: Write>(&self, mut src: R, mut dst: &mut W) -> Result<(), Box<dyn std::error::Error>> {
match self {
Action::Encode(config) => {
let mut encoder = Encoder::new(&mut dst, config.scheme, config.hrp.clone())?; std::io::copy(&mut src, &mut encoder)?; encoder.flush()?; },
Action::Decode => {
let mut s = String::new(); src.read_to_string(&mut s)?; let b: Bech32 = s.parse()?; dst.write_all(&b.data)?; },
Action::Help => dst.write_all(Self::HELP.as_ref())?,
};
Ok(())
}
}
impl std::str::FromStr for Action {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"e" | "enc" | "encode" => Ok(Action::Encode(EncodeConfig::from_env()?)),
"d" | "dec" | "decode" => Ok(Action::Decode),
"h" | "-h" | "--help" | "help" => Ok(Action::Help),
_ => Err(format!("unknown action: {}", s).into()),
}
}
}
fn run<R: Read, W: Write>(args: Vec<String>, mut src: R, mut dst: &mut W) -> Result<(), Box<dyn std::error::Error>> {
match args.len() {
2 => args[1].parse::<Action>()?.run(&mut src, &mut dst), _ => Err(format!("Usage: {} [encode|decode|help]", args[0]).into()), }
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect(); let (mut stdin, mut stdout) = (std::io::stdin(), std::io::stdout()); run(args, &mut stdin, &mut stdout) }
#[cfg(test)]
mod tests {
mod encode_config {
use super::super::*;
fn with_env<F: FnOnce()>(vals: &[(&str, &str)], f: F) {
use std::env::{VarError, remove_var, set_var, var};
let old_vals = vals.iter().map(|(key, _)| (key, match var(key) {
Ok(s) if !s.is_empty() => Some(s),
Ok(_) | Err(VarError::NotPresent) => None,
Err(err) => panic!("{err}"),
}));
vals.iter().for_each(|(key, val)| unsafe { set_var(key, val) }); f();
old_vals.for_each(|(key, val)| match val {
Some(val) => unsafe { set_var(key, val) },
None => unsafe { remove_var(key) },
});
}
#[test]
#[ignore] fn test_from_env() {
let hrp: Hrp = DEFAULT_HRP.parse().unwrap();
let pass_tests = vec![(
"empty",
vec![],
EncodeConfig { scheme: Scheme::Bech32m, hrp: hrp.clone() },
), (
"scheme=bech32",
vec![("BECH32_SCHEME", "bech32")],
EncodeConfig { scheme: Scheme::Bech32, hrp: hrp.clone() },
), (
"scheme=bech32m",
vec![("BECH32_SCHEME", "bech32m")],
EncodeConfig { scheme: Scheme::Bech32m, hrp: hrp.clone() },
), (
"hrp=asdf",
vec![("BECH32_HRP", "asdf")],
EncodeConfig { scheme: Scheme::Bech32m, hrp: "asdf".parse().unwrap() },
), (
"scheme=bech32, hrp=fdsa",
vec![("BECH32_SCHEME", "bech32"), ("BECH32_HRP", "fdsa")],
EncodeConfig { scheme: Scheme::Bech32, hrp: "fdsa".parse().unwrap() },
)];
for (name, env, exp) in pass_tests {
with_env(&env, || {
let got = EncodeConfig::from_env().unwrap();
assert_eq!(got, exp, "{name}");
});
}
let fail_tests = vec![(
"scheme=invalid",
vec![("BECH32_SCHEME", "invalid")],
"unknown scheme: invalid",
), (
"hrp=foo bar",
vec![("BECH32_SCHEME", ""), ("BECH32_HRP", "foo bar")],
"invalid character at position 3",
)];
for (name, env, exp) in fail_tests {
with_env(&env, || {
match EncodeConfig::from_env() {
Ok(config) => panic!("got {config:?}, exp err"),
Err(err) => assert_eq!(err.to_string(), exp, "{name}"),
};
});
}
for key in vec!["BECH32_SCHEME", "BECH32_HRP"] {
unsafe { std::env::remove_var(key); }
}
}
}
mod action {
use super::super::*;
#[test]
fn test_from_str() {
let hrp: Hrp = DEFAULT_HRP.parse().unwrap();
let config = EncodeConfig { scheme: DEFAULT_SCHEME, hrp: hrp };
let tests = vec![
("d", Action::Decode),
("dec", Action::Decode),
("decode", Action::Decode),
("e", Action::Encode(config.clone())),
("enc", Action::Encode(config.clone())),
("encode", Action::Encode(config.clone())),
("-h", Action::Help),
("--help", Action::Help),
("help", Action::Help),
];
for (s, exp) in tests {
let got: Action = s.parse().unwrap();
assert_eq!(got, exp, "{s}");
}
}
#[test]
fn test_from_str_fail() {
let tests = vec![
("asdf", "unknown action: asdf"),
];
for (s, exp) in tests {
match s.parse::<Action>() {
Ok(val) => panic!("got {val:?}, exp err"),
Err(err) => assert_eq!(err.to_string(), exp, "{s}"),
};
}
}
#[test]
fn test_run() {
let hrp: Hrp = DEFAULT_HRP.parse().unwrap();
let config = EncodeConfig { scheme: DEFAULT_SCHEME, hrp: hrp };
let tests = vec![
("encode asdf", Action::Encode(config.clone()), "asdf", "example1v9ekges6962cn"),
("decode asdf", Action::Decode, "example1v9ekges6962cn", "asdf\0"),
("help", Action::Help, "", Action::HELP),
];
for (name, action, s, exp) in tests {
let mut got = vec![]; action.run(&mut s.as_bytes(), &mut got).unwrap(); let got = str::from_utf8(&got).unwrap(); assert_eq!(got, exp, "{name}"); }
}
}
#[test]
fn test_run() {
use super::*;
let pass_tests = vec![(
"encode",
vec!["bech32".to_string(), "encode".to_string()],
"asdf",
"example1v9ekges6962cn",
), (
"decode",
vec!["bech32".to_string(), "decode".to_string()],
"example1v9ekges6962cn",
"asdf\0",
)];
for (name, args, s, exp) in pass_tests {
let mut got = vec![]; run(args, &mut s.as_bytes(), &mut got).unwrap(); let got = str::from_utf8(&got).unwrap(); assert_eq!(got, exp, "{name}"); }
let fail_tests = vec![(
"missing action",
vec!["bech32".to_string()],
"",
"Usage: bech32 [encode|decode|help]",
), (
"extra args",
vec!["bech32".to_string(), "foo".to_string(), "bar".to_string()],
"",
"Usage: bech32 [encode|decode|help]",
)];
for (name, args, s, exp) in fail_tests {
let mut got = vec![]; match run(args, &mut s.as_bytes(), &mut got) {
Ok(_) => {
let got = str::from_utf8(&got).unwrap(); panic!("got success (got = {got}), exp error");
},
Err(err) => assert_eq!(err.to_string(), exp, "{name}"),
}
}
}
}