///|
/// Severity for diagnostic output.
pub(all) enum DiagnosticSeverity {
DiagnosticOk
DiagnosticInfo
DiagnosticWarning
DiagnosticError
} derive(Eq, Debug)
///|
/// Recommended next step for a failed or non-canonical input.
pub(all) enum DiagnosticRecommendation {
RecommendNone
RecommendNormalizeInput
RecommendLowercaseInput
RecommendCheckSeparator
RecommendCheckHrp
RecommendCheckCharset
RecommendCheckChecksum
RecommendCheckPadding
RecommendCheckWitnessVersion
RecommendCheckWitnessProgramLength
RecommendUseBech32
RecommendUseBech32m
RecommendCheckNetwork
RecommendCheckByteConversion
} derive(Eq, Debug)
///|
/// Diagnostic result for one input string.
pub(all) struct Diagnostic {
input : String
normalized : String
valid : Bool
segwit_valid : Bool
canonical : Bool
severity : DiagnosticSeverity
recommendation : DiagnosticRecommendation
case_style : CaseStyle
variant : String
hrp : String
network : String
program_kind : String
error_code : String
error_message : String
hints : Array[String]
} derive(Eq, Debug)
///|
/// Aggregated diagnostic counts over a batch of inputs.
pub(all) struct ValidationSummary {
total : Int
valid : Int
invalid : Int
segwit_valid : Int
canonical : Int
ok : Int
info : Int
warning : Int
error : Int
case_no_letters : Int
case_lower : Int
case_upper : Int
case_mixed : Int
variant_bech32 : Int
variant_bech32m : Int
network_mainnet : Int
network_testnet : Int
network_regtest : Int
network_unknown : Int
program_p2wpkh : Int
program_p2wsh : Int
program_taproot : Int
program_other : Int
recommend_none : Int
recommend_normalize_input : Int
recommend_lowercase_input : Int
recommend_check_separator : Int
recommend_check_hrp : Int
recommend_check_charset : Int
recommend_check_checksum : Int
recommend_check_padding : Int
recommend_check_witness_version : Int
recommend_check_witness_program_length : Int
recommend_use_bech32 : Int
recommend_use_bech32m : Int
recommend_check_network : Int
recommend_check_byte_conversion : Int
error_empty_input : Int
error_too_long : Int
error_missing_separator : Int
error_empty_hrp : Int
error_empty_data : Int
error_mixed_case : Int
error_invalid_hrp_char : Int
error_invalid_data_char : Int
error_invalid_data_value : Int
error_invalid_checksum : Int
error_invalid_bit_group : Int
error_invalid_padding : Int
error_invalid_witness_version : Int
error_invalid_witness_program_length : Int
error_invalid_witness_encoding : Int
error_unexpected_hrp : Int
error_unexpected_network : Int
} derive(Eq, Debug)
///|
/// Comparison between two diagnostics.
pub(all) struct ComparisonReport {
left_input : String
right_input : String
left_valid : Bool
right_valid : Bool
left_segwit_valid : Bool
right_segwit_valid : Bool
left_canonical : Bool
right_canonical : Bool
left_variant : String
right_variant : String
left_case_style : CaseStyle
right_case_style : CaseStyle
left_hrp : String
right_hrp : String
left_network : String
right_network : String
left_program_kind : String
right_program_kind : String
left_error_code : String
right_error_code : String
same_validity : Bool
same_segwit_validity : Bool
same_canonicality : Bool
same_variant : Bool
same_case_style : Bool
same_hrp : Bool
same_network : Bool
same_program_kind : Bool
same_error_code : Bool
same_length : Bool
difference_count : Int
} derive(Eq, Debug)
///|
pub fn severity_name(severity : DiagnosticSeverity) -> String {
match severity {
DiagnosticOk => "ok"
DiagnosticInfo => "info"
DiagnosticWarning => "warning"
DiagnosticError => "error"
}
}
///|
pub fn recommendation_name(recommendation : DiagnosticRecommendation) -> String {
match recommendation {
RecommendNone => "none"
RecommendNormalizeInput => "normalize_input"
RecommendLowercaseInput => "lowercase_input"
RecommendCheckSeparator => "check_separator"
RecommendCheckHrp => "check_hrp"
RecommendCheckCharset => "check_charset"
RecommendCheckChecksum => "check_checksum"
RecommendCheckPadding => "check_padding"
RecommendCheckWitnessVersion => "check_witness_version"
RecommendCheckWitnessProgramLength => "check_witness_program_length"
RecommendUseBech32 => "use_bech32"
RecommendUseBech32m => "use_bech32m"
RecommendCheckNetwork => "check_network"
RecommendCheckByteConversion => "check_byte_conversion"
}
}
///|
pub fn recommendation_message(
recommendation : DiagnosticRecommendation,
) -> String {
match recommendation {
RecommendNone => "input is already in a good shape"
RecommendNormalizeInput =>
"normalize the input to lower-case canonical form"
RecommendLowercaseInput =>
"convert the input to lower-case before re-encoding"
RecommendCheckSeparator => "add or move the separator '1'"
RecommendCheckHrp => "verify the human-readable part"
RecommendCheckCharset => "check the Bech32 data charset"
RecommendCheckChecksum => "recompute the checksum"
RecommendCheckPadding => "fix the bit conversion padding"
RecommendCheckWitnessVersion => "use a witness version between 0 and 16"
RecommendCheckWitnessProgramLength => "fix the witness program length"
RecommendUseBech32 => "version 0 witness programs must use Bech32"
RecommendUseBech32m => "version 1+ witness programs must use Bech32m"
RecommendCheckNetwork =>
"check whether the address belongs to the expected network"
RecommendCheckByteConversion =>
"check byte-to-word conversion and payload width"
}
}
///|
pub fn diagnose(input : String) -> Diagnostic {
build_diagnostic(input, false)
}
///|
pub fn diagnose_segwit(input : String) -> Diagnostic {
build_diagnostic(input, true)
}
///|
pub fn diagnose_many(inputs : Array[String]) -> Array[Diagnostic] {
let out = Array::new(capacity=inputs.length())
for input in inputs {
out.push(diagnose(input))
}
out
}
///|
pub fn diagnose_segwit_many(inputs : Array[String]) -> Array[Diagnostic] {
let out = Array::new(capacity=inputs.length())
for input in inputs {
out.push(diagnose_segwit(input))
}
out
}
///|
pub fn summarize_inputs(inputs : Array[String]) -> ValidationSummary {
summarize_diagnostics(diagnose_many(inputs))
}
///|
pub fn summarize_segwit_inputs(inputs : Array[String]) -> ValidationSummary {
summarize_diagnostics(diagnose_segwit_many(inputs))
}
///|
pub fn summarize_diagnostics(
diagnostics : Array[Diagnostic],
) -> ValidationSummary {
let mut total = 0
let mut valid = 0
let mut invalid = 0
let mut segwit_valid = 0
let mut canonical = 0
let mut ok = 0
let mut info = 0
let mut warning = 0
let mut error = 0
let mut case_no_letters = 0
let mut case_lower = 0
let mut case_upper = 0
let mut case_mixed = 0
let mut variant_bech32 = 0
let mut variant_bech32m = 0
let mut network_mainnet = 0
let mut network_testnet = 0
let mut network_regtest = 0
let mut network_unknown = 0
let mut program_p2wpkh = 0
let mut program_p2wsh = 0
let mut program_taproot = 0
let mut program_other = 0
let mut recommend_none = 0
let mut recommend_normalize_input = 0
let mut recommend_lowercase_input = 0
let mut recommend_check_separator = 0
let mut recommend_check_hrp = 0
let mut recommend_check_charset = 0
let mut recommend_check_checksum = 0
let mut recommend_check_padding = 0
let mut recommend_check_witness_version = 0
let mut recommend_check_witness_program_length = 0
let mut recommend_use_bech32 = 0
let mut recommend_use_bech32m = 0
let mut recommend_check_network = 0
let mut recommend_check_byte_conversion = 0
let mut error_empty_input = 0
let mut error_too_long = 0
let mut error_missing_separator = 0
let mut error_empty_hrp = 0
let mut error_empty_data = 0
let mut error_mixed_case = 0
let mut error_invalid_hrp_char = 0
let mut error_invalid_data_char = 0
let mut error_invalid_data_value = 0
let mut error_invalid_checksum = 0
let mut error_invalid_bit_group = 0
let mut error_invalid_padding = 0
let mut error_invalid_witness_version = 0
let mut error_invalid_witness_program_length = 0
let mut error_invalid_witness_encoding = 0
let mut error_unexpected_hrp = 0
let mut error_unexpected_network = 0
for diag in diagnostics {
total += 1
if diag.valid {
valid += 1
} else {
invalid += 1
}
if diag.segwit_valid {
segwit_valid += 1
}
if diag.canonical {
canonical += 1
}
match diag.severity {
DiagnosticOk => ok += 1
DiagnosticInfo => info += 1
DiagnosticWarning => warning += 1
DiagnosticError => error += 1
}
match diag.case_style {
CaseNoLetters => case_no_letters += 1
CaseLower => case_lower += 1
CaseUpper => case_upper += 1
CaseMixed => case_mixed += 1
}
match diag.variant {
"bech32" => variant_bech32 += 1
"bech32m" => variant_bech32m += 1
_ => ()
}
if diag.network == "bitcoin_mainnet" {
network_mainnet += 1
} else if diag.network == "bitcoin_testnet" {
network_testnet += 1
} else if diag.network == "bitcoin_regtest" {
network_regtest += 1
} else if !diag.network.is_empty() {
network_unknown += 1
}
if diag.program_kind == "p2wpkh" {
program_p2wpkh += 1
} else if diag.program_kind == "p2wsh" {
program_p2wsh += 1
} else if diag.program_kind == "taproot" {
program_taproot += 1
} else if !diag.program_kind.is_empty() {
program_other += 1
}
match diag.recommendation {
RecommendNone => recommend_none += 1
RecommendNormalizeInput => recommend_normalize_input += 1
RecommendLowercaseInput => recommend_lowercase_input += 1
RecommendCheckSeparator => recommend_check_separator += 1
RecommendCheckHrp => recommend_check_hrp += 1
RecommendCheckCharset => recommend_check_charset += 1
RecommendCheckChecksum => recommend_check_checksum += 1
RecommendCheckPadding => recommend_check_padding += 1
RecommendCheckWitnessVersion => recommend_check_witness_version += 1
RecommendCheckWitnessProgramLength =>
recommend_check_witness_program_length += 1
RecommendUseBech32 => recommend_use_bech32 += 1
RecommendUseBech32m => recommend_use_bech32m += 1
RecommendCheckNetwork => recommend_check_network += 1
RecommendCheckByteConversion => recommend_check_byte_conversion += 1
}
match diag.error_code {
"empty_input" => error_empty_input += 1
"too_long" => error_too_long += 1
"missing_separator" => error_missing_separator += 1
"empty_hrp" => error_empty_hrp += 1
"empty_data" => error_empty_data += 1
"mixed_case" => error_mixed_case += 1
"invalid_hrp_char" => error_invalid_hrp_char += 1
"invalid_data_char" => error_invalid_data_char += 1
"invalid_data_value" => error_invalid_data_value += 1
"invalid_checksum" => error_invalid_checksum += 1
"invalid_bit_group" => error_invalid_bit_group += 1
"invalid_padding" => error_invalid_padding += 1
"invalid_witness_version" => error_invalid_witness_version += 1
"invalid_witness_program_length" =>
error_invalid_witness_program_length += 1
"invalid_witness_encoding" => error_invalid_witness_encoding += 1
"unexpected_hrp" => error_unexpected_hrp += 1
"unexpected_network" => error_unexpected_network += 1
_ => ()
}
}
{
total,
valid,
invalid,
segwit_valid,
canonical,
ok,
info,
warning,
error,
case_no_letters,
case_lower,
case_upper,
case_mixed,
variant_bech32,
variant_bech32m,
network_mainnet,
network_testnet,
network_regtest,
network_unknown,
program_p2wpkh,
program_p2wsh,
program_taproot,
program_other,
recommend_none,
recommend_normalize_input,
recommend_lowercase_input,
recommend_check_separator,
recommend_check_hrp,
recommend_check_charset,
recommend_check_checksum,
recommend_check_padding,
recommend_check_witness_version,
recommend_check_witness_program_length,
recommend_use_bech32,
recommend_use_bech32m,
recommend_check_network,
recommend_check_byte_conversion,
error_empty_input,
error_too_long,
error_missing_separator,
error_empty_hrp,
error_empty_data,
error_mixed_case,
error_invalid_hrp_char,
error_invalid_data_char,
error_invalid_data_value,
error_invalid_checksum,
error_invalid_bit_group,
error_invalid_padding,
error_invalid_witness_version,
error_invalid_witness_program_length,
error_invalid_witness_encoding,
error_unexpected_hrp,
error_unexpected_network,
}
}
///|
pub fn compare_inputs(left : String, right : String) -> ComparisonReport {
compare_diagnostics(diagnose(left), diagnose(right))
}
///|
pub fn compare_diagnostics(
left : Diagnostic,
right : Diagnostic,
) -> ComparisonReport {
let same_validity = left.valid == right.valid
let same_segwit_validity = left.segwit_valid == right.segwit_valid
let same_canonicality = left.canonical == right.canonical
let same_variant = left.variant == right.variant
let same_case_style = left.case_style == right.case_style
let same_hrp = left.hrp == right.hrp
let same_network = left.network == right.network
let same_program_kind = left.program_kind == right.program_kind
let same_error_code = left.error_code == right.error_code
let same_length = left.normalized.length() == right.normalized.length()
let mut difference_count = 0
if !same_validity {
difference_count += 1
}
if !same_segwit_validity {
difference_count += 1
}
if !same_canonicality {
difference_count += 1
}
if !same_variant {
difference_count += 1
}
if !same_case_style {
difference_count += 1
}
if !same_hrp {
difference_count += 1
}
if !same_network {
difference_count += 1
}
if !same_program_kind {
difference_count += 1
}
if !same_error_code {
difference_count += 1
}
if !same_length {
difference_count += 1
}
{
left_input: left.input,
right_input: right.input,
left_valid: left.valid,
right_valid: right.valid,
left_segwit_valid: left.segwit_valid,
right_segwit_valid: right.segwit_valid,
left_canonical: left.canonical,
right_canonical: right.canonical,
left_variant: left.variant,
right_variant: right.variant,
left_case_style: left.case_style,
right_case_style: right.case_style,
left_hrp: left.hrp,
right_hrp: right.hrp,
left_network: left.network,
right_network: right.network,
left_program_kind: left.program_kind,
right_program_kind: right.program_kind,
left_error_code: left.error_code,
right_error_code: right.error_code,
same_validity,
same_segwit_validity,
same_canonicality,
same_variant,
same_case_style,
same_hrp,
same_network,
same_program_kind,
same_error_code,
same_length,
difference_count,
}
}
///|
pub fn comparison_hints(report : ComparisonReport) -> Array[String] {
let hints = Array::new()
if !report.same_validity {
hints.push("one input is valid while the other is not")
}
if !report.same_variant {
hints.push("the checksum variants differ")
}
if !report.same_hrp {
hints.push("the human-readable part differs")
}
if !report.same_network {
hints.push("the SegWit networks differ")
}
if !report.same_program_kind {
hints.push("the witness program categories differ")
}
if !report.same_error_code {
hints.push("the error codes differ")
}
if hints.is_empty() {
hints.push("the two inputs behave the same way")
}
hints
}
///|
pub fn render_diagnostic(diagnostic : Diagnostic) -> String {
let out = StringBuilder(size_hint=512)
write_line(out, "diagnostic")
write_kv(out, "input", diagnostic.input)
write_kv(out, "normalized", diagnostic.normalized)
write_kv(out, "valid", bool_text(diagnostic.valid))
write_kv(out, "segwit_valid", bool_text(diagnostic.segwit_valid))
write_kv(out, "canonical", bool_text(diagnostic.canonical))
write_kv(out, "severity", severity_name(diagnostic.severity))
write_kv(
out,
"recommendation",
recommendation_name(diagnostic.recommendation),
)
write_kv(out, "case_style", case_style_name(diagnostic.case_style))
write_kv(out, "variant", diagnostic.variant)
write_kv(out, "hrp", diagnostic.hrp)
write_kv(out, "network", diagnostic.network)
write_kv(out, "program_kind", diagnostic.program_kind)
write_kv(out, "error_code", diagnostic.error_code)
write_kv(out, "error_message", diagnostic.error_message)
write_hint_block(out, diagnostic.hints)
out.to_string()
}
///|
pub fn render_comparison(report : ComparisonReport) -> String {
let out = StringBuilder(size_hint=512)
write_line(out, "comparison")
write_kv(out, "left_input", report.left_input)
write_kv(out, "right_input", report.right_input)
write_kv(out, "same_validity", bool_text(report.same_validity))
write_kv(out, "same_segwit_validity", bool_text(report.same_segwit_validity))
write_kv(out, "same_canonicality", bool_text(report.same_canonicality))
write_kv(out, "same_variant", bool_text(report.same_variant))
write_kv(out, "same_case_style", bool_text(report.same_case_style))
write_kv(out, "same_hrp", bool_text(report.same_hrp))
write_kv(out, "same_network", bool_text(report.same_network))
write_kv(out, "same_program_kind", bool_text(report.same_program_kind))
write_kv(out, "same_error_code", bool_text(report.same_error_code))
write_kv(out, "same_length", bool_text(report.same_length))
write_kv(
out,
"difference_count",
int_to_decimal_string(report.difference_count),
)
write_line(out, "")
write_line(out, "comparison_hints")
write_hint_block(out, comparison_hints(report))
out.to_string()
}
///|
pub fn render_validation_summary(summary : ValidationSummary) -> String {
let out = StringBuilder(size_hint=2048)
write_line(out, "validation summary")
write_kv(out, "total", int_to_decimal_string(summary.total))
write_kv(out, "valid", int_to_decimal_string(summary.valid))
write_kv(out, "invalid", int_to_decimal_string(summary.invalid))
write_kv(out, "segwit_valid", int_to_decimal_string(summary.segwit_valid))
write_kv(out, "canonical", int_to_decimal_string(summary.canonical))
write_kv(out, "ok", int_to_decimal_string(summary.ok))
write_kv(out, "info", int_to_decimal_string(summary.info))
write_kv(out, "warning", int_to_decimal_string(summary.warning))
write_kv(out, "error", int_to_decimal_string(summary.error))
write_kv(
out,
"case_no_letters",
int_to_decimal_string(summary.case_no_letters),
)
write_kv(out, "case_lower", int_to_decimal_string(summary.case_lower))
write_kv(out, "case_upper", int_to_decimal_string(summary.case_upper))
write_kv(out, "case_mixed", int_to_decimal_string(summary.case_mixed))
write_kv(out, "variant_bech32", int_to_decimal_string(summary.variant_bech32))
write_kv(
out,
"variant_bech32m",
int_to_decimal_string(summary.variant_bech32m),
)
write_kv(
out,
"network_mainnet",
int_to_decimal_string(summary.network_mainnet),
)
write_kv(
out,
"network_testnet",
int_to_decimal_string(summary.network_testnet),
)
write_kv(
out,
"network_regtest",
int_to_decimal_string(summary.network_regtest),
)
write_kv(
out,
"network_unknown",
int_to_decimal_string(summary.network_unknown),
)
write_kv(out, "program_p2wpkh", int_to_decimal_string(summary.program_p2wpkh))
write_kv(out, "program_p2wsh", int_to_decimal_string(summary.program_p2wsh))
write_kv(
out,
"program_taproot",
int_to_decimal_string(summary.program_taproot),
)
write_kv(out, "program_other", int_to_decimal_string(summary.program_other))
write_line(out, "")
write_line(out, "recommendations")
write_kv(out, "none", int_to_decimal_string(summary.recommend_none))
write_kv(
out,
"normalize_input",
int_to_decimal_string(summary.recommend_normalize_input),
)
write_kv(
out,
"lowercase_input",
int_to_decimal_string(summary.recommend_lowercase_input),
)
write_kv(
out,
"check_separator",
int_to_decimal_string(summary.recommend_check_separator),
)
write_kv(out, "check_hrp", int_to_decimal_string(summary.recommend_check_hrp))
write_kv(
out,
"check_charset",
int_to_decimal_string(summary.recommend_check_charset),
)
write_kv(
out,
"check_checksum",
int_to_decimal_string(summary.recommend_check_checksum),
)
write_kv(
out,
"check_padding",
int_to_decimal_string(summary.recommend_check_padding),
)
write_kv(
out,
"check_witness_version",
int_to_decimal_string(summary.recommend_check_witness_version),
)
write_kv(
out,
"check_witness_program_length",
int_to_decimal_string(summary.recommend_check_witness_program_length),
)
write_kv(
out,
"use_bech32",
int_to_decimal_string(summary.recommend_use_bech32),
)
write_kv(
out,
"use_bech32m",
int_to_decimal_string(summary.recommend_use_bech32m),
)
write_kv(
out,
"check_network",
int_to_decimal_string(summary.recommend_check_network),
)
write_kv(
out,
"check_byte_conversion",
int_to_decimal_string(summary.recommend_check_byte_conversion),
)
write_line(out, "")
write_line(out, "errors")
write_kv(out, "empty_input", int_to_decimal_string(summary.error_empty_input))
write_kv(out, "too_long", int_to_decimal_string(summary.error_too_long))
write_kv(
out,
"missing_separator",
int_to_decimal_string(summary.error_missing_separator),
)
write_kv(out, "empty_hrp", int_to_decimal_string(summary.error_empty_hrp))
write_kv(out, "empty_data", int_to_decimal_string(summary.error_empty_data))
write_kv(out, "mixed_case", int_to_decimal_string(summary.error_mixed_case))
write_kv(
out,
"invalid_hrp_char",
int_to_decimal_string(summary.error_invalid_hrp_char),
)
write_kv(
out,
"invalid_data_char",
int_to_decimal_string(summary.error_invalid_data_char),
)
write_kv(
out,
"invalid_data_value",
int_to_decimal_string(summary.error_invalid_data_value),
)
write_kv(
out,
"invalid_checksum",
int_to_decimal_string(summary.error_invalid_checksum),
)
write_kv(
out,
"invalid_bit_group",
int_to_decimal_string(summary.error_invalid_bit_group),
)
write_kv(
out,
"invalid_padding",
int_to_decimal_string(summary.error_invalid_padding),
)
write_kv(
out,
"invalid_witness_version",
int_to_decimal_string(summary.error_invalid_witness_version),
)
write_kv(
out,
"invalid_witness_program_length",
int_to_decimal_string(summary.error_invalid_witness_program_length),
)
write_kv(
out,
"invalid_witness_encoding",
int_to_decimal_string(summary.error_invalid_witness_encoding),
)
write_kv(
out,
"unexpected_hrp",
int_to_decimal_string(summary.error_unexpected_hrp),
)
write_kv(
out,
"unexpected_network",
int_to_decimal_string(summary.error_unexpected_network),
)
out.to_string()
}
///|
pub fn summary_has_errors(summary : ValidationSummary) -> Bool {
summary.error > 0
}
///|
pub fn summary_is_clean(summary : ValidationSummary) -> Bool {
summary.invalid == 0 && summary.error == 0
}
///|
pub fn summary_has_segwit(summary : ValidationSummary) -> Bool {
summary.segwit_valid > 0
}
///|
fn build_diagnostic(input : String, segwit_mode : Bool) -> Diagnostic {
let report = validation_report(input)
let canonical = is_canonical(input)
let mut severity = if report.valid {
if canonical {
DiagnosticOk
} else {
DiagnosticInfo
}
} else {
DiagnosticError
}
let mut recommendation = recommendation_for_general_report(report, canonical)
let mut network = ""
let mut program_kind = ""
let hints = hints_for_general_report(report, canonical)
if report.valid {
match inspect_segwit(input) {
Ok(info) => {
network = network_name(info.network)
program_kind = witness_program_kind_name(info.program_kind)
if network == "unknown" && segwit_mode {
severity = DiagnosticWarning
recommendation = RecommendCheckNetwork
hints.push(
"the address is valid Bech32 but the HRP is not a standard Bitcoin network",
)
}
}
Err(err) =>
if segwit_mode {
severity = DiagnosticWarning
recommendation = recommendation_for_error(err)
hints.append(hints_for_error(err))
}
}
}
if !report.valid {
hints.append(hints_for_error_code(report.error_code))
}
if !canonical && report.valid {
hints.push(
"normalize the input to lowercase before storing or publishing it",
)
}
if segwit_mode && report.valid && !report.segwit_valid {
hints.push("the string is valid Bech32 but not a valid SegWit address")
}
{
input,
normalized: report.normalized,
valid: report.valid,
segwit_valid: report.segwit_valid,
canonical,
severity,
recommendation,
case_style: classify_case(input),
variant: report.variant,
hrp: report.hrp,
network,
program_kind,
error_code: report.error_code,
error_message: report.error_message,
hints,
}
}
///|
fn recommendation_for_general_report(
report : ValidationReport,
canonical : Bool,
) -> DiagnosticRecommendation {
if !report.valid {
recommendation_for_error_code(report.error_code)
} else if !canonical {
RecommendNormalizeInput
} else {
RecommendNone
}
}
///|
fn recommendation_for_error_code(code : String) -> DiagnosticRecommendation {
match code {
"empty_input" => RecommendCheckSeparator
"too_long" => RecommendCheckByteConversion
"missing_separator" => RecommendCheckSeparator
"empty_hrp" => RecommendCheckHrp
"empty_data" => RecommendCheckChecksum
"mixed_case" => RecommendLowercaseInput
"invalid_hrp_char" => RecommendCheckHrp
"invalid_data_char" => RecommendCheckCharset
"invalid_data_value" => RecommendCheckByteConversion
"invalid_checksum" => RecommendCheckChecksum
"invalid_bit_group" => RecommendCheckByteConversion
"invalid_padding" => RecommendCheckPadding
"invalid_witness_version" => RecommendCheckWitnessVersion
"invalid_witness_program_length" => RecommendCheckWitnessProgramLength
"invalid_witness_encoding" => RecommendCheckNetwork
"unexpected_hrp" => RecommendCheckHrp
"unexpected_network" => RecommendCheckNetwork
_ => RecommendNone
}
}
///|
fn recommendation_for_error(err : Bech32Error) -> DiagnosticRecommendation {
recommendation_for_error_code(error_code(err))
}
///|
fn hints_for_general_report(
report : ValidationReport,
canonical : Bool,
) -> Array[String] {
let hints = Array::new()
if report.valid && canonical {
hints.push("this input is a valid canonical Bech32 string")
} else if report.valid && !canonical {
hints.push("this input is valid but not canonical lowercase form")
} else {
hints.push("the input is not a valid Bech32 string")
}
hints
}
///|
fn hints_for_error_code(code : String) -> Array[String] {
match code {
"empty_input" => ["provide a full Bech32 string"]
"too_long" => ["the string exceeds the 90-character Bech32 limit"]
"missing_separator" => ["add the separator character '1'"]
"empty_hrp" => ["the human-readable part cannot be empty"]
"empty_data" => ["the data part must contain at least six checksum words"]
"mixed_case" => ["use all lower-case or all upper-case input"]
"invalid_hrp_char" =>
["the human-readable part must stay within printable ASCII"]
"invalid_data_char" => ["the data part must use the Bech32 character set"]
"invalid_data_value" => ["the decoded data value is outside 0..31"]
"invalid_checksum" => ["recompute the checksum with the correct variant"]
"invalid_bit_group" => ["check the from_bits and to_bits arguments"]
"invalid_padding" => ["use canonical padding rules when decoding"]
"invalid_witness_version" => ["SegWit witness version must stay in 0..16"]
"invalid_witness_program_length" =>
["the witness program length does not match the selected version"]
"invalid_witness_encoding" =>
["the checksum variant does not match the witness version"]
"unexpected_hrp" =>
["the decoded HRP does not match the expected namespace"]
"unexpected_network" =>
["the decoded network does not match the expected network"]
_ => ["unknown validation error"]
}
}
///|
fn hints_for_error(err : Bech32Error) -> Array[String] {
match err {
EmptyInput => ["provide a full Bech32 string"]
TooLong(_) => ["the string exceeds the 90-character Bech32 limit"]
MissingSeparator => ["add the separator character '1'"]
EmptyHrp => ["the human-readable part cannot be empty"]
EmptyData => ["the data part must contain at least six checksum words"]
MixedCase => ["use all lower-case or all upper-case input"]
InvalidHrpChar(_, _) =>
["the human-readable part must stay within printable ASCII"]
InvalidDataChar(_, _) => ["the data part must use the Bech32 character set"]
InvalidDataValue(_, _) => ["the decoded data value is outside 0..31"]
InvalidChecksum => ["recompute the checksum with the correct variant"]
InvalidBitGroup(_, _) => ["check the from_bits and to_bits arguments"]
InvalidPadding => ["use canonical padding rules when decoding"]
InvalidWitnessVersion(_) => ["SegWit witness version must stay in 0..16"]
InvalidWitnessProgramLength(_, _) =>
["the witness program length does not match the selected version"]
InvalidWitnessEncoding(expected, actual) =>
[
"the checksum variant does not match the witness version",
"expected " +
variant_name(expected) +
" but decoded " +
variant_name(actual),
]
UnexpectedHrp(expected, actual) =>
[
"the decoded HRP does not match the expected namespace",
"expected " + expected + " but got " + actual,
]
UnexpectedNetwork(expected, actual) =>
[
"the decoded network does not match the expected network",
"expected " +
network_name(expected) +
" but got " +
network_name(actual),
]
}
}
///|
fn write_kv(out : StringBuilder, key : String, value : String) -> Unit {
out.write_string(key)
out.write_string(": ")
out.write_string(value)
out.write_char('\n')
}
///|
fn write_line(out : StringBuilder, value : String) -> Unit {
out.write_string(value)
out.write_char('\n')
}
///|
fn write_hint_block(out : StringBuilder, hints : Array[String]) -> Unit {
if hints.is_empty() {
write_line(out, "hints: none")
return
}
write_line(out, "hints")
for hint in hints {
out.write_string("- ")
out.write_string(hint)
out.write_char('\n')
}
}
///|
fn bool_text(value : Bool) -> String {
if value {
"true"
} else {
"false"
}
}
///|
fn int_to_decimal_string(value : Int) -> String {
if value == 0 {
return "0"
}
let mut n = value
let mut negative = false
if n < 0 {
negative = true
n = -n
}
let digits = Array::new()
while n > 0 {
let d = n % 10
digits.push(char_at("0123456789", d))
n = n / 10
}
let out = StringBuilder(size_hint=12)
if negative {
out.write_char('-')
}
let mut i = digits.length()
while i > 0 {
i -= 1
out.write_char(digits[i])
}
out.to_string()
}