@@ -119,6 +119,80 @@ impl Hrp {
119119 Ok ( new)
120120 }
121121
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+
122196 /// Parses the human-readable part (see [`Hrp::parse`] for full docs).
123197 ///
124198 /// Does not check that `hrp` is valid according to BIP-173 but does check for valid ASCII
@@ -424,6 +498,7 @@ mod tests {
424498 #[ test]
425499 fn $test_name( ) {
426500 assert!( Hrp :: parse( $hrp) . is_ok( ) ) ;
501+ assert!( Hrp :: parse_display( $hrp) . is_ok( ) ) ;
427502 }
428503 ) *
429504 }
@@ -445,6 +520,7 @@ mod tests {
445520 #[ test]
446521 fn $test_name( ) {
447522 assert!( Hrp :: parse( $hrp) . is_err( ) ) ;
523+ assert!( Hrp :: parse_display( $hrp) . is_err( ) ) ;
448524 }
449525 ) *
450526 }
@@ -538,4 +614,45 @@ mod tests {
538614 let hrp = Hrp :: parse_unchecked ( s) ;
539615 assert_eq ! ( hrp. as_bytes( ) , s. as_bytes( ) ) ;
540616 }
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+ }
541658}
0 commit comments