Skip to main content

ms_codec/
error.rs

1//! ms-codec error taxonomy. Variants mirror SPEC §4 decoder validity rules
2//! plus the encoder-side validation surface from SPEC §3.5 / §3.5.1.
3
4use std::fmt;
5
6/// ms-codec error type.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum Error {
10    /// Upstream codex32 parse / checksum failure (delegated from rust-codex32).
11    Codex32(codex32::Error),
12    /// HRP was not "ms" (SPEC §4 rule 2).
13    WrongHrp {
14        /// The HRP that was observed.
15        got: String,
16    },
17    /// Threshold was not 0 (SPEC §4 rule 3).
18    ThresholdNotZero {
19        /// The threshold-position byte (ASCII digit) that was observed.
20        got: u8,
21    },
22    /// Share-index was not 's' — BIP-93 requires 's' for threshold=0 (SPEC §4 rule 4).
23    ShareIndexNotSecret {
24        /// The share-index character that was observed.
25        got: char,
26    },
27    /// Tag bytes were not in the codex32 alphabet (SPEC §4 rule 5).
28    TagInvalidAlphabet {
29        /// The 4-byte id-field bytes that failed alphabet validation.
30        got: [u8; 4],
31    },
32    /// Tag was structurally valid but not in RESERVED_TAG_TABLE (SPEC §4 rule 6).
33    UnknownTag {
34        /// The 4-byte tag that was not recognized.
35        got: [u8; 4],
36    },
37    /// Tag was in RESERVED_TAG_TABLE but reserved-not-emitted in v0.1 (SPEC §4 rule 7,
38    /// SPEC §3.5.1 encoder symmetry).
39    ReservedTagNotEmittedInV01 {
40        /// The 4-byte reserved tag (one of seed/xprv/mnem/prvk in v0.1).
41        got: [u8; 4],
42    },
43    /// Reserved-prefix byte was not 0x00 (SPEC §4 rule 8).
44    ReservedPrefixViolation {
45        /// The non-zero prefix byte that was observed.
46        got: u8,
47    },
48    /// Total string length was outside the v0.1 emittable set (SPEC §4 rule 9).
49    UnexpectedStringLength {
50        /// The total string length that was observed.
51        got: usize,
52        /// The set of v0.1-emittable lengths.
53        allowed: &'static [usize],
54    },
55    /// Payload byte length did not match the tag's spec (SPEC §3.5, §4 rule 10).
56    PayloadLengthMismatch {
57        /// The 4-byte tag whose length set was checked against.
58        tag: [u8; 4],
59        /// The set of valid byte lengths for this tag.
60        expected: &'static [usize],
61        /// The observed payload byte length (after stripping the prefix byte).
62        got: usize,
63    },
64}
65
66impl fmt::Display for Error {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self {
69            Error::Codex32(e) => write!(f, "codex32 parse error: {:?}", e),
70            Error::WrongHrp { got } => write!(f, "wrong HRP: got {:?}, expected \"ms\"", got),
71            Error::ThresholdNotZero { got } => {
72                write!(
73                    f,
74                    "threshold not 0 (got '{}'); v0.1 is single-string only",
75                    *got as char
76                )
77            }
78            Error::ShareIndexNotSecret { got } => {
79                write!(
80                    f,
81                    "share-index not 's' (got '{}'); BIP-93 requires 's' for threshold=0",
82                    got
83                )
84            }
85            Error::TagInvalidAlphabet { got } => {
86                write!(f, "tag bytes not in codex32 alphabet: {:?}", got)
87            }
88            Error::UnknownTag { got } => write!(
89                f,
90                "unknown tag {:?}; not a member of RESERVED_TAG_TABLE",
91                std::str::from_utf8(got).unwrap_or("<non-utf8>")
92            ),
93            Error::ReservedTagNotEmittedInV01 { got } => write!(
94                f,
95                "tag {:?} reserved-not-emitted in v0.1; deferred to v0.2+",
96                std::str::from_utf8(got).unwrap_or("<non-utf8>")
97            ),
98            Error::ReservedPrefixViolation { got } => {
99                write!(f, "reserved-prefix byte was 0x{:02x}, expected 0x00", got)
100            }
101            Error::UnexpectedStringLength { got, allowed } => {
102                write!(f, "string length {} outside v0.1 set {:?}", got, allowed)
103            }
104            Error::PayloadLengthMismatch { tag, expected, got } => write!(
105                f,
106                "tag {:?} payload length {} not in expected set {:?}",
107                std::str::from_utf8(tag).unwrap_or("<non-utf8>"),
108                got,
109                expected
110            ),
111        }
112    }
113}
114
115impl std::error::Error for Error {
116    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
117        // codex32::Error doesn't impl std::error::Error in v0.1.0; chain stops here.
118        None
119    }
120}
121
122impl From<codex32::Error> for Error {
123    fn from(e: codex32::Error) -> Self {
124        Error::Codex32(e)
125    }
126}
127
128/// Result alias for ms-codec.
129pub type Result<T> = std::result::Result<T, Error>;