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