@@ -10,6 +10,7 @@ use std::ops::RangeInclusive;
1010use derive_where:: derive_where;
1111use intmap:: IntMap ;
1212use itertools:: Itertools as _;
13+ use unicode_segmentation:: UnicodeSegmentation as _;
1314
1415use crate :: backup:: TryIntoWith ;
1516use crate :: backup:: chat:: reactions:: { ReactionError , ReactionSet } ;
@@ -69,8 +70,8 @@ pub enum PollError {
6970 UnknownVoterId ,
7071 /// voter id is not self nor contact
7172 InvalidVoterId ,
72- /// poll option size ({0 }) is out of bounds
73- InvalidPollStringSize ( usize ) ,
73+ /// {0} size ({1 }) is out of bounds
74+ InvalidPollStringSize ( & ' static str , usize ) ,
7475 /// {0} option(s) is too few for a poll
7576 TooFewOptions ( usize ) ,
7677 /// {0}
@@ -123,7 +124,7 @@ impl<R: Clone, C: LookupPair<RecipientId, MinimalRecipientData, R> + ReportUnusu
123124 votes,
124125 special_fields : _,
125126 } = self ;
126- validate_poll_string_len ( & option) ?;
127+ validate_poll_string_len ( & option, "poll option" ) ?;
127128 validate_unique_voters ( & votes) ?;
128129 let votes = votes
129130 . into_iter ( )
@@ -148,7 +149,7 @@ impl<R: Clone, C: LookupPair<RecipientId, MinimalRecipientData, R> + ReportUnusu
148149 reactions,
149150 special_fields : _,
150151 } = self ;
151- validate_poll_string_len ( & question) ?;
152+ validate_poll_string_len ( & question, "poll question" ) ?;
152153 if options. len ( ) < MIN_POLL_OPTIONS {
153154 return Err ( Self :: Error :: TooFewOptions ( options. len ( ) ) ) ;
154155 }
@@ -177,7 +178,7 @@ impl<C: ReportUnusualTimestamp> TryIntoWith<PollTerminate, C> for PollTerminateP
177178 question,
178179 special_fields : _,
179180 } = self ;
180- validate_poll_string_len ( & question) ?;
181+ validate_poll_string_len ( & question, "poll question" ) ?;
181182 let target_sent_timestamp = Timestamp :: from_millis (
182183 targetSentTimestamp,
183184 "PollTerminateUpdate.targetSentTimestamp" ,
@@ -190,10 +191,10 @@ impl<C: ReportUnusualTimestamp> TryIntoWith<PollTerminate, C> for PollTerminateP
190191 }
191192}
192193
193- fn validate_poll_string_len ( s : & str ) -> Result < ( ) , PollError > {
194- let len = s. len ( ) ;
194+ fn validate_poll_string_len ( s : & str , description : & ' static str ) -> Result < ( ) , PollError > {
195+ let len = s. graphemes ( true ) . count ( ) ;
195196 if !POLL_STRING_LENGTH_RANGE . contains ( & len) {
196- return Err ( PollError :: InvalidPollStringSize ( len) ) ;
197+ return Err ( PollError :: InvalidPollStringSize ( description , len) ) ;
197198 }
198199 Ok ( ( ) )
199200}
@@ -270,10 +271,11 @@ mod test {
270271
271272 #[ test_case( "a" => Ok ( ( ) ) ; "lower bound" ) ]
272273 #[ test_case( & format!( "{:0100}" , 0 ) => Ok ( ( ) ) ; "upper bound" ) ]
273- #[ test_case( "" => Err ( PollError :: InvalidPollStringSize ( 0 ) ) ; "too short" ) ]
274- #[ test_case( & format!( "{:0101}" , 0 ) => Err ( PollError :: InvalidPollStringSize ( 101 ) ) ; "too long" ) ]
274+ #[ test_case( "" => Err ( PollError :: InvalidPollStringSize ( "test string" , 0 ) ) ; "too short" ) ]
275+ #[ test_case( & format!( "{:0101}" , 0 ) => Err ( PollError :: InvalidPollStringSize ( "test string" , 101 ) ) ; "too long" ) ]
276+ #[ test_case( "🧑🧑🧒🧒🧑🧑🧒🧒🧑🧑🧒🧒🧑🧑🧒🧒🧑🧑🧒🧒" => Ok ( ( ) ) ; "grapheme clusters" ) ]
275277 fn length_check ( s : & str ) -> Result < ( ) , PollError > {
276- validate_poll_string_len ( s)
278+ validate_poll_string_len ( s, "test string" )
277279 }
278280
279281 fn poll_option_proto ( option : & str ) -> PollOptionProto {
@@ -361,10 +363,10 @@ mod test {
361363 #[ test_case( |x| x. options = vec![ ] => Err ( PollError :: TooFewOptions ( 0 ) ) ; "not an option" ) ]
362364 #[ test_case( |x| x. options. truncate( 1 ) => Err ( PollError :: TooFewOptions ( 1 ) ) ; "but one option" ) ]
363365 #[ test_case( |x| x. options. truncate( 2 ) => Ok ( ( ) ) ; "barely enough choice" ) ]
364- #[ test_case( |x| x. question = "" . to_string( ) => Err ( PollError :: InvalidPollStringSize ( 0 ) ) ; "empty question" ) ]
366+ #[ test_case( |x| x. question = "" . to_string( ) => Err ( PollError :: InvalidPollStringSize ( "poll question" , 0 ) ) ; "empty question" ) ]
365367 #[ test_case( |x| x. question = "a" . to_string( ) => Ok ( ( ) ) ; "question len lower bound" ) ]
366368 #[ test_case( |x| x. question = format!( "{:0100}" , 0 ) => Ok ( ( ) ) ; "question len upper bound" ) ]
367- #[ test_case( |x| x. question = format!( "{:0101}" , 0 ) => Err ( PollError :: InvalidPollStringSize ( 101 ) ) ; "question too long" ) ]
369+ #[ test_case( |x| x. question = format!( "{:0101}" , 0 ) => Err ( PollError :: InvalidPollStringSize ( "poll question" , 101 ) ) ; "question too long" ) ]
368370 #[ test_case( |x| x. reactions. clear( ) => Ok ( ( ) ) ; "no reactions" ) ]
369371 #[ test_case( |x| x. reactions. push( ReactionProto :: default ( ) ) => Err ( PollError :: Reaction ( ReactionError :: EmptyEmoji ) ) ; "invalid reaction" ) ]
370372 fn poll ( modify : fn ( & mut PollProto ) ) -> Result < ( ) , PollError > {
@@ -401,10 +403,10 @@ mod test {
401403 #[ test_case( |_| { } => Ok ( ( ) ) ; "happy path" ) ]
402404 #[ test_case( |x| x. targetSentTimestamp = Timestamp :: MAX_SAFE_TIMESTAMP_MS + 1 =>
403405 Err ( PollError :: InvalidTimestamp ( TimestampError ( "PollTerminateUpdate.targetSentTimestamp" , Timestamp :: MAX_SAFE_TIMESTAMP_MS + 1 ) ) ) ; "bad timestamp" ) ]
404- #[ test_case( |x| x. question = "" . to_string( ) => Err ( PollError :: InvalidPollStringSize ( 0 ) ) ; "empty question" ) ]
406+ #[ test_case( |x| x. question = "" . to_string( ) => Err ( PollError :: InvalidPollStringSize ( "poll question" , 0 ) ) ; "empty question" ) ]
405407 #[ test_case( |x| x. question = "a" . to_string( ) => Ok ( ( ) ) ; "question len lower bound" ) ]
406408 #[ test_case( |x| x. question = format!( "{:0100}" , 0 ) => Ok ( ( ) ) ; "question len upper bound" ) ]
407- #[ test_case( |x| x. question = format!( "{:0101}" , 0 ) => Err ( PollError :: InvalidPollStringSize ( 101 ) ) ; "question too long" ) ]
409+ #[ test_case( |x| x. question = format!( "{:0101}" , 0 ) => Err ( PollError :: InvalidPollStringSize ( "poll question" , 101 ) ) ; "question too long" ) ]
408410 fn poll_terminate ( modify : fn ( & mut PollTerminateProto ) ) -> Result < ( ) , PollError > {
409411 let mut terminate = poll_terminate_proto ( ) ;
410412 modify ( & mut terminate) ;
0 commit comments