@@ -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,25 @@ 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 :: LogStorageException
189+ | FlussError :: KvStorageException
190+ | FlussError :: NotLeaderOrFollower
191+ | FlussError :: CorruptRecordException
192+ | FlussError :: UnknownTableOrBucketException
193+ | FlussError :: RequestTimeOut
194+ | FlussError :: StorageException
195+ | FlussError :: NotEnoughReplicasAfterAppendException
196+ | FlussError :: NotEnoughReplicasException
197+ | FlussError :: LeaderNotAvailableException
198+ )
199+ }
200+
175201 /// Returns a friendly description of the error.
176202 pub fn message ( & self ) -> & ' static str {
177203 match self {
@@ -403,4 +429,55 @@ mod tests {
403429 let fluss_error = FlussError :: from ( api_error) ;
404430 assert_eq ! ( fluss_error, FlussError :: TableNotExist ) ;
405431 }
432+
433+ #[ test]
434+ fn is_retriable_known_retriable_errors ( ) {
435+ let retriable = [
436+ FlussError :: NetworkException ,
437+ FlussError :: CorruptMessage ,
438+ FlussError :: SchemaNotExist ,
439+ FlussError :: LogStorageException ,
440+ FlussError :: KvStorageException ,
441+ FlussError :: NotLeaderOrFollower ,
442+ FlussError :: CorruptRecordException ,
443+ FlussError :: UnknownTableOrBucketException ,
444+ FlussError :: RequestTimeOut ,
445+ FlussError :: StorageException ,
446+ FlussError :: NotEnoughReplicasAfterAppendException ,
447+ FlussError :: NotEnoughReplicasException ,
448+ FlussError :: LeaderNotAvailableException ,
449+ ] ;
450+ for err in & retriable {
451+ assert ! ( err. is_retriable( ) , "{err:?} should be retriable" ) ;
452+ }
453+ }
454+
455+ #[ test]
456+ fn is_retriable_known_non_retriable_errors ( ) {
457+ let non_retriable = [
458+ FlussError :: UnknownServerError ,
459+ FlussError :: None ,
460+ FlussError :: TableNotExist ,
461+ FlussError :: AuthenticateException ,
462+ FlussError :: AuthorizationException ,
463+ FlussError :: RecordTooLargeException ,
464+ FlussError :: DeletionDisabledException ,
465+ FlussError :: InvalidCoordinatorException ,
466+ FlussError :: FencedLeaderEpochException ,
467+ FlussError :: FencedTieringEpochException ,
468+ FlussError :: RetriableAuthenticateException ,
469+ ] ;
470+ for err in & non_retriable {
471+ assert ! ( !err. is_retriable( ) , "{err:?} should not be retriable" ) ;
472+ }
473+ }
474+
475+ #[ test]
476+ fn api_error_is_retriable_delegates_to_fluss_error ( ) {
477+ let retriable_api = FlussError :: RequestTimeOut . to_api_error ( None ) ;
478+ assert ! ( retriable_api. is_retriable( ) ) ;
479+
480+ let permanent_api = FlussError :: TableNotExist . to_api_error ( None ) ;
481+ assert ! ( !permanent_api. is_retriable( ) ) ;
482+ }
406483}
0 commit comments