Skip to main content

rain_metadata/meta/
normalize.rs

1use super::{
2    KnownMeta,
3    super::error::Error,
4    types::{
5        authoring::v1::AuthoringMeta, solidity_abi::v2::SolidityAbiMeta,
6        interpreter_caller::v1::InterpreterCallerMeta,
7    },
8};
9
10fn normalize_json<'de, T>(data: &'de [u8]) -> Result<Vec<u8>, Error>
11where
12    T: serde::Deserialize<'de> + serde::Serialize + validator::Validate,
13{
14    let parsed = serde_json::from_str::<T>(std::str::from_utf8(data)?)?;
15    parsed.validate()?;
16    Ok(serde_json::to_string(&parsed)?.as_bytes().to_vec())
17}
18
19impl KnownMeta {
20    /// normalizes meta types and also performs validation on those that need validation
21    pub fn normalize(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
22        Ok(match self {
23            KnownMeta::SolidityAbiV2 => normalize_json::<SolidityAbiMeta>(data)?,
24            KnownMeta::InterpreterCallerMetaV1 => normalize_json::<InterpreterCallerMeta>(data)?,
25            KnownMeta::AuthoringMetaV1 => {
26                // for AuthoringMeta since it can be a json or abi encoded bytes, we try to abi
27                // decode first and then json deserialize if that fails, if either succeeds
28                // then the result of that will be abi encoded with validation
29                match AuthoringMeta::abi_decode(data) {
30                    Ok(am) => am.abi_encode_validate()?,
31                    _ => AuthoringMeta::abi_encode_validate(
32                        &serde_json::from_str::<AuthoringMeta>(std::str::from_utf8(data)?)?,
33                    )?,
34                }
35            }
36            // rest of meta types are only pure bytes (ut8 strings or binary)
37            // so no normalization/validation can happen for them at this level
38            _ => data.to_vec(),
39        })
40    }
41}
42
43#[cfg(all(test, not(target_family = "wasm")))]
44mod tests {
45    use crate::error::Error;
46    use crate::meta::types::authoring::v1::{AuthoringMeta, AuthoringMetaItem};
47    use crate::meta::KnownMeta;
48
49    /// OpV1 is a known meta this crate does not model, so normalize passes
50    /// its bytes through rather than validating them. It reaches the same
51    /// fallthrough as every other unmodelled type.
52    #[test]
53    fn test_normalize_op_v1_is_a_passthrough() {
54        let bytes = b"{  \"name\" : \"add\" }";
55        assert_eq!(KnownMeta::OpV1.normalize(bytes).unwrap(), bytes.to_vec());
56    }
57
58    /// SolidityAbiV2 rejects data that is not json and data that is not utf8.
59    #[test]
60    fn test_normalize_solidity_abi_v2_rejects_bad_input() {
61        assert!(matches!(
62            KnownMeta::SolidityAbiV2.normalize(b"not json at all"),
63            Err(Error::SerdeJsonError(_))
64        ));
65        assert!(matches!(
66            KnownMeta::SolidityAbiV2.normalize(&[0xff, 0xfe]),
67            Err(Error::Utf8Error(_))
68        ));
69    }
70
71    /// SolidityAbiV2 normalizes whitespace away to the canonical compact form.
72    #[test]
73    fn test_normalize_solidity_abi_v2_canonicalizes() {
74        assert_eq!(
75            KnownMeta::SolidityAbiV2.normalize(b"[ ]").unwrap(),
76            b"[]".to_vec()
77        );
78    }
79
80    /// InterpreterCallerMetaV1 parses but rejects metadata failing validation:
81    /// at least one method is required.
82    #[test]
83    fn test_normalize_interpreter_caller_rejects_empty_methods() {
84        let invalid = br#"{"name":"Test Caller","abiName":"TestCaller","methods":[]}"#;
85        assert!(matches!(
86            KnownMeta::InterpreterCallerMetaV1.normalize(invalid),
87            Err(Error::ValidationErrors(_))
88        ));
89    }
90
91    /// Meta types with no json schema at this level pass through untouched.
92    #[test]
93    fn test_normalize_passthrough_for_binary_metas() {
94        let data = vec![0x00, 0x01, 0xff];
95        assert_eq!(
96            KnownMeta::ExpressionDeployerV2BytecodeV1
97                .normalize(&data)
98                .unwrap(),
99            data
100        );
101    }
102
103    fn sample_authoring_meta() -> AuthoringMeta {
104        serde_json::from_str(
105            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
106        )
107        .unwrap()
108    }
109
110    /// Valid abi encoded input takes the abi-decode path and is re-encoded
111    /// with validation, byte identically.
112    #[test]
113    fn test_normalize_authoring_meta_v1_abi_path() {
114        let authoring_meta = sample_authoring_meta();
115        let abi = authoring_meta.abi_encode_validate().unwrap();
116        let normalized = KnownMeta::AuthoringMetaV1.normalize(&abi).unwrap();
117        assert_eq!(normalized, abi);
118    }
119
120    /// Abi-decodable input that fails validation must be rejected: the abi
121    /// path re-encodes via abi_encode_validate, not a passthrough.
122    #[test]
123    fn test_normalize_authoring_meta_v1_abi_invalid_rejected() {
124        let invalid = AuthoringMeta(vec![AuthoringMetaItem {
125            word: "NOTKEBAB".to_string(),
126            operand_parser_offset: 0,
127            description: "some description".to_string(),
128        }]);
129        // encode WITHOUT validation so the bytes are decodable but invalid
130        let abi = invalid.abi_encode().unwrap();
131        let result = KnownMeta::AuthoringMetaV1.normalize(&abi);
132        assert!(matches!(result, Err(Error::ValidationErrors(_))));
133    }
134
135    /// Json input falls back to serde parse and is abi encoded with
136    /// validation: output is the abi encoding, not the raw json bytes.
137    #[test]
138    fn test_normalize_authoring_meta_v1_json_fallback() {
139        let json = r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#;
140        let expected = sample_authoring_meta().abi_encode_validate().unwrap();
141        let normalized = KnownMeta::AuthoringMetaV1
142            .normalize(json.as_bytes())
143            .unwrap();
144        assert_eq!(normalized, expected);
145        assert_ne!(normalized, json.as_bytes().to_vec());
146    }
147
148    /// Meta types without a structured normal form pass raw bytes through
149    /// unchanged.
150    #[test]
151    fn test_normalize_default_arm_passthrough() {
152        let data = b"some dotrain text".to_vec();
153        assert_eq!(KnownMeta::DotrainV1.normalize(&data).unwrap(), data);
154        let binary = vec![0xffu8, 0x00, 0x01];
155        assert_eq!(KnownMeta::RainlangV1.normalize(&binary).unwrap(), binary);
156    }
157}