Repository: go-to-k/delstack
Pull request #119 (2023-03-05 12:50:38)

refactor: change retryer


Patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 95439e1..bd6da15 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,6 +13,7 @@
         "unshallow",
         "gomod",
         "retryable",
+        "retryer",
     ],
     "gopls": {
         "analyses": {
diff --git a/pkg/client/errors.go b/pkg/client/errors.go
index 120fa7a..751af4f 100644
--- a/pkg/client/errors.go
+++ b/pkg/client/errors.go
@@ -13,7 +13,7 @@ type ClientError struct {
 func (e *ClientError) Error() string {
 	var msg string
 	if e.ResourceName == nil {
-		msg = fmt.Sprintf("[no resource] %v", e.Err)
+		msg = fmt.Sprintf("[resource -] %v", e.Err)
 	} else {
 		msg = fmt.Sprintf("[resource %s] %v", *e.ResourceName, e.Err)
 	}
diff --git a/pkg/client/iam.go b/pkg/client/iam.go
index d416424..387069d 100644
--- a/pkg/client/iam.go
+++ b/pkg/client/iam.go
@@ -39,17 +39,11 @@ func (i *Iam) DeleteRole(ctx context.Context, roleName *string) error {
 	retryable := func(err error) bool {
 		return strings.Contains(err.Error(), "api error Throttling: Rate exceeded")
 	}
+	optFn := func(o *iam.Options) {
+		o.Retryer = NewRetryer(retryable, SleepTimeSecForIam)
+	}
 
-	_, err := Retry(
-		&RetryInput[iam.DeleteRoleInput, iam.DeleteRoleOutput, iam.Options]{
-			Ctx:              ctx,
-			SleepTimeSec:     SleepTimeSecForIam,
-			TargetResource:   roleName,
-			Input:            input,
-			ApiCaller:        i.client.DeleteRole,
-			RetryableChecker: retryable,
-		},
-	)
+	_, err := i.client.DeleteRole(ctx, input, optFn)
 	if err != nil {
 		return &ClientError{
 			ResourceName: roleName,
@@ -81,17 +75,11 @@ func (i *Iam) ListAttachedRolePolicies(ctx context.Context, roleName *string) ([
 		retryable := func(err error) bool {
 			return strings.Contains(err.Error(), "api error Throttling: Rate exceeded")
 		}
+		optFn := func(o *iam.Options) {
+			o.Retryer = NewRetryer(retryable, SleepTimeSecForIam)
+		}
 
-		output, err := Retry(
-			&RetryInput[iam.ListAttachedRolePoliciesInput, iam.ListAttachedRolePoliciesOutput, iam.Options]{
-				Ctx:              ctx,
-				SleepTimeSec:     SleepTimeSecForIam,
-				TargetResource:   roleName,
-				Input:            input,
-				ApiCaller:        i.client.ListAttachedRolePolicies,
-				RetryableChecker: retryable,
-			},
-		)
+		output, err := i.client.ListAttachedRolePolicies(ctx, input, optFn)
 		if err != nil {
 			return nil, &ClientError{
 				ResourceName: roleName,
@@ -129,17 +117,11 @@ func (i *Iam) DetachRolePolicy(ctx context.Context, roleName *string, PolicyArn
 	retryable := func(err error) bool {
 		return strings.Contains(err.Error(), "api error Throttling: Rate exceeded")
 	}
+	optFn := func(o *iam.Options) {
+		o.Retryer = NewRetryer(retryable, SleepTimeSecForIam)
+	}
 
-	_, err := Retry(
-		&RetryInput[iam.DetachRolePolicyInput, iam.DetachRolePolicyOutput, iam.Options]{
-			Ctx:              ctx,
-			SleepTimeSec:     SleepTimeSecForIam,
-			TargetResource:   roleName,
-			Input:            input,
-			ApiCaller:        i.client.DetachRolePolicy,
-			RetryableChecker: retryable,
-		},
-	)
+	_, err := i.client.DetachRolePolicy(ctx, input, optFn)
 	if err != nil {
 		return &ClientError{
 			ResourceName: roleName,
@@ -157,17 +139,11 @@ func (i *Iam) CheckRoleExists(ctx context.Context, roleName *string) (bool, erro
 	retryable := func(err error) bool {
 		return strings.Contains(err.Error(), "api error Throttling: Rate exceeded")
 	}
+	optFn := func(o *iam.Options) {
+		o.Retryer = NewRetryer(retryable, SleepTimeSecForIam)
+	}
 
-	_, err := Retry(
-		&RetryInput[iam.GetRoleInput, iam.GetRoleOutput, iam.Options]{
-			Ctx:              ctx,
-			SleepTimeSec:     SleepTimeSecForIam,
-			TargetResource:   roleName,
-			Input:            input,
-			ApiCaller:        i.client.GetRole,
-			RetryableChecker: retryable,
-		},
-	)
+	_, err := i.client.GetRole(ctx, input, optFn)
 
 	if err != nil && strings.Contains(err.Error(), "NoSuchEntity") {
 		return false, nil
diff --git a/pkg/client/retryer.go b/pkg/client/retryer.go
index d3b6f2b..3d6e703 100644
--- a/pkg/client/retryer.go
+++ b/pkg/client/retryer.go
@@ -2,71 +2,53 @@ package client
 
 import (
 	"context"
-	"fmt"
 	"math/rand"
-	"strconv"
 	"time"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
 )
 
 const MaxRetryCount = 10
 
-// T: Input type for API Request.
-// U: Output type for API Response.
-// V: Options type for API Request.
-type RetryInput[T, U, V any] struct {
-	Ctx              context.Context
-	SleepTimeSec     int
-	TargetResource   *string
-	Input            *T
-	ApiOptions       []func(*V)
-	ApiCaller        func(ctx context.Context, input *T, optFns ...func(*V)) (*U, error)
-	RetryableChecker func(error) bool
-}
-
-// T: Input type for API Request.
-// U: Output type for API Response.
-// V: Options type for API Request.
-func Retry[T, U, V any](
-	in *RetryInput[T, U, V],
-) (*U, error) {
-	retryCount := 0
+var _ aws.RetryerV2 = (*Retryer)(nil)
 
-	for {
-		output, err := in.ApiCaller(in.Ctx, in.Input, in.ApiOptions...)
-		if err == nil {
-			return output, nil
-		}
+type Retryer struct {
+	isErrorRetryableFunc func(error) bool
+	delayTimeSec         int
+}
 
-		if in.RetryableChecker(err) {
-			retryCount++
-			if err := waitForRetry(in.Ctx, retryCount, in.SleepTimeSec, in.TargetResource, err); err != nil {
-				return nil, err
-			}
-			continue
-		}
-		return nil, err
+func NewRetryer(isErrorRetryableFunc func(error) bool, delayTimeSec int) *Retryer {
+	return &Retryer{
+		isErrorRetryableFunc: isErrorRetryableFunc,
+		delayTimeSec:         delayTimeSec,
 	}
 }
 
-func waitForRetry(ctx context.Context, retryCount int, sleepTimeSec int, targetResource *string, err error) error {
-	if retryCount > MaxRetryCount {
-		errorDetail := err.Error() + "\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "
-		return fmt.Errorf("RetryCountOverError: %v, %v", *targetResource, errorDetail)
-	}
+func (r *Retryer) IsErrorRetryable(err error) bool {
+	return r.isErrorRetryableFunc(err)
+}
 
-	select {
-	case <-ctx.Done():
-		return ctx.Err()
-	case <-time.After(getRandomSleepTime(sleepTimeSec)):
-	}
-	return nil
+func (r *Retryer) MaxAttempts() int {
+	return MaxRetryCount
 }
 
-func getRandomSleepTime(sleepTimeSec int) time.Duration {
+func (r *Retryer) RetryDelay(int, error) (time.Duration, error) {
 	rand.Seed(time.Now().UnixNano())
 	waitTime := 1
-	if sleepTimeSec > 1 {
-		waitTime += rand.Intn(sleepTimeSec)
+	if r.delayTimeSec > 1 {
+		waitTime += rand.Intn(r.delayTimeSec)
 	}
-	return time.Duration(waitTime) * time.Second
+	return time.Duration(waitTime) * time.Second, nil
+}
+
+func (r *Retryer) GetRetryToken(context.Context, error) (func(error) error, error) {
+	return func(error) error { return nil }, nil
+}
+
+func (r *Retryer) GetInitialToken() func(error) error {
+	return func(error) error { return nil }
+}
+
+func (r *Retryer) GetAttemptToken(context.Context) (func(error) error, error) {
+	return func(error) error { return nil }, nil
 }
diff --git a/pkg/client/s3.go b/pkg/client/s3.go
index 8c13c60..4e715ed 100644
--- a/pkg/client/s3.go
+++ b/pkg/client/s3.go
@@ -108,17 +108,11 @@ func (s *S3) DeleteObjects(ctx context.Context, bucketName *string, objects []ty
 			retryable := func(err error) bool {
 				return strings.Contains(err.Error(), "api error SlowDown")
 			}
+			optFn := func(o *s3.Options) {
+				o.Retryer = NewRetryer(retryable, SleepTimeSecForS3)
+			}
 
-			output, err := Retry(
-				&RetryInput[s3.DeleteObjectsInput, s3.DeleteObjectsOutput, s3.Options]{
-					Ctx:              ctx,
-					SleepTimeSec:     SleepTimeSecForS3,
-					TargetResource:   bucketName,
-					Input:            input,
-					ApiCaller:        s.client.DeleteObjects,
-					RetryableChecker: retryable,
-				},
-			)
+			output, err := s.client.DeleteObjects(ctx, input, optFn)
 			if err != nil {
 				return err // return non wrapping error because wrap after eg.Wait()
 			}


Test patch:
diff --git a/pkg/client/iam_test.go b/pkg/client/iam_test.go
index ce3f994..debbfae 100644
--- a/pkg/client/iam_test.go
+++ b/pkg/client/iam_test.go
@@ -4,10 +4,10 @@ import (
 	"context"
 	"fmt"
 	"reflect"
-	"strconv"
 	"testing"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/aws/retry"
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/iam"
 	"github.com/aws/aws-sdk-go-v2/service/iam/types"
@@ -104,8 +104,11 @@ func TestIam_DeleteRole(t *testing.T) {
 							"DeleteRoleApiErrorMock",
 							func(context.Context, middleware.FinalizeInput, middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) {
 								return middleware.FinalizeOutput{
-									Result: &iam.DeleteRoleOutput{},
-								}, middleware.Metadata{}, fmt.Errorf("api error Throttling: Rate exceeded")
+										Result: &iam.DeleteRoleOutput{},
+									}, middleware.Metadata{}, &retry.MaxAttemptsError{
+										Attempt: MaxRetryCount,
+										Err:     fmt.Errorf("api error Throttling: Rate exceeded"),
+									}
 							},
 						),
 						middleware.Before,
@@ -114,7 +117,7 @@ func TestIam_DeleteRole(t *testing.T) {
 			},
 			want: &ClientError{
 				ResourceName: aws.String("test"),
-				Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: DeleteRole, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				Err:          fmt.Errorf("operation error IAM: DeleteRole, exceeded maximum number of attempts, 10, api error Throttling: Rate exceeded"),
 			},
 			wantErr: true,
 		},
@@ -276,8 +279,11 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 							"ListAttachedRolePoliciesApiErrorMock",
 							func(context.Context, middleware.FinalizeInput, middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) {
 								return middleware.FinalizeOutput{
-									Result: &iam.ListAttachedRolePoliciesOutput{},
-								}, middleware.Metadata{}, fmt.Errorf("api error Throttling: Rate exceeded")
+										Result: &iam.ListAttachedRolePoliciesOutput{},
+									}, middleware.Metadata{}, &retry.MaxAttemptsError{
+										Attempt: MaxRetryCount,
+										Err:     fmt.Errorf("api error Throttling: Rate exceeded"),
+									}
 							},
 						),
 						middleware.Before,
@@ -288,7 +294,7 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 				output: nil,
 				err: &ClientError{
 					ResourceName: aws.String("test"),
-					Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: ListAttachedRolePolicies, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+					Err:          fmt.Errorf("operation error IAM: ListAttachedRolePolicies, exceeded maximum number of attempts, 10, api error Throttling: Rate exceeded"),
 				},
 			},
 			wantErr: true,
@@ -490,8 +496,11 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 									}, middleware.Metadata{}, nil
 								} else {
 									return middleware.FinalizeOutput{
-										Result: &iam.ListAttachedRolePoliciesOutput{},
-									}, middleware.Metadata{}, fmt.Errorf("api error Throttling: Rate exceeded")
+											Result: &iam.ListAttachedRolePoliciesOutput{},
+										}, middleware.Metadata{}, &retry.MaxAttemptsError{
+											Attempt: MaxRetryCount,
+											Err:     fmt.Errorf("api error Throttling: Rate exceeded"),
+										}
 								}
 							},
 						),
@@ -504,7 +513,7 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 				output: nil,
 				err: &ClientError{
 					ResourceName: aws.String("test"),
-					Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: ListAttachedRolePolicies, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+					Err:          fmt.Errorf("operation error IAM: ListAttachedRolePolicies, exceeded maximum number of attempts, 10, api error Throttling: Rate exceeded"),
 				},
 			},
 			wantErr: true,
@@ -667,8 +676,11 @@ func TestIam_DetachRolePolicies(t *testing.T) {
 							"DetachRolePolicyApiErrorMock",
 							func(context.Context, middleware.FinalizeInput, middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) {
 								return middleware.FinalizeOutput{
-									Result: &iam.DetachRolePolicyOutput{},
-								}, middleware.Metadata{}, fmt.Errorf("api error Throttling: Rate exceeded")
+										Result: &iam.DetachRolePolicyOutput{},
+									}, middleware.Metadata{}, &retry.MaxAttemptsError{
+										Attempt: MaxRetryCount,
+										Err:     fmt.Errorf("api error Throttling: Rate exceeded"),
+									}
 							},
 						),
 						middleware.Before,
@@ -677,7 +689,7 @@ func TestIam_DetachRolePolicies(t *testing.T) {
 			},
 			want: &ClientError{
 				ResourceName: aws.String("test"),
-				Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: DetachRolePolicy, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				Err:          fmt.Errorf("operation error IAM: DetachRolePolicy, exceeded maximum number of attempts, 10, api error Throttling: Rate exceeded"),
 			},
 			wantErr: true,
 		},
@@ -785,8 +797,11 @@ func TestIam_DetachRolePolicy(t *testing.T) {
 							"DetachRolePolicyApiErrorMock",
 							func(context.Context, middleware.FinalizeInput, middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) {
 								return middleware.FinalizeOutput{
-									Result: &iam.DetachRolePolicyOutput{},
-								}, middleware.Metadata{}, fmt.Errorf("api error Throttling: Rate exceeded")
+										Result: &iam.DetachRolePolicyOutput{},
+									}, middleware.Metadata{}, &retry.MaxAttemptsError{
+										Attempt: MaxRetryCount,
+										Err:     fmt.Errorf("api error Throttling: Rate exceeded"),
+									}
 							},
 						),
 						middleware.Before,
@@ -795,7 +810,7 @@ func TestIam_DetachRolePolicy(t *testing.T) {
 			},
 			want: &ClientError{
 				ResourceName: aws.String("test"),
-				Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: DetachRolePolicy, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				Err:          fmt.Errorf("operation error IAM: DetachRolePolicy, exceeded maximum number of attempts, 10, api error Throttling: Rate exceeded"),
 			},
 			wantErr: true,
 		},
@@ -939,8 +954,11 @@ func TestIam_CheckRoleExists(t *testing.T) {
 							"GetRoleApiErrorMock",
 							func(context.Context, middleware.FinalizeInput, middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) {
 								return middleware.FinalizeOutput{
-									Result: &iam.GetRoleOutput{},
-								}, middleware.Metadata{}, fmt.Errorf("api error Throttling: Rate exceeded")
+										Result: &iam.GetRoleOutput{},
+									}, middleware.Metadata{}, &retry.MaxAttemptsError{
+										Attempt: MaxRetryCount,
+										Err:     fmt.Errorf("api error Throttling: Rate exceeded"),
+									}
 							},
 						),
 						middleware.Before,
@@ -951,7 +969,7 @@ func TestIam_CheckRoleExists(t *testing.T) {
 				exists: false,
 				err: &ClientError{
 					ResourceName: aws.String("test"),
-					Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: GetRole, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+					Err:          fmt.Errorf("operation error IAM: GetRole, exceeded maximum number of attempts, 10, api error Throttling: Rate exceeded"),
 				},
 			},
 			wantErr: true,
diff --git a/pkg/client/retryer_test.go b/pkg/client/retryer_test.go
deleted file mode 100644
index 51d77b0..0000000
--- a/pkg/client/retryer_test.go
+++ /dev/null
@@ -1,242 +0,0 @@
-package client
-
-import (
-	"context"
-	"fmt"
-	"strconv"
-	"testing"
-
-	"github.com/aws/aws-sdk-go-v2/aws"
-)
-
-/*
-	Test Cases
-*/
-
-func TestRetry(t *testing.T) {
-	tests := []struct {
-		name    string
-		args    RetryInput[struct{}, struct{}, struct{}]
-		want    error
-		wantErr bool
-	}{
-		{
-			name: "retry and then occur an error: ApiCaller returns error and RetryableChecker returns true",
-			args: RetryInput[struct{}, struct{}, struct{}]{
-				Ctx:            context.Background(),
-				SleepTimeSec:   1,
-				TargetResource: aws.String("resource"),
-				Input:          &struct{}{},
-				ApiCaller: func(ctx context.Context, input *struct{}, optFns ...func(*struct{})) (*struct{}, error) {
-					return input, fmt.Errorf("ApiFuncError")
-				},
-				RetryableChecker: func(err error) bool {
-					return true
-				},
-			},
-			want:    fmt.Errorf("RetryCountOverError: resource, ApiFuncError\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
-			wantErr: true,
-		},
-		{
-			name: "do not retry and then occur an error: ApiCaller returns error and RetryableChecker returns false",
-			args: RetryInput[struct{}, struct{}, struct{}]{
-				Ctx:            context.Background(),
-				SleepTimeSec:   1,
-				TargetResource: aws.String("resource"),
-				Input:          &struct{}{},
-				ApiCaller: func(ctx context.Context, input *struct{}, optFns ...func(*struct{})) (*struct{}, error) {
-					return input, fmt.Errorf("ApiFuncError")
-				},
-				RetryableChecker: func(err error) bool {
-					return false
-				},
-			},
-			want:    fmt.Errorf("ApiFuncError"),
-			wantErr: true,
-		},
-		{
-			name: "success: ApiCaller do not return error and RetryableChecker returns true but is not concerned about",
-			args: RetryInput[struct{}, struct{}, struct{}]{
-				Ctx:            context.Background(),
-				SleepTimeSec:   1,
-				TargetResource: aws.String("resource"),
-				Input:          &struct{}{},
-				ApiCaller: func(ctx context.Context, input *struct{}, optFns ...func(*struct{})) (*struct{}, error) {
-					return input, nil
-				},
-				RetryableChecker: func(err error) bool {
-					return true
-				},
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "success: ApiCaller do not return error and RetryableChecker returns false but is not concerned about",
-			args: RetryInput[struct{}, struct{}, struct{}]{
-				Ctx:            context.Background(),
-				SleepTimeSec:   1,
-				TargetResource: aws.String("resource"),
-				Input:          &struct{}{},
-				ApiCaller: func(ctx context.Context, input *struct{}, optFns ...func(*struct{})) (*struct{}, error) {
-					return input, nil
-				},
-				RetryableChecker: func(err error) bool {
-					return false
-				},
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "success: ApiCaller do not return error with empty ApiOptions",
-			args: RetryInput[struct{}, struct{}, struct{}]{
-				Ctx:            context.Background(),
-				SleepTimeSec:   1,
-				TargetResource: aws.String("resource"),
-				Input:          &struct{}{},
-				ApiOptions:     []func(*struct{}){},
-				ApiCaller: func(ctx context.Context, input *struct{}, optFns ...func(*struct{})) (*struct{}, error) {
-					return input, nil
-				},
-				RetryableChecker: func(err error) bool {
-					return false
-				},
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "success: ApiCaller do not return error with some ApiOptions",
-			args: RetryInput[struct{}, struct{}, struct{}]{
-				Ctx:            context.Background(),
-				SleepTimeSec:   1,
-				TargetResource: aws.String("resource"),
-				Input:          &struct{}{},
-				ApiOptions: []func(*struct{}){
-					func(*struct{}) {},
-					func(*struct{}) {},
-				},
-				ApiCaller: func(ctx context.Context, input *struct{}, optFns ...func(*struct{})) (*struct{}, error) {
-					return input, nil
-				},
-				RetryableChecker: func(err error) bool {
-					return false
-				},
-			},
-			want:    nil,
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			_, err := Retry(&tt.args)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("Retry() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if tt.wantErr && err.Error() != tt.want.Error() {
-				t.Errorf("Retry() error = %v, want %v", err.Error(), tt.want.Error())
-				return
-			}
-		})
-	}
-}
-
-func Test_waitForRetry(t *testing.T) {
-	type args struct {
-		ctx                context.Context
-		retryCount         int
-		sleepTimeSec       int
-		targetResourceType *string
-		err                error
-	}
-
-	cases := []struct {
-		name    string
-		args    args
-		want    error
-		wantErr bool
-	}{
-		{
-			name: "sleepTimeSec = 0: not error",
-			args: args{
-				ctx:                context.Background(),
-				retryCount:         0,
-				sleepTimeSec:       0,
-				targetResourceType: aws.String("resource"),
-				err:                fmt.Errorf("API Error"),
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "retryCount = 0: not error",
-			args: args{
-				ctx:                context.Background(),
-				retryCount:         0,
-				sleepTimeSec:       1,
-				targetResourceType: aws.String("resource"),
-				err:                fmt.Errorf("API Error"),
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "retryCount = MaxRetryCount - 1: not error",
-			args: args{
-				ctx:                context.Background(),
-				retryCount:         MaxRetryCount - 1,
-				sleepTimeSec:       1,
-				targetResourceType: aws.String("resource"),
-				err:                fmt.Errorf("API Error"),
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "retryCount = MaxRetryCount: not error",
-			args: args{
-				ctx:                context.Background(),
-				retryCount:         MaxRetryCount,
-				sleepTimeSec:       1,
-				targetResourceType: aws.String("resource"),
-				err:                fmt.Errorf("API Error"),
-			},
-			want:    nil,
-			wantErr: false,
-		},
-		{
-			name: "retryCount = MaxRetryCount + 1: RetryCountOverError",
-			args: args{
-				ctx:                context.Background(),
-				retryCount:         MaxRetryCount + 1,
-				sleepTimeSec:       1,
-				targetResourceType: aws.String("resource"),
-				err:                fmt.Errorf("API Error"),
-			},
-			want:    fmt.Errorf("RetryCountOverError: resource, API Error\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
-			wantErr: true,
-		},
-	}
-
-	for _, tt := range cases {
-		t.Run(tt.name, func(t *testing.T) {
-			err := waitForRetry(
-				tt.args.ctx,
-				tt.args.retryCount,
-				tt.args.sleepTimeSec,
-				tt.args.targetResourceType,
-				tt.args.err,
-			)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if tt.wantErr && err.Error() != tt.want.Error() {
-				t.Errorf("err = %v, want %v", err, tt.want)
-			}
-		})
-	}
-
-}
diff --git a/pkg/client/s3_test.go b/pkg/client/s3_test.go
index da7209a..4109562 100644
--- a/pkg/client/s3_test.go
+++ b/pkg/client/s3_test.go
@@ -5,10 +5,10 @@ import (
 	"fmt"
 	"reflect"
 	"runtime"
-	"strconv"
 	"testing"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/aws/retry"
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/s3"
 	"github.com/aws/aws-sdk-go-v2/service/s3/types"
@@ -298,11 +298,14 @@ func TestS3_DeleteObjects(t *testing.T) {
 							"DeleteObjectsApiErrorMock",
 							func(context.Context, middleware.FinalizeInput, middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) {
 								return middleware.FinalizeOutput{
-									Result: &s3.DeleteObjectsOutput{
-										Deleted: []types.DeletedObject{},
-										Errors:  []types.Error{},
-									},
-								}, middleware.Metadata{}, fmt.Errorf("api error SlowDown")
+										Result: &s3.DeleteObjectsOutput{
+											Deleted: []types.DeletedObject{},
+											Errors:  []types.Error{},
+										},
+									}, middleware.Metadata{}, &retry.MaxAttemptsError{
+										Attempt: MaxRetryCount,
+										Err:     fmt.Errorf("api error SlowDown"),
+									}
 							},
 						),
 						middleware.Before,
@@ -313,7 +316,7 @@ func TestS3_DeleteObjects(t *testing.T) {
 				output: nil,
 				err: &ClientError{
 					ResourceName: aws.String("test"),
-					Err:          fmt.Errorf("RetryCountOverError: test, operation error S3: DeleteObjects, api error SlowDown\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+					Err:          fmt.Errorf("operation error S3: DeleteObjects, exceeded maximum number of attempts, 10, api error SlowDown"),
 				},
 			},
 			wantErr: true,