11use schemars:: JsonSchema ;
2- use serde:: { Deserialize , Serialize } ;
2+ use serde:: { Deserialize , Deserializer , Serialize } ;
33
44use crate :: prelude:: * ;
55
66/// Replicates the cosmos-sdk bank module Metadata type
77#[ derive( Serialize , Deserialize , Clone , Default , Debug , PartialEq , Eq , JsonSchema ) ]
88pub struct DenomMetadata {
99 pub description : String ,
10+ #[ serde( deserialize_with = "deserialize_null_default" ) ]
1011 pub denom_units : Vec < DenomUnit > ,
1112 pub base : String ,
1213 pub display : String ,
@@ -21,5 +22,287 @@ pub struct DenomMetadata {
2122pub struct DenomUnit {
2223 pub denom : String ,
2324 pub exponent : u32 ,
25+ #[ serde( deserialize_with = "deserialize_null_default" ) ]
2426 pub aliases : Vec < String > ,
2527}
28+
29+ // Deserialize a field that is null, defaulting to the type's default value.
30+ // Panic if the field is missing.
31+ fn deserialize_null_default < ' de , D , T > ( deserializer : D ) -> Result < T , D :: Error >
32+ where
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) ]
41+ mod 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+ }
0 commit comments