From 1ae31279fae604f08da81d95cae5979ef7029dc3 Mon Sep 17 00:00:00 2001 From: yndu13 Date: Fri, 15 Mar 2024 13:01:03 +0800 Subject: [PATCH] [v2.x]Rebuild retry policy --- go.mod | 2 +- tea/back_off_policy.go | 117 +++++++++++ tea/back_off_policy_test.go | 135 +++++++++++++ tea/{tea.go => core.go} | 282 +------------------------- tea/{tea_test.go => core_test.go} | 321 +----------------------------- tea/error.go | 149 ++++++++++++++ tea/error_test.go | 112 +++++++++++ tea/function.go | 81 ++++++++ tea/function_test.go | 122 ++++++++++++ tea/json_parser_test.go | 2 +- tea/retry.go | 106 ++++++++++ tea/retry_test.go | 311 +++++++++++++++++++++++++++++ tea/trans.go | 75 +++++++ tea/trans_test.go | 62 +++++- utils/array.go | 13 ++ utils/array_test.go | 21 ++ 16 files changed, 1308 insertions(+), 603 deletions(-) create mode 100644 tea/back_off_policy.go create mode 100644 tea/back_off_policy_test.go rename tea/{tea.go => core.go} (79%) rename tea/{tea_test.go => core_test.go} (69%) create mode 100644 tea/error.go create mode 100644 tea/error_test.go create mode 100644 tea/function.go create mode 100644 tea/function_test.go create mode 100644 tea/retry.go create mode 100644 tea/retry_test.go create mode 100644 utils/array.go create mode 100644 utils/array_test.go diff --git a/go.mod b/go.mod index 27e53f5..b70d80c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/alibabacloud-go/tea +module github.com/alibabacloud-go/tea/v2 go 1.14 diff --git a/tea/back_off_policy.go b/tea/back_off_policy.go new file mode 100644 index 0000000..3ba0e3a --- /dev/null +++ b/tea/back_off_policy.go @@ -0,0 +1,117 @@ +package tea + +import ( + "math" + "math/rand" +) + +type BackoffPolicy interface { + GetDelayTimeMillis(ctx *RetryPolicyContext) *int64 +} + +func NewBackoffPolicy(options map[string]interface{}) (backoffPolicy BackoffPolicy) { + policy := StringValue(TransInterfaceToString(options["policy"])) + switch policy { + case "Fixed": + backoffPolicy = &FixedBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + } + return + case "Random": + backoffPolicy = &RandomBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + Cap: TransInterfaceToInt64(options["cap"]), + } + return + case "Exponential": + backoffPolicy = &ExponentialBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + Cap: TransInterfaceToInt64(options["cap"]), + } + return + case "EqualJitter": + backoffPolicy = &EqualJitterBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + Cap: TransInterfaceToInt64(options["cap"]), + } + return + case "ExponentialWithEqualJitter": + backoffPolicy = &EqualJitterBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + Cap: TransInterfaceToInt64(options["cap"]), + } + return + case "FullJitter": + backoffPolicy = &FullJitterBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + Cap: TransInterfaceToInt64(options["cap"]), + } + return + case "ExponentialWithFullJitter": + backoffPolicy = &FullJitterBackoffPolicy{ + Period: TransInterfaceToInt(options["period"]), + Cap: TransInterfaceToInt64(options["cap"]), + } + return + } + return nil +} + +type FixedBackoffPolicy struct { + Period *int +} + +func (fixedBackoff *FixedBackoffPolicy) GetDelayTimeMillis(ctx *RetryPolicyContext) *int64 { + return Int64(int64(IntValue(fixedBackoff.Period))) +} + +type RandomBackoffPolicy struct { + Period *int + Cap *int64 +} + +func (randomBackoff *RandomBackoffPolicy) GetDelayTimeMillis(ctx *RetryPolicyContext) *int64 { + if randomBackoff.Cap == nil { + randomBackoff.Cap = Int64(20 * 1000) + } + ceil := math.Min(float64(*randomBackoff.Cap), float64(IntValue(randomBackoff.Period))*float64(IntValue(ctx.RetriesAttempted))) + return Int64(int64(rand.Float64() * ceil)) +} + +type ExponentialBackoffPolicy struct { + Period *int + Cap *int64 +} + +func (exponentialBackoff *ExponentialBackoffPolicy) GetDelayTimeMillis(ctx *RetryPolicyContext) *int64 { + if exponentialBackoff.Cap == nil { + exponentialBackoff.Cap = Int64(3 * 24 * 60 * 60 * 1000) + } + return Int64(int64(math.Min(float64(*exponentialBackoff.Cap), float64(IntValue(exponentialBackoff.Period))*math.Pow(2.0, float64(IntValue(ctx.RetriesAttempted)))))) +} + +type EqualJitterBackoffPolicy struct { + Period *int + Cap *int64 +} + +func (equalJitterBackoff *EqualJitterBackoffPolicy) GetDelayTimeMillis(ctx *RetryPolicyContext) *int64 { + if equalJitterBackoff.Cap == nil { + equalJitterBackoff.Cap = Int64(3 * 24 * 60 * 60 * 1000) + } + ceil := math.Min(float64(*equalJitterBackoff.Cap), float64(IntValue(equalJitterBackoff.Period))*math.Pow(2.0, float64(IntValue(ctx.RetriesAttempted)))) + return Int64(int64(ceil/2 + rand.Float64()*(ceil/2+1))) +} + +type FullJitterBackoffPolicy struct { + Period *int + Cap *int64 +} + +func (fullJitterBackof *FullJitterBackoffPolicy) GetDelayTimeMillis(ctx *RetryPolicyContext) *int64 { + if fullJitterBackof.Cap == nil { + fullJitterBackof.Cap = Int64(3 * 24 * 60 * 60 * 1000) + } + ceil := math.Min(float64(*fullJitterBackof.Cap), float64(IntValue(fullJitterBackof.Period))*math.Pow(2.0, float64(IntValue(ctx.RetriesAttempted)))) + return Int64(int64(rand.Float64() * ceil)) +} diff --git a/tea/back_off_policy_test.go b/tea/back_off_policy_test.go new file mode 100644 index 0000000..5fd87ce --- /dev/null +++ b/tea/back_off_policy_test.go @@ -0,0 +1,135 @@ +package tea + +import ( + "reflect" + "testing" + + "github.com/alibabacloud-go/tea/v2/utils" +) + +func TestBackoffPolicy(t *testing.T) { + var backoffPolicy BackoffPolicy + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "Fixed", + "period": 1000, + }) + typeOfPolicy := reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "FixedBackoffPolicy", typeOfPolicy.Elem().Name()) + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "Random", + "period": 2, + "cap": int64(60 * 1000), + }) + typeOfPolicy = reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "RandomBackoffPolicy", typeOfPolicy.Elem().Name()) + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "Exponential", + "period": 2, + "cap": int64(60 * 1000), + }) + typeOfPolicy = reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "ExponentialBackoffPolicy", typeOfPolicy.Elem().Name()) + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "EqualJitter", + "period": 2, + "cap": int64(60 * 1000), + }) + typeOfPolicy = reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "EqualJitterBackoffPolicy", typeOfPolicy.Elem().Name()) + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "ExponentialWithEqualJitter", + "period": 2, + "cap": int64(60 * 1000), + }) + typeOfPolicy = reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "EqualJitterBackoffPolicy", typeOfPolicy.Elem().Name()) + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "FullJitter", + "period": 2, + "cap": int64(60 * 1000), + }) + typeOfPolicy = reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "FullJitterBackoffPolicy", typeOfPolicy.Elem().Name()) + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "ExponentialWithFullJitter", + "period": 2, + "cap": int64(60 * 1000), + }) + typeOfPolicy = reflect.TypeOf(backoffPolicy) + utils.AssertEqual(t, "FullJitterBackoffPolicy", typeOfPolicy.Elem().Name()) +} + +func TestFixedBackoffPolicy(t *testing.T) { + backoffPolicy := FixedBackoffPolicy{ + Period: Int(1000), + } + utils.AssertEqual(t, int64(1000), Int64Value(backoffPolicy.GetDelayTimeMillis(nil))) + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + } + utils.AssertEqual(t, int64(1000), Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext))) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + } + utils.AssertEqual(t, int64(1000), Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext))) +} + +func TestRandomBackoffPolicy(t *testing.T) { + backoffPolicy := RandomBackoffPolicy{ + Period: Int(2), + Cap: Int64(60 * 1000), + } + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + } + utils.AssertEqual(t, true, Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext)) < 2) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + } + utils.AssertEqual(t, true, Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext)) < 4) +} + +func TestExponentialBackoffPolicy(t *testing.T) { + backoffPolicy := ExponentialBackoffPolicy{ + Period: Int(2), + Cap: Int64(60 * 1000), + } + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + } + utils.AssertEqual(t, int64(4), Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext))) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + } + utils.AssertEqual(t, int64(8), Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext))) +} + +func TestEqualJitterBackoffPolicy(t *testing.T) { + backoffPolicy := EqualJitterBackoffPolicy{ + Period: Int(2), + Cap: Int64(60 * 1000), + } + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + } + utils.AssertEqual(t, true, Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext)) < 5) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + } + utils.AssertEqual(t, true, Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext)) < 9) +} + +func TestFullJitterBackoffPolicy(t *testing.T) { + backoffPolicy := FullJitterBackoffPolicy{ + Period: Int(2), + Cap: Int64(60 * 1000), + } + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + } + utils.AssertEqual(t, true, Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext)) < 4) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + } + utils.AssertEqual(t, true, Int64Value(backoffPolicy.GetDelayTimeMillis(&retryPolicyContext)) < 8) +} diff --git a/tea/tea.go b/tea/core.go similarity index 79% rename from tea/tea.go rename to tea/core.go index 3fc9b08..9f47936 100644 --- a/tea/tea.go +++ b/tea/core.go @@ -10,8 +10,6 @@ import ( "errors" "fmt" "io" - "math" - "math/rand" "net" "net/http" "net/url" @@ -24,7 +22,7 @@ import ( "time" "github.com/alibabacloud-go/debug/debug" - "github.com/alibabacloud-go/tea/utils" + "github.com/alibabacloud-go/tea/v2/utils" "golang.org/x/net/proxy" ) @@ -42,11 +40,6 @@ var basicTypes = []string{ // Verify whether the parameters meet the requirements var validateParams = []string{"require", "pattern", "maxLength", "minLength", "maximum", "minimum", "maxItems", "minItems"} -// CastError is used for cast type fails -type CastError struct { - Message *string -} - // Request is used wrap http request type Request struct { Protocol *string @@ -67,18 +60,6 @@ type Response struct { Headers map[string]*string } -// SDKError struct is used save error code and message -type SDKError struct { - Code *string - StatusCode *int - Message *string - Data *string - Stack *string - errMsg *string - Description *string - AccessDeniedDetail map[string]interface{} -} - // RuntimeObject is used for converting http configuration type RuntimeObject struct { IgnoreSSL *bool `json:"ignoreSSL" xml:"ignoreSSL"` @@ -146,13 +127,6 @@ func NewRuntimeObject(runtime map[string]interface{}) *RuntimeObject { return runtimeObject } -// NewCastError is used for cast type fails -func NewCastError(message *string) (err error) { - return &CastError{ - Message: message, - } -} - // NewRequest is used shortly create Request func NewRequest() (req *Request) { return &Request{ @@ -171,108 +145,6 @@ func NewResponse(httpResponse *http.Response) (res *Response) { return } -// NewSDKError is used for shortly create SDKError object -func NewSDKError(obj map[string]interface{}) *SDKError { - err := &SDKError{} - if val, ok := obj["code"].(int); ok { - err.Code = String(strconv.Itoa(val)) - } else if val, ok := obj["code"].(string); ok { - err.Code = String(val) - } - - if obj["message"] != nil { - err.Message = String(obj["message"].(string)) - } - if obj["description"] != nil { - err.Description = String(obj["description"].(string)) - } - if detail := obj["accessDeniedDetail"]; detail != nil { - r := reflect.ValueOf(detail) - if r.Kind().String() == "map" { - res := make(map[string]interface{}) - tmp := r.MapKeys() - for _, key := range tmp { - res[key.String()] = r.MapIndex(key).Interface() - } - err.AccessDeniedDetail = res - } - } - if data := obj["data"]; data != nil { - r := reflect.ValueOf(data) - if r.Kind().String() == "map" { - res := make(map[string]interface{}) - tmp := r.MapKeys() - for _, key := range tmp { - res[key.String()] = r.MapIndex(key).Interface() - } - if statusCode := res["statusCode"]; statusCode != nil { - if code, ok := statusCode.(int); ok { - err.StatusCode = Int(code) - } else if tmp, ok := statusCode.(string); ok { - code, err_ := strconv.Atoi(tmp) - if err_ == nil { - err.StatusCode = Int(code) - } - } else if code, ok := statusCode.(*int); ok { - err.StatusCode = code - } - } - } - byt := bytes.NewBuffer([]byte{}) - jsonEncoder := json.NewEncoder(byt) - jsonEncoder.SetEscapeHTML(false) - jsonEncoder.Encode(data) - err.Data = String(string(bytes.TrimSpace(byt.Bytes()))) - } - - if statusCode, ok := obj["statusCode"].(int); ok { - err.StatusCode = Int(statusCode) - } else if status, ok := obj["statusCode"].(string); ok { - statusCode, err_ := strconv.Atoi(status) - if err_ == nil { - err.StatusCode = Int(statusCode) - } - } - - return err -} - -// Set ErrMsg by msg -func (err *SDKError) SetErrMsg(msg string) { - err.errMsg = String(msg) -} - -func (err *SDKError) Error() string { - if err.errMsg == nil { - str := fmt.Sprintf("SDKError:\n StatusCode: %d\n Code: %s\n Message: %s\n Data: %s\n", - IntValue(err.StatusCode), StringValue(err.Code), StringValue(err.Message), StringValue(err.Data)) - err.SetErrMsg(str) - } - return StringValue(err.errMsg) -} - -// Return message of CastError -func (err *CastError) Error() string { - return StringValue(err.Message) -} - -// Convert is use convert map[string]interface object to struct -func Convert(in interface{}, out interface{}) error { - byt, _ := json.Marshal(in) - decoder := jsonParser.NewDecoder(bytes.NewReader(byt)) - decoder.UseNumber() - err := decoder.Decode(&out) - return err -} - -// Recover is used to format error -func Recover(in interface{}) error { - if in == nil { - return nil - } - return errors.New(fmt.Sprint(in)) -} - // ReadBody is used read response body func (response *Response) ReadBody() (body []byte, err error) { defer response.Body.Close() @@ -518,24 +390,6 @@ func getNoProxy(protocol string, runtime *RuntimeObject) []string { return urls } -func ToReader(obj interface{}) io.Reader { - switch obj.(type) { - case *string: - tmp := obj.(*string) - return strings.NewReader(StringValue(tmp)) - case []byte: - return strings.NewReader(string(obj.([]byte))) - case io.Reader: - return obj.(io.Reader) - default: - panic("Invalid Body. Please set a valid Body.") - } -} - -func ToString(val interface{}) string { - return fmt.Sprintf("%v", val) -} - func getHttpProxy(protocol, host string, runtime *RuntimeObject) (proxy *url.URL, err error) { urls := getNoProxy(protocol, runtime) for _, url := range urls { @@ -599,65 +453,6 @@ func setDialContext(runtime *RuntimeObject) func(cxt context.Context, net, addr } } -func ToObject(obj interface{}) map[string]interface{} { - result := make(map[string]interface{}) - byt, _ := json.Marshal(obj) - err := json.Unmarshal(byt, &result) - if err != nil { - return nil - } - return result -} - -func AllowRetry(retry interface{}, retryTimes *int) *bool { - if IntValue(retryTimes) == 0 { - return Bool(true) - } - retryMap, ok := retry.(map[string]interface{}) - if !ok { - return Bool(false) - } - retryable, ok := retryMap["retryable"].(bool) - if !ok || !retryable { - return Bool(false) - } - - maxAttempts, ok := retryMap["maxAttempts"].(int) - if !ok || maxAttempts < IntValue(retryTimes) { - return Bool(false) - } - return Bool(true) -} - -func Merge(args ...interface{}) map[string]*string { - finalArg := make(map[string]*string) - for _, obj := range args { - switch obj.(type) { - case map[string]*string: - arg := obj.(map[string]*string) - for key, value := range arg { - if value != nil { - finalArg[key] = value - } - } - default: - byt, _ := json.Marshal(obj) - arg := make(map[string]string) - err := json.Unmarshal(byt, &arg) - if err != nil { - return finalArg - } - for key, value := range arg { - if value != "" { - finalArg[key] = String(value) - } - } - } - } - - return finalArg -} - func isNil(a interface{}) bool { defer func() { recover() @@ -821,44 +616,6 @@ func structToMap(dataValue reflect.Value) map[string]interface{} { return out } -func Retryable(err error) *bool { - if err == nil { - return Bool(false) - } - if realErr, ok := err.(*SDKError); ok { - if realErr.StatusCode == nil { - return Bool(false) - } - code := IntValue(realErr.StatusCode) - return Bool(code >= http.StatusInternalServerError) - } - return Bool(true) -} - -func GetBackoffTime(backoff interface{}, retrytimes *int) *int { - backoffMap, ok := backoff.(map[string]interface{}) - if !ok { - return Int(0) - } - policy, ok := backoffMap["policy"].(string) - if !ok || policy == "no" { - return Int(0) - } - - period, ok := backoffMap["period"].(int) - if !ok || period == 0 { - return Int(0) - } - - maxTime := math.Pow(2.0, float64(IntValue(retrytimes))) - return Int(rand.Intn(int(maxTime-1)) * period) -} - -func Sleep(backoffTime *int) { - sleeptime := time.Duration(IntValue(backoffTime)) * time.Second - time.Sleep(sleeptime) -} - func Validate(params interface{}) error { if params == nil { return nil @@ -1131,40 +888,3 @@ func isFilterType(realType string, filterTypes []string) bool { } return false } - -func TransInterfaceToBool(val interface{}) *bool { - if val == nil { - return nil - } - - return Bool(val.(bool)) -} - -func TransInterfaceToInt(val interface{}) *int { - if val == nil { - return nil - } - - return Int(val.(int)) -} - -func TransInterfaceToString(val interface{}) *string { - if val == nil { - return nil - } - - return String(val.(string)) -} - -func Prettify(i interface{}) string { - resp, _ := json.MarshalIndent(i, "", " ") - return string(resp) -} - -func ToInt(a *int32) *int { - return Int(int(Int32Value(a))) -} - -func ToInt32(a *int) *int32 { - return Int32(int32(IntValue(a))) -} diff --git a/tea/tea_test.go b/tea/core_test.go similarity index 69% rename from tea/tea_test.go rename to tea/core_test.go index 272a030..ee8782b 100644 --- a/tea/tea_test.go +++ b/tea/core_test.go @@ -15,7 +15,7 @@ import ( "testing" "time" - "github.com/alibabacloud-go/tea/utils" + "github.com/alibabacloud-go/tea/v2/utils" ) type test struct { @@ -23,15 +23,6 @@ type test struct { Body []byte `json:"body,omitempty"` } -type PrettifyTest struct { - name string - Strs []string - Nums8 []int8 - Unum8 []uint8 - Value string - Mapvalue map[string]string -} - var runtimeObj = map[string]interface{}{ "ignoreSSL": false, "readTimeout": 0, @@ -105,11 +96,6 @@ func mockResponse(statusCode int, content string, mockerr error) (res *http.Resp return } -func TestCastError(t *testing.T) { - err := NewCastError(String("cast error")) - utils.AssertEqual(t, "cast error", err.Error()) -} - func TestRequest(t *testing.T) { request := NewRequest() utils.AssertNotNil(t, request) @@ -127,30 +113,6 @@ func TestResponse(t *testing.T) { utils.AssertNil(t, err) } -func TestConvert(t *testing.T) { - in := map[string]interface{}{ - "key": "value", - "body": []byte("test"), - } - out := new(test) - err := Convert(in, &out) - utils.AssertNil(t, err) - utils.AssertEqual(t, "value", out.Key) - utils.AssertEqual(t, "test", string(out.Body)) -} - -func TestConvertType(t *testing.T) { - in := map[string]interface{}{ - "key": 123, - "body": []byte("test"), - } - out := new(test) - err := Convert(in, &out) - utils.AssertNil(t, err) - utils.AssertEqual(t, "123", out.Key) - utils.AssertEqual(t, "test", string(out.Body)) -} - func TestRuntimeObject(t *testing.T) { runtimeobject := NewRuntimeObject(nil) utils.AssertNil(t, runtimeobject.IgnoreSSL) @@ -159,150 +121,6 @@ func TestRuntimeObject(t *testing.T) { utils.AssertEqual(t, false, BoolValue(runtimeobject.IgnoreSSL)) } -func TestSDKError(t *testing.T) { - err := NewSDKError(map[string]interface{}{ - "code": "code", - "statusCode": 404, - "message": "message", - "data": map[string]interface{}{ - "httpCode": "404", - "requestId": "dfadfa32cgfdcasd4313", - "hostId": "github.com/alibabacloud/tea", - "recommend": "https://中文?q=a.b&product=c&requestId=123", - }, - "description": "description", - "accessDeniedDetail": map[string]interface{}{ - "AuthAction": "ram:ListUsers", - "AuthPrincipalType": "SubUser", - "PolicyType": "ResourceGroupLevelIdentityBassdPolicy", - "NoPermissionType": "ImplicitDeny", - "UserId": 123, - }, - }) - utils.AssertNotNil(t, err) - utils.AssertEqual(t, "SDKError:\n StatusCode: 404\n Code: code\n Message: message\n Data: {\"hostId\":\"github.com/alibabacloud/tea\",\"httpCode\":\"404\",\"recommend\":\"https://中文?q=a.b&product=c&requestId=123\",\"requestId\":\"dfadfa32cgfdcasd4313\"}\n", err.Error()) - - err.SetErrMsg("test") - utils.AssertEqual(t, "test", err.Error()) - utils.AssertEqual(t, 404, *err.StatusCode) - utils.AssertEqual(t, "description", *err.Description) - utils.AssertEqual(t, "ImplicitDeny", err.AccessDeniedDetail["NoPermissionType"]) - utils.AssertEqual(t, 123, err.AccessDeniedDetail["UserId"]) - - err = NewSDKError(map[string]interface{}{ - "statusCode": "404", - "data": map[string]interface{}{ - "statusCode": 500, - }, - }) - utils.AssertNotNil(t, err) - utils.AssertEqual(t, 404, *err.StatusCode) - - err = NewSDKError(map[string]interface{}{ - "data": map[string]interface{}{ - "statusCode": 500, - }, - }) - utils.AssertNotNil(t, err) - utils.AssertEqual(t, 500, *err.StatusCode) - - err = NewSDKError(map[string]interface{}{ - "data": map[string]interface{}{ - "statusCode": Int(500), - }, - }) - utils.AssertNotNil(t, err) - utils.AssertEqual(t, 500, *err.StatusCode) - - err = NewSDKError(map[string]interface{}{ - "data": map[string]interface{}{ - "statusCode": "500", - }, - }) - utils.AssertNotNil(t, err) - utils.AssertEqual(t, 500, *err.StatusCode) - - err = NewSDKError(map[string]interface{}{ - "code": "code", - "message": "message", - "data": map[string]interface{}{ - "requestId": "dfadfa32cgfdcasd4313", - }, - }) - utils.AssertNotNil(t, err) - utils.AssertNil(t, err.StatusCode) - - err = NewSDKError(map[string]interface{}{ - "code": "code", - "message": "message", - "data": "string data", - }) - utils.AssertNotNil(t, err) - utils.AssertNotNil(t, err.Data) - utils.AssertNil(t, err.StatusCode) -} - -func TestSDKErrorCode404(t *testing.T) { - err := NewSDKError(map[string]interface{}{ - "statusCode": 404, - "code": "NOTFOUND", - "message": "message", - }) - utils.AssertNotNil(t, err) - utils.AssertEqual(t, "SDKError:\n StatusCode: 404\n Code: NOTFOUND\n Message: message\n Data: \n", err.Error()) -} - -func TestToObject(t *testing.T) { - str := "{sdsfdsd:" - result := ToObject(str) - utils.AssertNil(t, result) - - input := map[string]string{ - "name": "test", - } - result = ToObject(input) - utils.AssertEqual(t, "test", result["name"].(string)) -} - -func TestAllowRetry(t *testing.T) { - allow := AllowRetry(nil, Int(0)) - utils.AssertEqual(t, true, BoolValue(allow)) - - allow = AllowRetry(nil, Int(1)) - utils.AssertEqual(t, false, BoolValue(allow)) - - input := map[string]interface{}{ - "retryable": false, - "maxAttempts": 2, - } - allow = AllowRetry(input, Int(1)) - utils.AssertEqual(t, false, BoolValue(allow)) - - input["retryable"] = true - allow = AllowRetry(input, Int(3)) - utils.AssertEqual(t, false, BoolValue(allow)) - - input["retryable"] = true - allow = AllowRetry(input, Int(1)) - utils.AssertEqual(t, true, BoolValue(allow)) -} - -func TestMerge(t *testing.T) { - in := map[string]*string{ - "tea": String("test"), - } - valid := map[string]interface{}{ - "valid": "test", - } - invalidStr := "sdfdg" - result := Merge(in, valid, invalidStr) - utils.AssertEqual(t, "test", StringValue(result["tea"])) - utils.AssertEqual(t, "test", StringValue(result["valid"])) - - result = Merge(nil) - utils.AssertEqual(t, map[string]*string{}, result) -} - type Test struct { Msg *string `json:"Msg,omitempty"` Cast *CastError `json:"Cast,omitempty"` @@ -314,7 +132,7 @@ type Test struct { Inter interface{} } -func TestToMap(t *testing.T) { +func Test_ToMap(t *testing.T) { in := map[string]*string{ "tea": String("test"), "nil": nil, @@ -390,69 +208,6 @@ func TestToMap(t *testing.T) { utils.AssertNil(t, result) } -func Test_Retryable(t *testing.T) { - ifRetry := Retryable(nil) - utils.AssertEqual(t, false, BoolValue(ifRetry)) - - err := errors.New("tea") - ifRetry = Retryable(err) - utils.AssertEqual(t, true, BoolValue(ifRetry)) - - errmsg := map[string]interface{}{ - "code": "err", - } - err = NewSDKError(errmsg) - ifRetry = Retryable(err) - utils.AssertEqual(t, false, BoolValue(ifRetry)) - - errmsg["statusCode"] = 400 - err = NewSDKError(errmsg) - ifRetry = Retryable(err) - utils.AssertEqual(t, false, BoolValue(ifRetry)) - - errmsg["statusCode"] = "400" - err = NewSDKError(errmsg) - ifRetry = Retryable(err) - utils.AssertEqual(t, false, BoolValue(ifRetry)) - - errmsg["statusCode"] = 500 - err = NewSDKError(errmsg) - ifRetry = Retryable(err) - utils.AssertEqual(t, true, BoolValue(ifRetry)) - - errmsg["statusCode"] = "500" - err = NewSDKError(errmsg) - ifRetry = Retryable(err) - utils.AssertEqual(t, true, BoolValue(ifRetry)) - - errmsg["statusCode"] = "test" - err = NewSDKError(errmsg) - ifRetry = Retryable(err) - utils.AssertEqual(t, false, BoolValue(ifRetry)) -} - -func Test_GetBackoffTime(t *testing.T) { - ms := GetBackoffTime(nil, Int(0)) - utils.AssertEqual(t, 0, IntValue(ms)) - - backoff := map[string]interface{}{ - "policy": "no", - } - ms = GetBackoffTime(backoff, Int(0)) - utils.AssertEqual(t, 0, IntValue(ms)) - - backoff["policy"] = "yes" - backoff["period"] = 0 - ms = GetBackoffTime(backoff, Int(1)) - utils.AssertEqual(t, 0, IntValue(ms)) - - Sleep(Int(1)) - - backoff["period"] = 3 - ms = GetBackoffTime(backoff, Int(1)) - utils.AssertEqual(t, true, IntValue(ms) <= 3) -} - var key = `-----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAN5I1VCLYr2IlTLrFpwUGcnwl8yi6V8Mdw+myxfusNgEWiH/FQ4T AZsIveiLOz9Gcc8m2mZSxst2qGII00scpiECAwEAAQJBAJZEhnA8yjN28eXKJy68 @@ -703,44 +458,6 @@ func Test_hookdo(t *testing.T) { utils.AssertEqual(t, "hookdo", err.Error()) } -func Test_ToReader(t *testing.T) { - str := "abc" - reader := ToReader(String(str)) - byt, err := ioutil.ReadAll(reader) - utils.AssertNil(t, err) - utils.AssertEqual(t, "abc", string(byt)) - - read := strings.NewReader("bcd") - reader = ToReader(read) - byt, err = ioutil.ReadAll(reader) - utils.AssertNil(t, err) - utils.AssertEqual(t, "bcd", string(byt)) - - byts := []byte("cdf") - reader = ToReader(byts) - byt, err = ioutil.ReadAll(reader) - utils.AssertNil(t, err) - utils.AssertEqual(t, "cdf", string(byt)) - - num := 10 - defer func() { - err := recover() - utils.AssertEqual(t, "Invalid Body. Please set a valid Body.", err.(string)) - }() - reader = ToReader(num) - byt, err = ioutil.ReadAll(reader) - utils.AssertNil(t, err) - utils.AssertEqual(t, "", string(byt)) -} - -func Test_ToString(t *testing.T) { - str := ToString(10) - utils.AssertEqual(t, "10", str) - - str = ToString("10") - utils.AssertEqual(t, "10", str) -} - func Test_Validate(t *testing.T) { num := 3 config := &validateTest{ @@ -760,17 +477,6 @@ func Test_Validate(t *testing.T) { utils.AssertNil(t, err) } -func Test_Recover(t *testing.T) { - err := Recover(nil) - utils.AssertNil(t, err) - defer func() { - if r := Recover(recover()); r != nil { - utils.AssertEqual(t, "test", r.Error()) - } - }() - panic("test") -} - func Test_validate(t *testing.T) { var test *validateTest err := validate(reflect.ValueOf(test)) @@ -887,26 +593,3 @@ func Test_validate(t *testing.T) { err = validate(reflect.ValueOf(val)) utils.AssertEqual(t, `The size of Num1 is 0.000000 which is less than 2.000000`, err.Error()) } - -func Test_Prettify(t *testing.T) { - prettifyTest := &PrettifyTest{ - name: "prettify", - Nums8: []int8{0, 1, 2, 4}, - Unum8: []uint8{0}, - Value: "ok", - Mapvalue: map[string]string{"key": "ccp", "value": "ok"}, - } - str := Prettify(prettifyTest) - utils.AssertContains(t, str, "Nums8") - - str = Prettify(nil) - utils.AssertEqual(t, str, "null") -} - -func Test_TransInt32AndInt(t *testing.T) { - a := ToInt(Int32(10)) - utils.AssertEqual(t, IntValue(a), 10) - - b := ToInt32(a) - utils.AssertEqual(t, Int32Value(b), int32(10)) -} diff --git a/tea/error.go b/tea/error.go new file mode 100644 index 0000000..cb46e98 --- /dev/null +++ b/tea/error.go @@ -0,0 +1,149 @@ +package tea + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" +) + +// BaseError is an interface for getting actual error +type BaseError interface { + error + Error() string + ErrorName() *string + ErrorCode() *string +} + +// CastError is used for cast type fails +type CastError struct { + Name *string + Message *string + Code *string +} + +// NewCastError is used for cast type fails +func NewCastError(message *string) *CastError { + return &CastError{ + Name: String("CastError"), + Message: message, + Code: nil, + } +} + +// Return message of CastError +func (err *CastError) Error() string { + return StringValue(err.Message) +} + +func (err *CastError) ErrorName() *string { + return err.Name +} + +func (err *CastError) ErrorCode() *string { + return err.Code +} + +// SDKError struct is used save error code and message +type SDKError struct { + Name *string + Code *string + StatusCode *int + Message *string + Data *string + Stack *string + errMsg *string + Description *string + AccessDeniedDetail map[string]interface{} +} + +// NewSDKError is used for shortly create SDKError object +func NewSDKError(obj map[string]interface{}) *SDKError { + err := &SDKError{ + Name: String("SDKError"), + } + if val, ok := obj["code"].(int); ok { + err.Code = String(strconv.Itoa(val)) + } else if val, ok := obj["code"].(string); ok { + err.Code = String(val) + } + + if obj["message"] != nil { + err.Message = String(obj["message"].(string)) + } + if obj["description"] != nil { + err.Description = String(obj["description"].(string)) + } + if detail := obj["accessDeniedDetail"]; detail != nil { + r := reflect.ValueOf(detail) + if r.Kind().String() == "map" { + res := make(map[string]interface{}) + tmp := r.MapKeys() + for _, key := range tmp { + res[key.String()] = r.MapIndex(key).Interface() + } + err.AccessDeniedDetail = res + } + } + if data := obj["data"]; data != nil { + r := reflect.ValueOf(data) + if r.Kind().String() == "map" { + res := make(map[string]interface{}) + tmp := r.MapKeys() + for _, key := range tmp { + res[key.String()] = r.MapIndex(key).Interface() + } + if statusCode := res["statusCode"]; statusCode != nil { + if code, ok := statusCode.(int); ok { + err.StatusCode = Int(code) + } else if tmp, ok := statusCode.(string); ok { + code, err_ := strconv.Atoi(tmp) + if err_ == nil { + err.StatusCode = Int(code) + } + } else if code, ok := statusCode.(*int); ok { + err.StatusCode = code + } + } + } + byt := bytes.NewBuffer([]byte{}) + jsonEncoder := json.NewEncoder(byt) + jsonEncoder.SetEscapeHTML(false) + jsonEncoder.Encode(data) + err.Data = String(string(bytes.TrimSpace(byt.Bytes()))) + } + + if statusCode, ok := obj["statusCode"].(int); ok { + err.StatusCode = Int(statusCode) + } else if status, ok := obj["statusCode"].(string); ok { + statusCode, err_ := strconv.Atoi(status) + if err_ == nil { + err.StatusCode = Int(statusCode) + } + } + + return err +} + +// Set ErrMsg by msg +func (err *SDKError) SetErrMsg(msg string) { + err.errMsg = String(msg) +} + +func (err *SDKError) Error() string { + if err.errMsg == nil { + str := fmt.Sprintf("SDKError:\n StatusCode: %d\n Code: %s\n Message: %s\n Data: %s\n", + IntValue(err.StatusCode), StringValue(err.Code), StringValue(err.Message), StringValue(err.Data)) + err.SetErrMsg(str) + } + return StringValue(err.errMsg) +} + +func (err *SDKError) ErrorName() *string { + return err.Name +} + +func (err *SDKError) ErrorCode() *string { + return err.Code +} diff --git a/tea/error_test.go b/tea/error_test.go new file mode 100644 index 0000000..3affb43 --- /dev/null +++ b/tea/error_test.go @@ -0,0 +1,112 @@ +package tea + +import ( + "testing" + + "github.com/alibabacloud-go/tea/v2/utils" +) + +func TestCastError(t *testing.T) { + var err BaseError + err = NewCastError(String("cast error")) + utils.AssertEqual(t, "cast error", err.Error()) + utils.AssertEqual(t, "", StringValue(err.ErrorCode())) + utils.AssertEqual(t, "CastError", StringValue(err.ErrorName())) +} + +func TestSDKError(t *testing.T) { + var err0 BaseError + err0 = NewSDKError(map[string]interface{}{ + "code": "code", + "statusCode": 404, + "message": "message", + "data": map[string]interface{}{ + "httpCode": "404", + "requestId": "dfadfa32cgfdcasd4313", + "hostId": "github.com/alibabacloud/tea", + "recommend": "https://中文?q=a.b&product=c&requestId=123", + }, + "description": "description", + "accessDeniedDetail": map[string]interface{}{ + "AuthAction": "ram:ListUsers", + "AuthPrincipalType": "SubUser", + "PolicyType": "ResourceGroupLevelIdentityBassdPolicy", + "NoPermissionType": "ImplicitDeny", + "UserId": 123, + }, + }) + utils.AssertNotNil(t, err0) + utils.AssertEqual(t, "SDKError:\n StatusCode: 404\n Code: code\n Message: message\n Data: {\"hostId\":\"github.com/alibabacloud/tea\",\"httpCode\":\"404\",\"recommend\":\"https://中文?q=a.b&product=c&requestId=123\",\"requestId\":\"dfadfa32cgfdcasd4313\"}\n", err0.Error()) + var err *SDKError + err = err0.(*SDKError) + err.SetErrMsg("test") + utils.AssertEqual(t, "test", err.Error()) + utils.AssertEqual(t, 404, *err.StatusCode) + utils.AssertEqual(t, "description", *err.Description) + utils.AssertEqual(t, "ImplicitDeny", err.AccessDeniedDetail["NoPermissionType"]) + utils.AssertEqual(t, 123, err.AccessDeniedDetail["UserId"]) + + err = NewSDKError(map[string]interface{}{ + "statusCode": "404", + "data": map[string]interface{}{ + "statusCode": 500, + }, + }) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, 404, *err.StatusCode) + + err = NewSDKError(map[string]interface{}{ + "data": map[string]interface{}{ + "statusCode": 500, + }, + }) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, 500, *err.StatusCode) + + err = NewSDKError(map[string]interface{}{ + "data": map[string]interface{}{ + "statusCode": Int(500), + }, + }) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, 500, *err.StatusCode) + + err = NewSDKError(map[string]interface{}{ + "data": map[string]interface{}{ + "statusCode": "500", + }, + }) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, 500, *err.StatusCode) + + err = NewSDKError(map[string]interface{}{ + "code": "code", + "message": "message", + "data": map[string]interface{}{ + "requestId": "dfadfa32cgfdcasd4313", + }, + }) + utils.AssertNotNil(t, err) + utils.AssertNil(t, err.StatusCode) + + err = NewSDKError(map[string]interface{}{ + "code": "code", + "message": "message", + "data": "string data", + }) + utils.AssertNotNil(t, err) + utils.AssertNotNil(t, err.Data) + utils.AssertNil(t, err.StatusCode) +} + +func TestSDKErrorCode404(t *testing.T) { + err := NewSDKError(map[string]interface{}{ + "statusCode": 404, + "code": "NOTFOUND", + "message": "message", + }) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "SDKError:\n StatusCode: 404\n Code: NOTFOUND\n Message: message\n Data: \n", err.Error()) + utils.AssertEqual(t, "NOTFOUND", StringValue(err.ErrorCode())) + utils.AssertEqual(t, "SDKError", StringValue(err.ErrorName())) +} diff --git a/tea/function.go b/tea/function.go new file mode 100644 index 0000000..f150b32 --- /dev/null +++ b/tea/function.go @@ -0,0 +1,81 @@ +package tea + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" +) + +func Prettify(i interface{}) string { + resp, _ := json.MarshalIndent(i, "", " ") + return string(resp) +} + +func Sleep(backoffTime *int) { + sleeptime := time.Duration(IntValue(backoffTime)) * time.Second + time.Sleep(sleeptime) +} + +func Merge(args ...interface{}) map[string]*string { + finalArg := make(map[string]*string) + for _, obj := range args { + switch obj.(type) { + case map[string]*string: + arg := obj.(map[string]*string) + for key, value := range arg { + if value != nil { + finalArg[key] = value + } + } + default: + byt, _ := json.Marshal(obj) + arg := make(map[string]string) + err := json.Unmarshal(byt, &arg) + if err != nil { + return finalArg + } + for key, value := range arg { + if value != "" { + finalArg[key] = String(value) + } + } + } + } + + return finalArg +} + +// Convert is use convert map[string]interface object to struct +func Convert(in interface{}, out interface{}) error { + byt, _ := json.Marshal(in) + decoder := jsonParser.NewDecoder(bytes.NewReader(byt)) + decoder.UseNumber() + err := decoder.Decode(&out) + return err +} + +// Recover is used to format error +func Recover(in interface{}) error { + if in == nil { + return nil + } + return errors.New(fmt.Sprint(in)) +} + +// Deprecated +func Retryable(err error) *bool { + if err == nil { + return Bool(false) + } + if realErr, ok := err.(*SDKError); ok { + if realErr.StatusCode == nil { + return Bool(false) + } + code := IntValue(realErr.StatusCode) + return Bool(code >= http.StatusInternalServerError) + } + return Bool(true) +} diff --git a/tea/function_test.go b/tea/function_test.go new file mode 100644 index 0000000..ac89758 --- /dev/null +++ b/tea/function_test.go @@ -0,0 +1,122 @@ +package tea + +import ( + "errors" + "testing" + + "github.com/alibabacloud-go/tea/v2/utils" +) + +type PrettifyTest struct { + name string + Strs []string + Nums8 []int8 + Unum8 []uint8 + Value string + Mapvalue map[string]string +} + +func Test_Prettify(t *testing.T) { + prettifyTest := &PrettifyTest{ + name: "prettify", + Nums8: []int8{0, 1, 2, 4}, + Unum8: []uint8{0}, + Value: "ok", + Mapvalue: map[string]string{"key": "ccp", "value": "ok"}, + } + str := Prettify(prettifyTest) + utils.AssertContains(t, str, "Nums8") + + str = Prettify(nil) + utils.AssertEqual(t, str, "null") +} + +func Test_Merge(t *testing.T) { + in := map[string]*string{ + "tea": String("test"), + } + valid := map[string]interface{}{ + "valid": "test", + } + invalidStr := "sdfdg" + result := Merge(in, valid, invalidStr) + utils.AssertEqual(t, "test", StringValue(result["tea"])) + utils.AssertEqual(t, "test", StringValue(result["valid"])) + + result = Merge(nil) + utils.AssertEqual(t, map[string]*string{}, result) +} + +func Test_Convert(t *testing.T) { + in := map[string]interface{}{ + "key": "value", + "body": []byte("test"), + } + out := new(test) + err := Convert(in, &out) + utils.AssertNil(t, err) + utils.AssertEqual(t, "value", out.Key) + utils.AssertEqual(t, "test", string(out.Body)) + + in = map[string]interface{}{ + "key": 123, + "body": []byte("test"), + } + out = new(test) + err = Convert(in, &out) + utils.AssertNil(t, err) + utils.AssertEqual(t, "123", out.Key) + utils.AssertEqual(t, "test", string(out.Body)) +} + +func Test_Recover(t *testing.T) { + err := Recover(nil) + utils.AssertNil(t, err) + defer func() { + if r := Recover(recover()); r != nil { + utils.AssertEqual(t, "test", r.Error()) + } + }() + panic("test") +} + +func Test_Retryable(t *testing.T) { + ifRetry := Retryable(nil) + utils.AssertEqual(t, false, BoolValue(ifRetry)) + + err := errors.New("tea") + ifRetry = Retryable(err) + utils.AssertEqual(t, true, BoolValue(ifRetry)) + + errmsg := map[string]interface{}{ + "code": "err", + } + err = NewSDKError(errmsg) + ifRetry = Retryable(err) + utils.AssertEqual(t, false, BoolValue(ifRetry)) + + errmsg["statusCode"] = 400 + err = NewSDKError(errmsg) + ifRetry = Retryable(err) + utils.AssertEqual(t, false, BoolValue(ifRetry)) + + errmsg["statusCode"] = "400" + err = NewSDKError(errmsg) + ifRetry = Retryable(err) + utils.AssertEqual(t, false, BoolValue(ifRetry)) + + errmsg["statusCode"] = 500 + err = NewSDKError(errmsg) + ifRetry = Retryable(err) + utils.AssertEqual(t, true, BoolValue(ifRetry)) + + errmsg["statusCode"] = "500" + err = NewSDKError(errmsg) + ifRetry = Retryable(err) + utils.AssertEqual(t, true, BoolValue(ifRetry)) + + errmsg["statusCode"] = "test" + err = NewSDKError(errmsg) + ifRetry = Retryable(err) + utils.AssertEqual(t, false, BoolValue(ifRetry)) +} diff --git a/tea/json_parser_test.go b/tea/json_parser_test.go index 1ef415b..2045b9f 100644 --- a/tea/json_parser_test.go +++ b/tea/json_parser_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/alibabacloud-go/tea/utils" + "github.com/alibabacloud-go/tea/v2/utils" jsoniter "github.com/json-iterator/go" "github.com/modern-go/reflect2" ) diff --git a/tea/retry.go b/tea/retry.go new file mode 100644 index 0000000..8660c48 --- /dev/null +++ b/tea/retry.go @@ -0,0 +1,106 @@ +package tea + +import ( + "time" + + "github.com/alibabacloud-go/tea/v2/utils" +) + +const ( + // DefaultMaxAttempts sets maximum number of retries + DefaultMaxAttempts = 3 + + // DefaultMinDelay sets minimum retry delay + DefaultMinDelay = 100 * time.Millisecond + + // DefaultMaxDelayTimeMillis sets maximum retry delay + DefaultMaxDelay = 120 * time.Second + + // DefaultMaxThrottleDelay sets maximum delay when throttled + DefaultMaxThrottleDelay = 300 * time.Second +) + +type RetryCondition struct { + MaxAttempts *int + MaxDelayTimeMillis *int64 + Backoff *BackoffPolicy + Exception []*string + ErrorCode []*string +} + +type RetryOptions struct { + Retryable *bool + RetryCondition []*RetryCondition + NoRetryCondition []*RetryCondition +} + +type RetryPolicyContext struct { + RetriesAttempted *int + Request *Request + Response *Response + Error BaseError +} + +func ShouldRetry(options *RetryOptions, ctx *RetryPolicyContext) *bool { + if IntValue(ctx.RetriesAttempted) == 0 { + return Bool(true) + } + if options == nil || !BoolValue(options.Retryable) { + return Bool(false) + } + + err := ctx.Error + noRetryConditions := options.NoRetryCondition + retryConditions := options.RetryCondition + if noRetryConditions != nil { + for _, noRetryCondition := range noRetryConditions { + if utils.Contains(noRetryCondition.Exception, err.ErrorName()) || utils.Contains(noRetryCondition.ErrorCode, err.ErrorCode()) { + return Bool(false) + } + } + } + if retryConditions != nil { + for _, retryCondition := range retryConditions { + if !utils.Contains(retryCondition.Exception, err.ErrorName()) && !utils.Contains(retryCondition.ErrorCode, err.ErrorCode()) { + continue + } + if IntValue(ctx.RetriesAttempted) > IntValue(retryCondition.MaxAttempts) { + return Bool(false) + } + return Bool(true) + } + } + return Bool(false) +} + +func GetBackoffDelay(options *RetryOptions, ctx *RetryPolicyContext) *int64 { + if IntValue(ctx.RetriesAttempted) == 0 { + return Int64(0) + } + err := ctx.Error + if options != nil { + retryConditions := options.RetryCondition + if retryConditions != nil { + for _, retryCondition := range retryConditions { + if !utils.Contains(retryCondition.Exception, err.ErrorName()) && !utils.Contains(retryCondition.ErrorCode, err.ErrorCode()) { + continue + } + var maxDelay int64 + if retryCondition.MaxDelayTimeMillis != nil { + maxDelay = Int64Value(retryCondition.MaxDelayTimeMillis) + } else { + maxDelay = DefaultMaxDelay.Milliseconds() + } + if retryCondition.Backoff == nil { + return Int64(DefaultMinDelay.Milliseconds()) + } + delayTimeMillis := (*retryCondition.Backoff).GetDelayTimeMillis(ctx) + if Int64Value(delayTimeMillis) > maxDelay { + return Int64(maxDelay) + } + return delayTimeMillis + } + } + } + return Int64(DefaultMinDelay.Milliseconds()) +} diff --git a/tea/retry_test.go b/tea/retry_test.go new file mode 100644 index 0000000..5c2aed2 --- /dev/null +++ b/tea/retry_test.go @@ -0,0 +1,311 @@ +package tea + +import ( + "testing" + + "github.com/alibabacloud-go/tea/v2/utils" +) + +type AErrorTest struct { + Name *string + Message *string + Code *string +} + +func (err *AErrorTest) Error() string { + return "error message" +} + +func (err *AErrorTest) ErrorName() *string { + return String("AErrorTest") +} + +func (err *AErrorTest) ErrorCode() *string { + return String("AErrorTestCode") +} + +type BErrorTest struct { + Name *string + Message *string + Code *string +} + +func (err *BErrorTest) Error() string { + return "error message" +} + +func (err *BErrorTest) ErrorName() *string { + return String("BErrorTest") +} + +func (err *BErrorTest) ErrorCode() *string { + return String("BErrorTestCode") +} + +type CErrorTest struct { + Name *string + Message *string + Code *string +} + +func (err *CErrorTest) Error() string { + return "error message" +} + +func (err *CErrorTest) ErrorName() *string { + return String("CErrorTest") +} + +func (err *CErrorTest) ErrorCode() *string { + return String("CErrorTestCode") +} + +func Test_ShouldRetry(t *testing.T) { + var backoffPolicy BackoffPolicy + backoffPolicy = &ExponentialBackoffPolicy{ + Period: Int(2), + Cap: Int64(60 * 1000), + } + retryCondition1 := RetryCondition{ + MaxAttempts: Int(1), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest")}, + ErrorCode: []*string{String("BErrorTestCode")}, + } + + retryCondition2 := RetryCondition{ + MaxAttempts: Int(2), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest"), String("CErrorTest")}, + } + + retryCondition3 := RetryCondition{ + Exception: []*string{String("BErrorTest")}, + } + + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(0), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(nil, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(nil, &retryPolicyContext))) + + retryOptions := RetryOptions{ + Retryable: Bool(false), + RetryCondition: nil, + NoRetryCondition: nil, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: nil, + NoRetryCondition: nil, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition1, &retryCondition2}, + NoRetryCondition: []*RetryCondition{&retryCondition3}, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(0), + Error: &BErrorTest{}, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &BErrorTest{}, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &CErrorTest{}, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + Error: &CErrorTest{}, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(3), + Error: &CErrorTest{}, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + +} + +func Test_GetBackoffDelay(t *testing.T) { + var backoffPolicy BackoffPolicy + backoffPolicy = &ExponentialBackoffPolicy{ + Period: Int(200), + Cap: Int64(60 * 1000), + } + retryCondition1 := RetryCondition{ + MaxAttempts: Int(1), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest")}, + ErrorCode: []*string{String("BErrorTestCode")}, + } + + retryCondition2 := RetryCondition{ + MaxAttempts: Int(2), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest"), String("CErrorTest")}, + } + + retryCondition3 := RetryCondition{ + Exception: []*string{String("BErrorTest")}, + } + + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(0), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(0), Int64Value(GetBackoffDelay(nil, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(nil, &retryPolicyContext))) + + retryOptions := RetryOptions{ + Retryable: Bool(false), + RetryCondition: nil, + NoRetryCondition: nil, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: nil, + NoRetryCondition: nil, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &AErrorTest{}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition1, &retryCondition2}, + NoRetryCondition: []*RetryCondition{&retryCondition3}, + } + utils.AssertEqual(t, int64(400), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(800), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &BErrorTest{}, + } + utils.AssertEqual(t, int64(400), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: &CErrorTest{}, + } + utils.AssertEqual(t, int64(400), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(2), + Error: &CErrorTest{}, + } + utils.AssertEqual(t, int64(800), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(3), + Error: &CErrorTest{}, + } + utils.AssertEqual(t, int64(1600), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryCondition4 := RetryCondition{ + MaxAttempts: Int(20), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest")}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition4}, + } + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(10), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(60000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(15), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(60000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + backoffPolicy = &ExponentialBackoffPolicy{ + Period: Int(200), + Cap: Int64(180 * 1000), + } + retryCondition4 = RetryCondition{ + MaxAttempts: Int(20), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest")}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition4}, + } + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(10), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(120000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(15), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(120000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryCondition4 = RetryCondition{ + MaxAttempts: Int(20), + MaxDelayTimeMillis: Int64(30 * 1000), + Backoff: &backoffPolicy, + Exception: []*string{String("AErrorTest")}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition4}, + } + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(10), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(30000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(15), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(30000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) +} diff --git a/tea/trans.go b/tea/trans.go index ded1642..2698526 100644 --- a/tea/trans.go +++ b/tea/trans.go @@ -1,5 +1,12 @@ package tea +import ( + "encoding/json" + "fmt" + "io" + "strings" +) + func String(a string) *string { return &a } @@ -489,3 +496,71 @@ func BoolSliceValue(a []*bool) []bool { } return res } + +func TransInterfaceToBool(val interface{}) *bool { + if val == nil { + return nil + } + + return Bool(val.(bool)) +} + +func TransInterfaceToInt(val interface{}) *int { + if val == nil { + return nil + } + + return Int(val.(int)) +} + +func TransInterfaceToInt64(val interface{}) *int64 { + if val == nil { + return nil + } + + return Int64(val.(int64)) +} + +func TransInterfaceToString(val interface{}) *string { + if val == nil { + return nil + } + + return String(val.(string)) +} + +func ToInt(a *int32) *int { + return Int(int(Int32Value(a))) +} + +func ToInt32(a *int) *int32 { + return Int32(int32(IntValue(a))) +} + +func ToString(val interface{}) string { + return fmt.Sprintf("%v", val) +} + +func ToObject(obj interface{}) map[string]interface{} { + result := make(map[string]interface{}) + byt, _ := json.Marshal(obj) + err := json.Unmarshal(byt, &result) + if err != nil { + return nil + } + return result +} + +func ToReader(obj interface{}) io.Reader { + switch obj.(type) { + case *string: + tmp := obj.(*string) + return strings.NewReader(StringValue(tmp)) + case []byte: + return strings.NewReader(string(obj.([]byte))) + case io.Reader: + return obj.(io.Reader) + default: + panic("Invalid Body. Please set a valid Body.") + } +} diff --git a/tea/trans_test.go b/tea/trans_test.go index 013a464..3d64ecc 100644 --- a/tea/trans_test.go +++ b/tea/trans_test.go @@ -1,9 +1,11 @@ package tea import ( + "io/ioutil" + "strings" "testing" - "github.com/alibabacloud-go/tea/utils" + "github.com/alibabacloud-go/tea/v2/utils" ) func Test_Trans(t *testing.T) { @@ -161,3 +163,61 @@ func Test_Trans(t *testing.T) { utils.AssertNil(t, Uint64Slice(nil)) utils.AssertNil(t, Uint64ValueSlice(nil)) } + +func Test_TransInt32AndInt(t *testing.T) { + a := ToInt(Int32(10)) + utils.AssertEqual(t, IntValue(a), 10) + + b := ToInt32(a) + utils.AssertEqual(t, Int32Value(b), int32(10)) +} + +func Test_ToString(t *testing.T) { + str := ToString(10) + utils.AssertEqual(t, "10", str) + + str = ToString("10") + utils.AssertEqual(t, "10", str) +} + +func Test_ToObject(t *testing.T) { + str := "{sdsfdsd:" + result := ToObject(str) + utils.AssertNil(t, result) + + input := map[string]string{ + "name": "test", + } + result = ToObject(input) + utils.AssertEqual(t, "test", result["name"].(string)) +} + +func Test_ToReader(t *testing.T) { + str := "abc" + reader := ToReader(String(str)) + byt, err := ioutil.ReadAll(reader) + utils.AssertNil(t, err) + utils.AssertEqual(t, "abc", string(byt)) + + read := strings.NewReader("bcd") + reader = ToReader(read) + byt, err = ioutil.ReadAll(reader) + utils.AssertNil(t, err) + utils.AssertEqual(t, "bcd", string(byt)) + + byts := []byte("cdf") + reader = ToReader(byts) + byt, err = ioutil.ReadAll(reader) + utils.AssertNil(t, err) + utils.AssertEqual(t, "cdf", string(byt)) + + num := 10 + defer func() { + err := recover() + utils.AssertEqual(t, "Invalid Body. Please set a valid Body.", err.(string)) + }() + reader = ToReader(num) + byt, err = ioutil.ReadAll(reader) + utils.AssertNil(t, err) + utils.AssertEqual(t, "", string(byt)) +} diff --git a/utils/array.go b/utils/array.go new file mode 100644 index 0000000..a94d039 --- /dev/null +++ b/utils/array.go @@ -0,0 +1,13 @@ +package utils + +func Contains(s []*string, str *string) bool { + if s == nil { + return false + } + for _, v := range s { + if str != nil && v != nil && *v == *str { + return true + } + } + return false +} diff --git a/utils/array_test.go b/utils/array_test.go new file mode 100644 index 0000000..a0d6d2b --- /dev/null +++ b/utils/array_test.go @@ -0,0 +1,21 @@ +package utils + +import ( + "testing" +) + +func Test_Contains(t *testing.T) { + apple := "apple" + banana := "banana" + cherry := "cherry" + slice := []*string{&apple, &banana, &cherry, nil} + AssertEqual(t, true, Contains(slice, &banana)) + toFind := "banana" + AssertEqual(t, true, Contains(slice, &toFind)) + notFind := "notFind" + AssertEqual(t, false, Contains(slice, ¬Find)) + notFind = "" + AssertEqual(t, false, Contains(slice, ¬Find)) + AssertEqual(t, false, Contains(slice, nil)) + AssertEqual(t, false, Contains(nil, nil)) +}