cosmwasm_std/
metadata.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4use crate::prelude::*;
5
6/// Replicates the cosmos-sdk bank module Metadata type
7#[derive(
8    Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
9)]
10pub struct DenomMetadata {
11    pub description: String,
12    #[serde(deserialize_with = "deserialize_null_default")]
13    pub denom_units: Vec<DenomUnit>,
14    pub base: String,
15    pub display: String,
16    pub name: String,
17    pub symbol: String,
18    pub uri: String,
19    pub uri_hash: String,
20}
21
22/// Replicates the cosmos-sdk bank module DenomUnit type
23#[derive(
24    Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
25)]
26pub struct DenomUnit {
27    pub denom: String,
28    pub exponent: u32,
29    #[serde(deserialize_with = "deserialize_null_default")]
30    pub aliases: Vec<String>,
31}
32
33// Deserialize a field that is null, defaulting to the type's default value.
34// Panic if the field is missing.
35fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
36where
37    T: Default + Deserialize<'de>,
38    D: Deserializer<'de>,
39{
40    let opt = Option::deserialize(deserializer)?;
41    Ok(opt.unwrap_or_default())
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::{DenomMetadata, DenomUnit};
48    use serde_json::{json, Error};
49
50    #[test]
51    fn deserialize_denom_metadata_with_null_fields_works() {
52        // Test case with null denom_units - should deserialize as empty vec
53        let json_with_null_denom_units = json!({
54            "description": "Test Token",
55            "denom_units": null,
56            "base": "utest",
57            "display": "TEST",
58            "name": "Test Token",
59            "symbol": "TEST",
60            "uri": "https://test.com",
61            "uri_hash": "hash"
62        });
63
64        let metadata_null_denom_units: DenomMetadata =
65            serde_json::from_value(json_with_null_denom_units).unwrap();
66        assert_eq!(
67            metadata_null_denom_units.denom_units,
68            Vec::<DenomUnit>::new()
69        );
70
71        // Test normal case with provided denom_units
72        let json_with_units = json!({
73            "description": "Test Token",
74            "denom_units": [
75                {
76                    "denom": "utest",
77                    "exponent": 6,
78                    "aliases": ["microtest"]
79                }
80            ],
81            "base": "utest",
82            "display": "TEST",
83            "name": "Test Token",
84            "symbol": "TEST",
85            "uri": "https://test.com",
86            "uri_hash": "hash"
87        });
88
89        let metadata_with_units: DenomMetadata = serde_json::from_value(json_with_units).unwrap();
90        assert_eq!(metadata_with_units.denom_units.len(), 1);
91        assert_eq!(metadata_with_units.denom_units[0].denom, "utest");
92
93        // Test with null aliases inside denom_units - should deserialize as empty vec
94        let json_with_null_aliases = json!({
95            "description": "Test Token",
96            "denom_units": [
97                {
98                    "denom": "utest",
99                    "exponent": 6,
100                    "aliases": null
101                }
102            ],
103            "base": "utest",
104            "display": "TEST",
105            "name": "Test Token",
106            "symbol": "TEST",
107            "uri": "https://test.com",
108            "uri_hash": "hash"
109        });
110
111        let metadata_with_null_aliases: DenomMetadata =
112            serde_json::from_value(json_with_null_aliases).unwrap();
113        assert_eq!(metadata_with_null_aliases.denom_units.len(), 1);
114        assert_eq!(
115            metadata_with_null_aliases.denom_units[0].aliases,
116            Vec::<String>::new()
117        );
118    }
119
120    #[test]
121    fn deserialize_denom_metadata_with_missing_fields_fails() {
122        // Missing denom_units should be treated like null
123        let json_missing_denom_units = json!({
124            "description": "Test Token",
125            "base": "utest",
126            "display": "TEST",
127            "name": "Test Token",
128            "symbol": "TEST",
129            "uri": "https://test.com",
130            "uri_hash": "hash"
131        });
132
133        let metadata: Result<DenomMetadata, Error> =
134            serde_json::from_value(json_missing_denom_units);
135        assert!(metadata.is_err());
136
137        let json_missing_alias = json!({
138            "description": "Test Token",
139            "base": "utest",
140            "denom_units": [
141                {
142                    "denom": "utest",
143                    "exponent": 6,
144                }
145            ],
146            "display": "TEST",
147            "name": "Test Token",
148            "symbol": "TEST",
149            "uri": "https://test.com",
150            "uri_hash": "hash"
151        });
152
153        let metadata_missing_alias: Result<DenomMetadata, Error> =
154            serde_json::from_value(json_missing_alias);
155        assert!(metadata_missing_alias.is_err());
156    }
157
158    #[test]
159    fn query_denom_metadata_with_null_denom_units_works() {
160        // Test case with null denom_units - should deserialize as empty vec
161        let json_with_null_denom_units = json!({
162            "description": "Test Token",
163            "denom_units": null,
164            "base": "utest",
165            "display": "TEST",
166            "name": "Test Token",
167            "symbol": "TEST",
168            "uri": "https://test.com",
169            "uri_hash": "hash"
170        });
171
172        let metadata_with_null_denom_units: DenomMetadata =
173            serde_json::from_value(json_with_null_denom_units).unwrap();
174        assert_eq!(
175            metadata_with_null_denom_units.denom_units,
176            Vec::<DenomUnit>::new()
177        );
178
179        // Test normal case with provided denom_units
180        let json_with_units = json!({
181            "description": "Test Token",
182            "denom_units": [
183                {
184                    "denom": "utest",
185                    "exponent": 6,
186                    "aliases": ["microtest"]
187                }
188            ],
189            "base": "utest",
190            "display": "TEST",
191            "name": "Test Token",
192            "symbol": "TEST",
193            "uri": "https://test.com",
194            "uri_hash": "hash"
195        });
196
197        let metadata_with_units: DenomMetadata = serde_json::from_value(json_with_units).unwrap();
198        assert_eq!(metadata_with_units.denom_units.len(), 1);
199        assert_eq!(metadata_with_units.denom_units[0].denom, "utest");
200        assert_eq!(metadata_with_units.denom_units[0].aliases.len(), 1);
201        assert_eq!(metadata_with_units.denom_units[0].aliases[0], "microtest");
202
203        // Test with null aliases inside denom_units - should deserialize as empty vec
204        let json_with_null_aliases = json!({
205            "description": "Test Token",
206            "denom_units": [
207                {
208                    "denom": "utest",
209                    "exponent": 6,
210                    "aliases": null
211                }
212            ],
213            "base": "utest",
214            "display": "TEST",
215            "name": "Test Token",
216            "symbol": "TEST",
217            "uri": "https://test.com",
218            "uri_hash": "hash"
219        });
220
221        let metadata_with_null_aliases: DenomMetadata =
222            serde_json::from_value(json_with_null_aliases).unwrap();
223        assert_eq!(metadata_with_null_aliases.denom_units.len(), 1);
224        assert_eq!(
225            metadata_with_null_aliases.denom_units[0].aliases,
226            Vec::<String>::new()
227        );
228    }
229
230    #[test]
231    fn query_denom_metadata_with_missing_fields_fails() {
232        // Missing denom_units should throw an error
233        let json_missing_denom_units = json!({
234            "description": "Test Token",
235            "base": "utest",
236            "display": "TEST",
237            "name": "Test Token",
238            "symbol": "TEST",
239            "uri": "https://test.com",
240            "uri_hash": "hash"
241        });
242
243        let json_missing_denom_units_metadata: Result<DenomMetadata, Error> =
244            serde_json::from_value(json_missing_denom_units);
245        assert!(json_missing_denom_units_metadata.is_err());
246
247        // Missing aliases field should throw an error
248        let json_missing_aliases = json!({
249            "description": "Test Token",
250            "denom_units": [
251                {
252                    "denom": "utest",
253                    "exponent": 6
254                }
255            ],
256            "base": "utest",
257            "display": "TEST",
258            "name": "Test Token",
259            "symbol": "TEST",
260            "uri": "https://test.com",
261            "uri_hash": "hash"
262        });
263
264        let missing_aliases_metadata: Result<DenomMetadata, Error> =
265            serde_json::from_value(json_missing_aliases);
266        assert!(missing_aliases_metadata.is_err());
267    }
268
269    #[test]
270    fn query_denom_metadata_with_mixed_null_and_value_works() {
271        // Test with multiple denom units, some with null aliases and some with values
272        let mixed_json = json!({
273            "description": "Mixed Token",
274            "denom_units": [
275                {
276                    "denom": "unit1",
277                    "exponent": 0,
278                    "aliases": null
279                },
280                {
281                    "denom": "unit2",
282                    "exponent": 6,
283                    "aliases": ["microunit", "u"]
284                },
285                {
286                    "denom": "unit3",
287                    "exponent": 9,
288                    "aliases": []
289                }
290            ],
291            "base": "unit1",
292            "display": "MIXED",
293            "name": "Mixed Token",
294            "symbol": "MIX",
295            "uri": "https://mixed.token",
296            "uri_hash": "hash123"
297        });
298
299        let metadata: DenomMetadata = serde_json::from_value(mixed_json).unwrap();
300
301        // First denom unit has null aliases, should be empty vec
302        assert!(metadata.denom_units[0].aliases.is_empty());
303
304        // Second has two aliases
305        assert_eq!(metadata.denom_units[1].aliases.len(), 2);
306        assert_eq!(metadata.denom_units[1].aliases[0], "microunit");
307        assert_eq!(metadata.denom_units[1].aliases[1], "u");
308
309        // Third has explicitly empty aliases
310        assert!(metadata.denom_units[2].aliases.is_empty());
311    }
312}