@@ -119,6 +119,80 @@ impl Hrp {
119
119
Ok ( new)
120
120
}
121
121
122
+ /// Parses the human-readable part from an object which can be formatted.
123
+ ///
124
+ /// The formatted form of the object is subject to all the same rules as [`Self::parse`].
125
+ /// This method is semantically equivalent to `Hrp::parse(&data.to_string())` but avoids
126
+ /// allocating an intermediate string.
127
+ pub fn parse_display < T : core:: fmt:: Display > ( data : T ) -> Result < Self , Error > {
128
+ use Error :: * ;
129
+
130
+ struct ByteFormatter {
131
+ arr : [ u8 ; MAX_HRP_LEN ] ,
132
+ index : usize ,
133
+ error : Option < Error > ,
134
+ }
135
+
136
+ impl core:: fmt:: Write for ByteFormatter {
137
+ fn write_str ( & mut self , s : & str ) -> fmt:: Result {
138
+ let mut has_lower: bool = false ;
139
+ let mut has_upper: bool = false ;
140
+ for ch in s. chars ( ) {
141
+ let b = ch as u8 ; // cast ok, `b` unused until `ch` is checked to be ASCII
142
+
143
+ // Break after finding an error so that we report the first invalid
144
+ // character, not the last.
145
+ if !ch. is_ascii ( ) {
146
+ self . error = Some ( Error :: NonAsciiChar ( ch) ) ;
147
+ break ;
148
+ } else if !( 33 ..=126 ) . contains ( & b) {
149
+ self . error = Some ( InvalidAsciiByte ( b) ) ;
150
+ break ;
151
+ }
152
+
153
+ if ch. is_ascii_lowercase ( ) {
154
+ if has_upper {
155
+ self . error = Some ( MixedCase ) ;
156
+ break ;
157
+ }
158
+ has_lower = true ;
159
+ } else if ch. is_ascii_uppercase ( ) {
160
+ if has_lower {
161
+ self . error = Some ( MixedCase ) ;
162
+ break ;
163
+ }
164
+ has_upper = true ;
165
+ } ;
166
+ }
167
+
168
+ // However, an invalid length error will take priority over an
169
+ // invalid character error.
170
+ if self . index + s. len ( ) > self . arr . len ( ) {
171
+ self . error = Some ( Error :: TooLong ( self . index + s. len ( ) ) ) ;
172
+ } else {
173
+ // Only do the actual copy if we passed the index check.
174
+ self . arr [ self . index ..self . index + s. len ( ) ] . copy_from_slice ( s. as_bytes ( ) ) ;
175
+ }
176
+
177
+ // Unconditionally update self.index so that in the case of a too-long
178
+ // string, our error return will reflect the full length.
179
+ self . index += s. len ( ) ;
180
+ Ok ( ( ) )
181
+ }
182
+ }
183
+
184
+ let mut byte_formatter = ByteFormatter { arr : [ 0 ; MAX_HRP_LEN ] , index : 0 , error : None } ;
185
+
186
+ write ! ( byte_formatter, "{}" , data) . expect ( "custom Formatter cannot fail" ) ;
187
+ if byte_formatter. index == 0 {
188
+ Err ( Empty )
189
+ } else if let Some ( err) = byte_formatter. error {
190
+ Err ( err)
191
+ } else {
192
+ Ok ( Self { buf : byte_formatter. arr , size : byte_formatter. index } )
193
+ }
194
+ }
195
+
122
196
/// Parses the human-readable part (see [`Hrp::parse`] for full docs).
123
197
///
124
198
/// Does not check that `hrp` is valid according to BIP-173 but does check for valid ASCII
@@ -424,6 +498,7 @@ mod tests {
424
498
#[ test]
425
499
fn $test_name( ) {
426
500
assert!( Hrp :: parse( $hrp) . is_ok( ) ) ;
501
+ assert!( Hrp :: parse_display( $hrp) . is_ok( ) ) ;
427
502
}
428
503
) *
429
504
}
@@ -445,6 +520,7 @@ mod tests {
445
520
#[ test]
446
521
fn $test_name( ) {
447
522
assert!( Hrp :: parse( $hrp) . is_err( ) ) ;
523
+ assert!( Hrp :: parse_display( $hrp) . is_err( ) ) ;
448
524
}
449
525
) *
450
526
}
@@ -538,4 +614,45 @@ mod tests {
538
614
let hrp = Hrp :: parse_unchecked ( s) ;
539
615
assert_eq ! ( hrp. as_bytes( ) , s. as_bytes( ) ) ;
540
616
}
617
+
618
+ #[ test]
619
+ fn parse_display ( ) {
620
+ let hrp = Hrp :: parse_display ( format_args ! ( "{}_{}" , 123 , "abc" ) ) . unwrap ( ) ;
621
+ assert_eq ! ( hrp. as_str( ) , "123_abc" ) ;
622
+
623
+ let hrp = Hrp :: parse_display ( format_args ! ( "{:083}" , 1 ) ) . unwrap ( ) ;
624
+ assert_eq ! (
625
+ hrp. as_str( ) ,
626
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000001"
627
+ ) ;
628
+
629
+ assert_eq ! ( Hrp :: parse_display( format_args!( "{:084}" , 1 ) ) , Err ( Error :: TooLong ( 84 ) ) , ) ;
630
+
631
+ assert_eq ! (
632
+ Hrp :: parse_display( format_args!( "{:83}" , 1 ) ) ,
633
+ Err ( Error :: InvalidAsciiByte ( b' ' ) ) ,
634
+ ) ;
635
+ }
636
+
637
+ #[ test]
638
+ fn parse_non_ascii ( ) {
639
+ assert_eq ! ( Hrp :: parse( "❤" ) . unwrap_err( ) , Error :: NonAsciiChar ( '❤' ) ) ;
640
+ }
641
+
642
+ #[ test]
643
+ fn parse_display_non_ascii ( ) {
644
+ assert_eq ! ( Hrp :: parse_display( "❤" ) . unwrap_err( ) , Error :: NonAsciiChar ( '❤' ) ) ;
645
+ }
646
+
647
+ #[ test]
648
+ fn parse_display_returns_first_error ( ) {
649
+ assert_eq ! ( Hrp :: parse_display( "❤ " ) . unwrap_err( ) , Error :: NonAsciiChar ( '❤' ) ) ;
650
+ }
651
+
652
+ // This test shows that the error does not contain heart.
653
+ #[ test]
654
+ fn parse_display_iterates_chars ( ) {
655
+ assert_eq ! ( Hrp :: parse_display( " ❤" ) . unwrap_err( ) , Error :: InvalidAsciiByte ( b' ' ) ) ;
656
+ assert_eq ! ( Hrp :: parse_display( "_❤" ) . unwrap_err( ) , Error :: NonAsciiChar ( '❤' ) ) ;
657
+ }
541
658
}
0 commit comments