1+ use std:: ops:: Deref ;
2+
13use crate :: address:: utxolib_compat:: { CashAddr , UtxolibNetwork } ;
24use crate :: error:: WasmUtxoError ;
35use wasm_bindgen:: JsValue ;
46
5- pub ( crate ) trait TryFromJsValue {
6- fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError >
7- where
8- Self : Sized ;
7+ // =============================================================================
8+ // TryFromJsValue trait
9+ // =============================================================================
10+
11+ /// Trait for converting JsValue to Rust types
12+ pub ( crate ) trait TryFromJsValue : Sized {
13+ fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > ;
14+ }
15+
16+ // =============================================================================
17+ // Bytes<N>: Fixed-size byte array wrapper
18+ // =============================================================================
19+
20+ /// Fixed-size byte array that implements TryFromJsValue
21+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
22+ pub ( crate ) struct Bytes < const N : usize > ( pub [ u8 ; N ] ) ;
23+
24+ impl < const N : usize > Deref for Bytes < N > {
25+ type Target = [ u8 ; N ] ;
26+ fn deref ( & self ) -> & Self :: Target {
27+ & self . 0
28+ }
29+ }
30+
31+ impl < const N : usize > AsRef < [ u8 ] > for Bytes < N > {
32+ fn as_ref ( & self ) -> & [ u8 ] {
33+ & self . 0
34+ }
35+ }
36+
37+ impl < const N : usize > From < Bytes < N > > for [ u8 ; N ] {
38+ fn from ( bytes : Bytes < N > ) -> Self {
39+ bytes. 0
40+ }
41+ }
42+
43+ impl < const N : usize > TryFromJsValue for Bytes < N > {
44+ fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
45+ let buffer = js_sys:: Uint8Array :: new ( value) ;
46+ if buffer. length ( ) as usize != N {
47+ return Err ( WasmUtxoError :: new ( & format ! (
48+ "Expected {} bytes, got {}" ,
49+ N ,
50+ buffer. length( )
51+ ) ) ) ;
52+ }
53+ let mut bytes = [ 0u8 ; N ] ;
54+ buffer. copy_to ( & mut bytes) ;
55+ Ok ( Bytes ( bytes) )
56+ }
957}
1058
11- // Implement TryFromJsValue for primitive types
59+ // =============================================================================
60+ // TryFromJsValue implementations for primitive types
61+ // =============================================================================
1262
1363impl TryFromJsValue for String {
1464 fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
@@ -36,6 +86,15 @@ impl TryFromJsValue for u32 {
3686 }
3787}
3888
89+ impl TryFromJsValue for Vec < u8 > {
90+ fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
91+ let buffer = js_sys:: Uint8Array :: new ( value) ;
92+ let mut bytes = vec ! [ 0u8 ; buffer. length( ) as usize ] ;
93+ buffer. copy_to ( & mut bytes) ;
94+ Ok ( bytes)
95+ }
96+ }
97+
3998impl < T : TryFromJsValue > TryFromJsValue for Option < T > {
4099 fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
41100 if value. is_undefined ( ) || value. is_null ( ) {
@@ -46,157 +105,72 @@ impl<T: TryFromJsValue> TryFromJsValue for Option<T> {
46105 }
47106}
48107
49- // Helper function to get a field from an object and convert it using TryFromJsValue
50- pub ( crate ) fn get_field < T : TryFromJsValue > ( obj : & JsValue , key : & str ) -> Result < T , WasmUtxoError > {
51- let field_value = js_sys:: Reflect :: get ( obj, & JsValue :: from_str ( key) )
52- . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , key) ) ) ?;
108+ // =============================================================================
109+ // Field access functions
110+ // =============================================================================
53111
54- T :: try_from_js_value ( & field_value)
55- . map_err ( |e| WasmUtxoError :: new ( & format ! ( "{} (field: {})" , e, key) ) )
112+ /// Get a raw JsValue field from an object without conversion
113+ fn get_raw_field ( obj : & JsValue , key : & str ) -> Result < JsValue , WasmUtxoError > {
114+ js_sys:: Reflect :: get ( obj, & JsValue :: from_str ( key) )
115+ . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , key) ) )
56116}
57117
58- // Helper function to get an optional field (returns None if undefined/null)
59- #[ allow( dead_code) ]
60- pub ( crate ) fn get_optional_field < T : TryFromJsValue > (
61- obj : & JsValue ,
62- key : & str ,
63- ) -> Result < Option < T > , WasmUtxoError > {
64- let field_value = js_sys:: Reflect :: get ( obj, & JsValue :: from_str ( key) )
65- . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , key) ) ) ?;
66-
67- if field_value. is_undefined ( ) || field_value. is_null ( ) {
68- Ok ( None )
69- } else {
70- T :: try_from_js_value ( & field_value)
71- . map ( Some )
72- . map_err ( |e| WasmUtxoError :: new ( & format ! ( "{} (field: {})" , e, key) ) )
73- }
118+ /// Navigate to a nested object using dot notation (e.g., "network.bip32")
119+ fn get_nested_raw ( obj : & JsValue , path : & str ) -> Result < JsValue , WasmUtxoError > {
120+ path. split ( '.' ) . try_fold ( obj. clone ( ) , |current, part| {
121+ js_sys:: Reflect :: get ( & current, & JsValue :: from_str ( part) )
122+ . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , part) ) )
123+ } )
124+ }
125+
126+ /// Get a field and convert it using TryFromJsValue
127+ pub ( crate ) fn get_field < T : TryFromJsValue > ( obj : & JsValue , key : & str ) -> Result < T , WasmUtxoError > {
128+ let field_value = get_raw_field ( obj, key) ?;
129+ T :: try_from_js_value ( & field_value)
130+ . map_err ( |e| WasmUtxoError :: new ( & format ! ( "{} (field: {})" , e, key) ) )
74131}
75132
76- // Helper function to get a nested field using dot notation (e.g., "network.bip32.public")
133+ /// Get a nested field using dot notation (e.g., "network.bip32.public")
77134pub ( crate ) fn get_nested_field < T : TryFromJsValue > (
78135 obj : & JsValue ,
79136 path : & str ,
80137) -> Result < T , WasmUtxoError > {
81- let parts: Vec < & str > = path. split ( '.' ) . collect ( ) ;
82- let mut current = obj. clone ( ) ;
83-
84- for ( i, part) in parts. iter ( ) . enumerate ( ) {
85- if i == parts. len ( ) - 1 {
86- // Last part - extract and convert
87- return get_field ( & current, part) ;
88- } else {
89- // Intermediate part - just get the object
90- current = js_sys:: Reflect :: get ( & current, & JsValue :: from_str ( part) )
91- . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , part) ) ) ?;
92- }
93- }
94-
95- Err ( WasmUtxoError :: new ( "Empty path" ) )
96- }
97-
98- // Helper function to get a buffer field as a fixed-size byte array
99- pub ( crate ) fn get_buffer_field < const N : usize > (
100- obj : & JsValue ,
101- key : & str ,
102- ) -> Result < [ u8 ; N ] , WasmUtxoError > {
103- let field_value = js_sys:: Reflect :: get ( obj, & JsValue :: from_str ( key) )
104- . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , key) ) ) ?;
105-
106- let buffer = js_sys:: Uint8Array :: new ( & field_value) ;
107- if buffer. length ( ) as usize != N {
108- return Err ( WasmUtxoError :: new ( & format ! (
109- "{} must be {} bytes, got {}" ,
110- key,
111- N ,
112- buffer. length( )
113- ) ) ) ;
114- }
115-
116- let mut bytes = [ 0u8 ; N ] ;
117- buffer. copy_to ( & mut bytes) ;
118- Ok ( bytes)
119- }
120-
121- // Helper function to get a buffer field as a Vec
122- #[ allow( dead_code) ]
123- pub ( crate ) fn get_buffer_field_vec ( obj : & JsValue , key : & str ) -> Result < Vec < u8 > , WasmUtxoError > {
124- let field_value = js_sys:: Reflect :: get ( obj, & JsValue :: from_str ( key) )
125- . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , key) ) ) ?;
126-
127- let buffer = js_sys:: Uint8Array :: new ( & field_value) ;
128- let mut bytes = vec ! [ 0u8 ; buffer. length( ) as usize ] ;
129- buffer. copy_to ( & mut bytes) ;
130- Ok ( bytes)
138+ let field_value = get_nested_raw ( obj, path) ?;
139+ T :: try_from_js_value ( & field_value)
140+ . map_err ( |e| WasmUtxoError :: new ( & format ! ( "{} (path: {})" , e, path) ) )
131141}
132142
133- // Helper function to get an optional buffer field as a fixed-size byte array
134- pub ( crate ) fn get_optional_buffer_field < const N : usize > (
135- obj : & JsValue ,
136- key : & str ,
137- ) -> Result < Option < [ u8 ; N ] > , WasmUtxoError > {
138- let field_value = js_sys:: Reflect :: get ( obj, & JsValue :: from_str ( key) )
139- . map_err ( |_| WasmUtxoError :: new ( & format ! ( "Failed to read {} from object" , key) ) ) ?;
140-
141- if field_value. is_undefined ( ) || field_value. is_null ( ) {
142- return Ok ( None ) ;
143- }
144-
145- let buffer = js_sys:: Uint8Array :: new ( & field_value) ;
146- if buffer. length ( ) as usize != N {
147- return Err ( WasmUtxoError :: new ( & format ! (
148- "{} must be {} bytes, got {}" ,
149- key,
150- N ,
151- buffer. length( )
152- ) ) ) ;
153- }
154-
155- let mut bytes = [ 0u8 ; N ] ;
156- buffer. copy_to ( & mut bytes) ;
157- Ok ( Some ( bytes) )
158- }
143+ // =============================================================================
144+ // TryFromJsValue implementations for domain types
145+ // =============================================================================
159146
160147impl TryFromJsValue for UtxolibNetwork {
161148 fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
162- let pub_key_hash = get_field ( value, "pubKeyHash" ) ?;
163- let script_hash = get_field ( value, "scriptHash" ) ?;
164- let bech32 = get_field ( value, "bech32" ) ?;
165- let cash_addr = get_field ( value, "cashAddr" ) ?;
166-
167149 Ok ( UtxolibNetwork {
168- pub_key_hash,
169- script_hash,
170- cash_addr ,
171- bech32 ,
150+ pub_key_hash : get_field ( value , "pubKeyHash" ) ? ,
151+ script_hash : get_field ( value , "scriptHash" ) ? ,
152+ bech32 : get_field ( value , "bech32" ) ? ,
153+ cash_addr : get_field ( value , "cashAddr" ) ? ,
172154 } )
173155 }
174156}
175157
176158impl TryFromJsValue for CashAddr {
177159 fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
178- let prefix = get_field ( value, "prefix" ) ?;
179- let pub_key_hash = get_field ( value, "pubKeyHash" ) ?;
180- let script_hash = get_field ( value, "scriptHash" ) ?;
181-
182160 Ok ( CashAddr {
183- prefix,
184- pub_key_hash,
185- script_hash,
161+ prefix : get_field ( value , "prefix" ) ? ,
162+ pub_key_hash : get_field ( value , "pubKeyHash" ) ? ,
163+ script_hash : get_field ( value , "scriptHash" ) ? ,
186164 } )
187165 }
188166}
189167
190168impl TryFromJsValue for crate :: inscriptions:: TapLeafScript {
191169 fn try_from_js_value ( value : & JsValue ) -> Result < Self , WasmUtxoError > {
192- let leaf_version: u8 = get_field ( value, "leafVersion" ) ?;
193- let script = get_buffer_field_vec ( value, "script" ) ?;
194- let control_block = get_buffer_field_vec ( value, "controlBlock" ) ?;
195-
196170 Ok ( crate :: inscriptions:: TapLeafScript {
197- leaf_version,
198- script,
199- control_block,
171+ leaf_version : get_field ( value , "leafVersion" ) ? ,
172+ script : get_field ( value , "script" ) ? ,
173+ control_block : get_field ( value , "controlBlock" ) ? ,
200174 } )
201175 }
202176}
@@ -211,7 +185,8 @@ impl TryFromJsValue for crate::networks::Network {
211185 . or_else ( || crate :: networks:: Network :: from_coin_name ( & network_str) )
212186 . ok_or_else ( || {
213187 WasmUtxoError :: new ( & format ! (
214- "Unknown network '{}'. Expected a utxolib name (e.g., 'bitcoin', 'testnet') or coin name (e.g., 'btc', 'tbtc')" ,
188+ "Unknown network '{}'. Expected a utxolib name (e.g., 'bitcoin', 'testnet') \
189+ or coin name (e.g., 'btc', 'tbtc')",
215190 network_str
216191 ) )
217192 } )
0 commit comments