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