Skip to main content

rain_metadata/error/
mod.rs

1use std::{string::FromUtf8Error, str::Utf8Error};
2
3use rain_metaboard_subgraph::metaboard_client::MetaboardSubgraphClientError;
4
5use crate::meta::KnownMagic;
6
7/// Covers all errors variants of Rain Metadat lib functionalities
8#[derive(Debug)]
9pub enum Error {
10    CorruptMeta,
11    NotRainMetaDocument,
12    InvalidHash,
13    UnknownMeta,
14    UnknownMagic,
15    NoRecordFound,
16    UnsupportedMeta,
17    BiggerThan32Bytes,
18    NulByteInInput,
19    InflateError(String),
20    InvalidInput(String),
21    InvalidUrl(String),
22    CorruptRecord(String),
23    SubgraphError(String),
24    Utf8Error(Utf8Error),
25    FromUtf8Error(FromUtf8Error),
26    ReqwestError(reqwest::Error),
27    SerdeCborError(serde_cbor::Error),
28    SerdeJsonError(serde_json::Error),
29    AbiCoderError(alloy::sol_types::Error),
30    ValidationErrors(validator::ValidationErrors),
31    DecodeHexStringError(alloy::primitives::hex::FromHexError),
32    InvalidMetaMagic(KnownMagic, KnownMagic),
33    MetaboardSubgraphClientError(MetaboardSubgraphClientError),
34}
35
36impl std::fmt::Display for Error {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Error::CorruptMeta => f.write_str("corrupt meta"),
40            Error::NotRainMetaDocument => f.write_str(
41                "not a rain meta document: the bytes do not begin with the rain meta magic number",
42            ),
43            Error::UnknownMeta => f.write_str("unknown meta"),
44            Error::UnknownMagic => f.write_str("unknown magic"),
45            Error::UnsupportedMeta => f.write_str("unsupported meta"),
46            Error::InvalidHash => f.write_str("invalid keccak256 hash"),
47            Error::NoRecordFound => f.write_str("found no matching record"),
48            Error::BiggerThan32Bytes => {
49                f.write_str("unexpected input size, must be 32 bytes or less")
50            }
51            Error::NulByteInInput => f.write_str("unexpected nul byte in input"),
52            Error::InvalidInput(v) => write!(f, "invalid input: {}", v),
53            Error::InvalidUrl(v) => write!(f, "invalid URL: {}", v),
54            Error::CorruptRecord(v) => write!(f, "corrupt record: {}", v),
55            Error::SubgraphError(v) => write!(f, "subgraph error: {}", v),
56            Error::ReqwestError(v) => write!(f, "{}", v),
57            Error::InflateError(v) => write!(f, "{}", v),
58            Error::Utf8Error(v) => write!(f, "{}", v),
59            Error::AbiCoderError(v) => write!(f, "{}", v),
60            Error::SerdeCborError(v) => write!(f, "{}", v),
61            Error::SerdeJsonError(v) => write!(f, "{}", v),
62            Error::FromUtf8Error(v) => write!(f, "{}", v),
63            Error::DecodeHexStringError(v) => write!(f, "{}", v),
64            Error::ValidationErrors(v) => write!(f, "{}", v),
65            Error::InvalidMetaMagic(expected, actual) => {
66                write!(
67                    f,
68                    "invalid meta magic: expected {:?}, got {:?}",
69                    expected, actual
70                )
71            }
72            Error::MetaboardSubgraphClientError(v) => write!(f, "{}", v),
73        }
74    }
75}
76
77impl std::error::Error for Error {}
78
79impl From<serde_json::Error> for Error {
80    fn from(value: serde_json::Error) -> Self {
81        Error::SerdeJsonError(value)
82    }
83}
84
85impl From<serde_cbor::Error> for Error {
86    fn from(value: serde_cbor::Error) -> Self {
87        Error::SerdeCborError(value)
88    }
89}
90
91impl From<FromUtf8Error> for Error {
92    fn from(value: FromUtf8Error) -> Self {
93        Error::FromUtf8Error(value)
94    }
95}
96
97impl From<Utf8Error> for Error {
98    fn from(value: Utf8Error) -> Self {
99        Error::Utf8Error(value)
100    }
101}
102
103impl From<validator::ValidationErrors> for Error {
104    fn from(value: validator::ValidationErrors) -> Self {
105        Error::ValidationErrors(value)
106    }
107}
108
109impl From<alloy::sol_types::Error> for Error {
110    fn from(value: alloy::sol_types::Error) -> Self {
111        Error::AbiCoderError(value)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    /// Fixed-string Display arms are part of the CLI's user-facing surface:
120    /// pin them exactly.
121    #[test]
122    fn test_display_fixed_strings() {
123        assert_eq!(Error::CorruptMeta.to_string(), "corrupt meta");
124        assert_eq!(
125            Error::NotRainMetaDocument.to_string(),
126            "not a rain meta document: the bytes do not begin with the rain meta magic number"
127        );
128        assert_eq!(Error::UnknownMeta.to_string(), "unknown meta");
129        assert_eq!(Error::UnknownMagic.to_string(), "unknown magic");
130        assert_eq!(Error::UnsupportedMeta.to_string(), "unsupported meta");
131        assert_eq!(Error::InvalidHash.to_string(), "invalid keccak256 hash");
132        assert_eq!(Error::NoRecordFound.to_string(), "found no matching record");
133        assert_eq!(
134            Error::BiggerThan32Bytes.to_string(),
135            "unexpected input size, must be 32 bytes or less"
136        );
137        assert_eq!(
138            Error::NulByteInInput.to_string(),
139            "unexpected nul byte in input"
140        );
141    }
142
143    /// Formatted wrappers carry the wrapped value in the rendered message.
144    #[test]
145    fn test_display_formatted_wrappers() {
146        assert_eq!(
147            Error::InvalidInput("abc".to_string()).to_string(),
148            "invalid input: abc"
149        );
150        assert_eq!(
151            Error::InvalidUrl("not-a-url".to_string()).to_string(),
152            "invalid URL: not-a-url"
153        );
154        assert_eq!(Error::InflateError("boom".to_string()).to_string(), "boom");
155        assert_eq!(
156            Error::CorruptRecord("bytecode is missing".to_string()).to_string(),
157            "corrupt record: bytecode is missing"
158        );
159        assert_eq!(
160            Error::SubgraphError("boom".to_string()).to_string(),
161            "subgraph error: boom"
162        );
163    }
164
165    /// InvalidMetaMagic renders expected first, actual second.
166    #[test]
167    fn test_display_invalid_meta_magic_field_order() {
168        let err = Error::InvalidMetaMagic(KnownMagic::RainMetaDocumentV1, KnownMagic::OpMetaV1);
169        assert_eq!(
170            err.to_string(),
171            "invalid meta magic: expected RainMetaDocumentV1, got OpMetaV1"
172        );
173    }
174
175    /// From impls must route each source error to its own variant, preserving
176    /// the source (observable through the rendered message).
177    #[test]
178    fn test_from_serde_json_routes_to_serde_json_error() {
179        let src = serde_json::from_str::<serde_json::Value>("{oops").unwrap_err();
180        let msg = src.to_string();
181        let err: Error = src.into();
182        match err {
183            Error::SerdeJsonError(e) => assert_eq!(e.to_string(), msg),
184            other => panic!("wrong variant: {:?}", other),
185        }
186    }
187
188    #[test]
189    fn test_from_utf8_error_routes_to_utf8_error() {
190        // The invalid byte is deliberate: it manufactures the source error.
191        #[allow(invalid_from_utf8)]
192        let src = std::str::from_utf8(&[0xff]).unwrap_err();
193        let err: Error = src.into();
194        match err {
195            Error::Utf8Error(e) => assert_eq!(e, src),
196            other => panic!("wrong variant: {:?}", other),
197        }
198    }
199
200    #[test]
201    fn test_from_from_utf8_error_routes_to_from_utf8_error() {
202        let src = String::from_utf8(vec![0xff]).unwrap_err();
203        let msg = src.to_string();
204        let err: Error = src.into();
205        match err {
206            Error::FromUtf8Error(e) => assert_eq!(e.to_string(), msg),
207            other => panic!("wrong variant: {:?}", other),
208        }
209    }
210}