@@ -39,6 +39,13 @@ impl Display for ApiError {
3939 }
4040}
4141
42+ impl ApiError {
43+ /// Returns `true` if retrying the request may succeed. Delegates to [`FlussError::is_retriable`].
44+ pub fn is_retriable ( & self ) -> bool {
45+ FlussError :: for_code ( self . code ) . is_retriable ( )
46+ }
47+ }
48+
4249/// Fluss protocol errors. These errors are part of the client-server protocol.
4350/// The error codes cannot be changed, but the names can be.
4451///
@@ -172,6 +179,23 @@ impl FlussError {
172179 * self as i32
173180 }
174181
182+ pub fn is_retriable ( & self ) -> bool {
183+ matches ! (
184+ self ,
185+ FlussError :: NetworkException
186+ | FlussError :: CorruptMessage
187+ | FlussError :: SchemaNotExist
188+ | FlussError :: NotLeaderOrFollower
189+ | FlussError :: CorruptRecordException
190+ | FlussError :: UnknownTableOrBucketException
191+ | FlussError :: RequestTimeOut
192+ | FlussError :: StorageException
193+ | FlussError :: NotEnoughReplicasAfterAppendException
194+ | FlussError :: NotEnoughReplicasException
195+ | FlussError :: LeaderNotAvailableException
196+ )
197+ }
198+
175199 /// Returns a friendly description of the error.
176200 pub fn message ( & self ) -> & ' static str {
177201 match self {
@@ -403,4 +427,53 @@ mod tests {
403427 let fluss_error = FlussError :: from ( api_error) ;
404428 assert_eq ! ( fluss_error, FlussError :: TableNotExist ) ;
405429 }
430+
431+ #[ test]
432+ fn is_retriable_known_retriable_errors ( ) {
433+ let retriable = [
434+ FlussError :: NetworkException ,
435+ FlussError :: CorruptMessage ,
436+ FlussError :: SchemaNotExist ,
437+ FlussError :: NotLeaderOrFollower ,
438+ FlussError :: CorruptRecordException ,
439+ FlussError :: UnknownTableOrBucketException ,
440+ FlussError :: RequestTimeOut ,
441+ FlussError :: StorageException ,
442+ FlussError :: NotEnoughReplicasAfterAppendException ,
443+ FlussError :: NotEnoughReplicasException ,
444+ FlussError :: LeaderNotAvailableException ,
445+ ] ;
446+ for err in & retriable {
447+ assert ! ( err. is_retriable( ) , "{err:?} should be retriable" ) ;
448+ }
449+ }
450+
451+ #[ test]
452+ fn is_retriable_known_non_retriable_errors ( ) {
453+ let non_retriable = [
454+ FlussError :: UnknownServerError ,
455+ FlussError :: None ,
456+ FlussError :: TableNotExist ,
457+ FlussError :: AuthenticateException ,
458+ FlussError :: AuthorizationException ,
459+ FlussError :: RecordTooLargeException ,
460+ FlussError :: DeletionDisabledException ,
461+ FlussError :: InvalidCoordinatorException ,
462+ FlussError :: FencedLeaderEpochException ,
463+ FlussError :: FencedTieringEpochException ,
464+ FlussError :: RetriableAuthenticateException ,
465+ ] ;
466+ for err in & non_retriable {
467+ assert ! ( !err. is_retriable( ) , "{err:?} should not be retriable" ) ;
468+ }
469+ }
470+
471+ #[ test]
472+ fn api_error_is_retriable_delegates_to_fluss_error ( ) {
473+ let retriable_api = FlussError :: RequestTimeOut . to_api_error ( None ) ;
474+ assert ! ( retriable_api. is_retriable( ) ) ;
475+
476+ let permanent_api = FlussError :: TableNotExist . to_api_error ( None ) ;
477+ assert ! ( !permanent_api. is_retriable( ) ) ;
478+ }
406479}
0 commit comments