From cc29d398161ebfd6bcbd91ff3c0b301fcb5241f7 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 and support HttpClient interface --- go.mod | 2 +- go.sum | 17 +- tea/back_off_policy.go | 117 +++++++ tea/back_off_policy_test.go | 135 ++++++++ tea/{tea.go => core.go} | 361 ++++---------------- tea/{tea_test.go => core_test.go} | 486 +++++++++----------------- tea/error.go | 197 +++++++++++ tea/error_test.go | 149 ++++++++ tea/function.go | 81 +++++ tea/function_test.go | 132 +++++++ tea/retry.go | 116 +++++++ tea/retry_test.go | 547 ++++++++++++++++++++++++++++++ tea/trans.go | 75 ++++ tea/trans_test.go | 84 +++++ utils/array.go | 13 + utils/array_test.go | 21 ++ 16 files changed, 1893 insertions(+), 640 deletions(-) create mode 100644 tea/back_off_policy.go create mode 100644 tea/back_off_policy_test.go rename tea/{tea.go => core.go} (78%) rename tea/{tea_test.go => core_test.go} (68%) 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..8ce85bf 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,5 @@ require ( github.com/alibabacloud-go/debug v1.0.0 github.com/json-iterator/go v1.1.12 github.com/modern-go/reflect2 v1.0.2 - golang.org/x/net v0.20.0 + golang.org/x/net v0.22.0 ) diff --git a/go.sum b/go.sum index 5af806b..6394fd0 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA= github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -10,15 +9,14 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -26,8 +24,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -38,12 +37,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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..603b275 --- /dev/null +++ b/tea/back_off_policy_test.go @@ -0,0 +1,135 @@ +package tea + +import ( + "reflect" + "testing" + + "github.com/alibabacloud-go/tea/utils" +) + +func TestBackoffPolicy(t *testing.T) { + var backoffPolicy BackoffPolicy + backoffPolicy = NewBackoffPolicy(map[string]interface{}{ + "policy": "Any", + }) + utils.AssertEqual(t, nil, 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), + } + 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), + } + 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), + } + 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), + } + 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 78% rename from tea/tea.go rename to tea/core.go index 3fc9b08..ebf858d 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" @@ -31,6 +29,27 @@ import ( var debugLog = debug.Init("tea") +type HttpRequest interface { +} + +type HttpResponse interface { +} + +type HttpClient interface { + Call(request *http.Request) (response *http.Response, err error) +} + +type teaClient struct { + sync.Mutex + httpClient *http.Client + ifInit bool +} + +func (client *teaClient) Call(request *http.Request) (response *http.Response, err error) { + response, err = client.httpClient.Do(request) + return +} + var hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { return fn } @@ -42,11 +61,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 +81,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"` @@ -97,12 +99,8 @@ type RuntimeObject struct { Listener utils.ProgressListener `json:"listener" xml:"listener"` Tracker *utils.ReaderTracker `json:"tracker" xml:"tracker"` Logger *utils.Logger `json:"logger" xml:"logger"` -} - -type teaClient struct { - sync.Mutex - httpClient *http.Client - ifInit bool + RetryOptions *RetryOptions `json:"retryOptions" xml:"retryOptions"` + HttpClient } var clientPool = &sync.Map{} @@ -134,6 +132,9 @@ func NewRuntimeObject(runtime map[string]interface{}) *RuntimeObject { Cert: TransInterfaceToString(runtime["cert"]), CA: TransInterfaceToString(runtime["ca"]), } + if runtime["retryOptions"] != nil { + runtimeObject.RetryOptions = runtime["retryOptions"].(*RetryOptions) + } if runtime["listener"] != nil { runtimeObject.Listener = runtime["listener"].(utils.ProgressListener) } @@ -143,14 +144,10 @@ func NewRuntimeObject(runtime map[string]interface{}) *RuntimeObject { if runtime["logger"] != nil { runtimeObject.Logger = runtime["logger"].(*utils.Logger) } - return runtimeObject -} - -// NewCastError is used for cast type fails -func NewCastError(message *string) (err error) { - return &CastError{ - Message: message, + if runtime["httpClient"] != nil { + runtimeObject.HttpClient = runtime["httpClient"].(HttpClient) } + return runtimeObject } // NewRequest is used shortly create Request @@ -171,108 +168,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() @@ -303,9 +198,11 @@ func getTeaClient(tag string) *teaClient { return client.(*teaClient) } -// DoRequest is used send request to server -func DoRequest(request *Request, requestRuntime map[string]interface{}) (response *Response, err error) { - runtimeObject := NewRuntimeObject(requestRuntime) +// DoAction is used send request to server +func DoAction(request *Request, runtimeObject *RuntimeObject) (response *Response, err error) { + if runtimeObject == nil { + runtimeObject = &RuntimeObject{} + } fieldMap := make(map[string]string) utils.InitLogMsg(fieldMap) defer func() { @@ -350,19 +247,28 @@ func DoRequest(request *Request, requestRuntime map[string]interface{}) (respons return } httpRequest.Host = StringValue(request.Domain) + var client HttpClient + if runtimeObject.HttpClient == nil { + client = getTeaClient(runtimeObject.getClientTag(StringValue(request.Domain))) + } else { + client = runtimeObject.HttpClient + } - client := getTeaClient(runtimeObject.getClientTag(StringValue(request.Domain))) - client.Lock() - if !client.ifInit { - trans, err := getHttpTransport(request, runtimeObject) - if err != nil { - return nil, err + if defaultClient, ok := client.(*teaClient); ok { + defaultClient.Lock() + if !defaultClient.ifInit || defaultClient.httpClient.Transport == nil { + trans, err := getHttpTransport(request, runtimeObject) + if err != nil { + defaultClient.Unlock() + return nil, err + } + defaultClient.httpClient.Transport = trans } - client.httpClient.Timeout = time.Duration(IntValue(runtimeObject.ReadTimeout)) * time.Millisecond - client.httpClient.Transport = trans - client.ifInit = true + defaultClient.httpClient.Timeout = time.Duration(IntValue(runtimeObject.ReadTimeout)) * time.Millisecond + defaultClient.ifInit = true + defaultClient.Unlock() } - client.Unlock() + for key, value := range request.Headers { if value == nil || key == "content-length" { continue @@ -384,7 +290,7 @@ func DoRequest(request *Request, requestRuntime map[string]interface{}) (respons putMsgToMap(fieldMap, httpRequest) startTime := time.Now() fieldMap["{start_time}"] = startTime.Format("2006-01-02 15:04:05") - res, err := hookDo(client.httpClient.Do)(httpRequest) + res, err := hookDo(client.Call)(httpRequest) fieldMap["{cost}"] = time.Since(startTime).String() completedBytes := int64(0) if runtimeObject.Tracker != nil { @@ -412,6 +318,13 @@ func DoRequest(request *Request, requestRuntime map[string]interface{}) (respons return } +// DoRequest is used send request to server +func DoRequest(request *Request, requestRuntime map[string]interface{}) (response *Response, err error) { + runtimeObject := NewRuntimeObject(requestRuntime) + response, err = DoAction(request, runtimeObject) + return +} + func getHttpTransport(req *Request, runtime *RuntimeObject) (*http.Transport, error) { trans := new(http.Transport) httpProxy, err := getHttpProxy(StringValue(req.Protocol), StringValue(req.Domain), runtime) @@ -518,24 +431,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 +494,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 +657,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 +929,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 68% rename from tea/tea_test.go rename to tea/core_test.go index 272a030..6ddbf44 100644 --- a/tea/tea_test.go +++ b/tea/core_test.go @@ -23,27 +23,24 @@ 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, - "localAddr": "", - "httpProxy": "", - "httpsProxy": "", - "maxIdleConns": 0, - "socks5Proxy": "", - "socks5NetWork": "", - "listener": &Progresstest{}, - "tracker": &utils.ReaderTracker{CompletedBytes: int64(10)}, - "logger": utils.NewLogger("info", "", &bytes.Buffer{}, "{time}"), + "ignoreSSL": false, + "readTimeout": 0, + "connectTimeout": 0, + "localAddr": "", + "httpProxy": "", + "httpsProxy": "", + "noProxy": "", + "maxIdleConns": 0, + "socks5Proxy": "", + "socks5NetWork": "", + "listener": &Progresstest{}, + "tracker": &utils.ReaderTracker{CompletedBytes: int64(10)}, + "logger": utils.NewLogger("info", "", &bytes.Buffer{}, "{time}"), + "retryOptions": &RetryOptions{}, + "key": "", + "cert": "", + "ca": "", } type validateTest struct { @@ -105,11 +102,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,180 +119,28 @@ 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) runtimeobject = NewRuntimeObject(runtimeObj) 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) + utils.AssertEqual(t, 0, IntValue(runtimeobject.ReadTimeout)) + utils.AssertEqual(t, 0, IntValue(runtimeobject.ConnectTimeout)) + utils.AssertEqual(t, "", StringValue(runtimeobject.LocalAddr)) + utils.AssertEqual(t, "", StringValue(runtimeobject.HttpProxy)) + utils.AssertEqual(t, "", StringValue(runtimeobject.HttpsProxy)) + utils.AssertEqual(t, "", StringValue(runtimeobject.NoProxy)) + utils.AssertEqual(t, 0, IntValue(runtimeobject.MaxIdleConns)) + utils.AssertEqual(t, "", StringValue(runtimeobject.Key)) + utils.AssertEqual(t, "", StringValue(runtimeobject.Cert)) + utils.AssertEqual(t, "", StringValue(runtimeobject.CA)) + utils.AssertEqual(t, "", StringValue(runtimeobject.Socks5Proxy)) + utils.AssertEqual(t, "", StringValue(runtimeobject.Socks5NetWork)) + utils.AssertNotNil(t, runtimeobject.Listener) + utils.AssertNotNil(t, runtimeobject.Tracker) + utils.AssertNotNil(t, runtimeobject.Logger) + utils.AssertNotNil(t, runtimeobject.RetryOptions) } type Test struct { @@ -314,7 +154,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 +230,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 @@ -594,7 +371,125 @@ func Test_DoRequest(t *testing.T) { utils.AssertEqual(t, `Internal error`, err.Error()) } -func Test_DoRequestWithConcurrent(t *testing.T) { +func Test_DoAction(t *testing.T) { + origTestHookDo := hookDo + defer func() { hookDo = origTestHookDo }() + hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { + return func(req *http.Request) (*http.Response, error) { + return mockResponse(200, ``, errors.New("Internal error")) + } + } + request := NewRequest() + request.Method = String("TEA TEST") + resp, err := DoAction(request, nil) + utils.AssertNil(t, resp) + utils.AssertEqual(t, `net/http: invalid method "TEA TEST"`, err.Error()) + + request.Method = String("") + request.Protocol = String("https") + request.Query = map[string]*string{ + "tea": String("test"), + } + runtimeObj["httpsProxy"] = "# #%gfdf" + runtimeObject := NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, resp) + utils.AssertContains(t, err.Error(), `invalid URL escape "%gf"`) + + request.Pathname = String("?log") + request.Headers["tea"] = String("") + request.Headers["content-length"] = nil + runtimeObj["httpsProxy"] = "http://someuser:somepassword@ecs.aliyun.com" + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, resp) + utils.AssertEqual(t, `Internal error`, err.Error()) + + request.Headers["host"] = String("tea-cn-hangzhou.aliyuncs.com:80") + request.Headers["user-agent"] = String("test") + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, resp) + utils.AssertEqual(t, `Internal error`, err.Error()) + + runtimeObj["socks5Proxy"] = "# #%gfdf" + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, resp) + utils.AssertContains(t, err.Error(), ` invalid URL escape "%gf"`) + + hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { + return func(req *http.Request) (*http.Response, error) { + return mockResponse(200, ``, nil) + } + } + runtimeObj["socks5Proxy"] = "socks5://someuser:somepassword@ecs.aliyun.com" + runtimeObj["localAddr"] = "127.0.0.1" + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, err) + utils.AssertEqual(t, "test", StringValue(resp.Headers["tea"])) + + runtimeObj["key"] = "private rsa key" + runtimeObj["cert"] = "private certification" + runtimeObj["ca"] = "private ca" + runtimeObj["ignoreSSL"] = true + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, err) + utils.AssertNotNil(t, resp) + + // update the host is to restart a client + request.Headers["host"] = String("a.com") + runtimeObj["ignoreSSL"] = false + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "tls: failed to find any PEM data in certificate input", err.Error()) + utils.AssertNil(t, resp) + + // update the host is to restart a client + request.Headers["host"] = String("b.com") + runtimeObj["key"] = key + runtimeObj["cert"] = cert + runtimeObj["ca"] = "private ca" + runtimeObject = NewRuntimeObject(runtimeObj) + _, err = DoAction(request, runtimeObject) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "Failed to parse root certificate", err.Error()) + + // update the host is to restart a client + request.Headers["host"] = String("c.com") + runtimeObj["ca"] = ca + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, err) + utils.AssertEqual(t, "test", StringValue(resp.Headers["tea"])) + + request.Protocol = String("HTTP") + runtimeObj["ignoreSSL"] = false + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, err) + utils.AssertEqual(t, "test", StringValue(resp.Headers["tea"])) + + hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { + return func(req *http.Request) (*http.Response, error) { + utils.AssertEqual(t, "tea-cn-hangzhou.aliyuncs.com:1080", req.Host) + return mockResponse(200, ``, errors.New("Internal error")) + } + } + request.Pathname = String("/log") + request.Protocol = String("http") + request.Port = Int(1080) + request.Headers["host"] = String("tea-cn-hangzhou.aliyuncs.com") + runtimeObject = NewRuntimeObject(runtimeObj) + resp, err = DoAction(request, runtimeObject) + utils.AssertNil(t, resp) + utils.AssertEqual(t, `Internal error`, err.Error()) +} + +func Test_DoActionWithConcurrent(t *testing.T) { origTestHookDo := hookDo defer func() { hookDo = origTestHookDo }() hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { @@ -609,11 +504,12 @@ func Test_DoRequestWithConcurrent(t *testing.T) { runtime := map[string]interface{}{ "readTimeout": readTimeout, } + runtimeObject := NewRuntimeObject(runtime) for j := 0; j < 50; j++ { wg.Add(1) go func() { request := NewRequest() - resp, err := DoRequest(request, runtime) + resp, err := DoAction(request, runtimeObject) utils.AssertNil(t, err) utils.AssertNotNil(t, resp) wg.Done() @@ -703,44 +599,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 +618,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 +734,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..d87dda0 --- /dev/null +++ b/tea/error.go @@ -0,0 +1,197 @@ +package tea + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" +) + +// BaseError is an interface for getting actual error +type BaseError interface { + error + ErrorName() *string + ErrorCode() *string +} + +// CastError is used for cast type fails +type CastError struct { + BaseError + Message *string + Code *string +} + +// NewCastError is used for cast type fails +func NewCastError(message *string) *CastError { + return &CastError{ + Message: message, + Code: nil, + } +} + +// Return message of CastError +func (err *CastError) Error() string { + return StringValue(err.Message) +} + +func (err *CastError) ErrorName() *string { + return String("CastError") +} + +func (err *CastError) ErrorCode() *string { + return err.Code +} + +// SDKError struct is used save error code and message, use as ResponseError +type SDKError struct { + BaseError + Name string + Code *string + StatusCode *int + Message *string + Data *string + Stack *string + errMsg *string + Description *string + AccessDeniedDetail map[string]interface{} + RequestId *string + Retryable *bool + RetryAfter *int64 +} + +// 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 +} + +func NewError(name string, obj map[string]interface{}) *SDKError { + err := &SDKError{ + Name: name, + } + 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 obj["statusCode"] != nil { + err.StatusCode = Int(obj["statusCode"].(int)) + } + if obj["requestId"] != nil { + err.RequestId = String(obj["requestId"].(string)) + } + if obj["retryable"] != nil { + err.Retryable = Bool(obj["retryable"].(bool)) + } + if obj["retryAfter"] != nil { + err.RetryAfter = Int64(obj["retryAfter"].(int64)) + } + if data := obj["data"]; data != nil { + byt := bytes.NewBuffer([]byte{}) + jsonEncoder := json.NewEncoder(byt) + jsonEncoder.SetEscapeHTML(false) + jsonEncoder.Encode(data) + err.Data = String(string(bytes.TrimSpace(byt.Bytes()))) + } + 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 String(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..d730e91 --- /dev/null +++ b/tea/error_test.go @@ -0,0 +1,149 @@ +package tea + +import ( + "testing" + + "github.com/alibabacloud-go/tea/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": 400, + "message": "message", + "data": "string data", + }) + utils.AssertNotNil(t, err) + utils.AssertNotNil(t, err.Data) + utils.AssertEqual(t, "400", StringValue(err.Code)) + 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, "", StringValue(err.ErrorName())) +} + +func TestNewError(t *testing.T) { + var err0 BaseError + err0 = NewError("ResponseError", 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, + }, + "requestId": "123456", + "retryable": true, + "retryAfter": int64(100), + }) + 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"]) +} diff --git a/tea/function.go b/tea/function.go new file mode 100644 index 0000000..6bd3296 --- /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 *int64) { + sleeptime := time.Duration(Int64Value(backoffTime)) * time.Millisecond + 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..be9d355 --- /dev/null +++ b/tea/function_test.go @@ -0,0 +1,132 @@ +package tea + +import ( + "errors" + "testing" + "time" + + "github.com/alibabacloud-go/tea/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_Sleep(t *testing.T) { + start := time.Now() + Sleep(Int64(1000)) + Sleep(Int64(0)) + Sleep(nil) + cost := time.Since(start) + utils.AssertEqual(t, cost.Seconds() >= 1, true) +} + +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/retry.go b/tea/retry.go new file mode 100644 index 0000000..97dcc04 --- /dev/null +++ b/tea/retry.go @@ -0,0 +1,116 @@ +package tea + +import ( + "math" + "time" + + "github.com/alibabacloud-go/tea/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 +) + +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 error +} + +func ShouldRetry(options *RetryOptions, ctx *RetryPolicyContext) *bool { + if IntValue(ctx.RetriesAttempted) == 0 { + return Bool(true) + } + if options == nil || !BoolValue(options.Retryable) { + return Bool(false) + } + + if err, ok := ctx.Error.(BaseError); ok { + 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) + } + if err1, ok := err.(*SDKError); ok { + if BoolValue(err1.Retryable) == false { + return Bool(false) + } + } + return Bool(true) + } + } + } + return Bool(false) +} + +func GetBackoffDelay(options *RetryOptions, ctx *RetryPolicyContext) *int64 { + if IntValue(ctx.RetriesAttempted) == 0 { + return Int64(0) + } + + if err, ok := ctx.Error.(BaseError); ok { + 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 err1, ok := err.(*SDKError); ok { + if err1.RetryAfter != nil { + return Int64(int64(math.Min(float64(Int64Value(err1.RetryAfter)), float64(maxDelay)))) + } + } + + if retryCondition.Backoff == nil { + return Int64(DefaultMinDelay.Milliseconds()) + } + delayTimeMillis := (*retryCondition.Backoff).GetDelayTimeMillis(ctx) + return Int64(int64(math.Min(float64(Int64Value(delayTimeMillis)), float64(maxDelay)))) + } + } + } + } + return Int64(DefaultMinDelay.Milliseconds()) +} diff --git a/tea/retry_test.go b/tea/retry_test.go new file mode 100644 index 0000000..191c726 --- /dev/null +++ b/tea/retry_test.go @@ -0,0 +1,547 @@ +package tea + +import ( + "testing" + + "github.com/alibabacloud-go/tea/utils" +) + +type AErrorTest struct { + BaseError + 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 { + BaseError + 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 { + BaseError + 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_ThrottlingShouldRetry(t *testing.T) { + var backoffPolicy BackoffPolicy + backoffPolicy = &ExponentialBackoffPolicy{ + Period: Int(2), + Cap: Int64(60 * 1000), + } + retryCondition := RetryCondition{ + MaxAttempts: Int(1), + Backoff: &backoffPolicy, + Exception: []*string{String("ThrottlingError")}, + } + + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: NewError("", map[string]interface{}{}), + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(nil, &retryPolicyContext))) + + retryOptions := RetryOptions{ + Retryable: Bool(false), + RetryCondition: []*RetryCondition{&retryCondition}, + NoRetryCondition: nil, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition}, + NoRetryCondition: nil, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError := NewError("", map[string]interface{}{ + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "retryable": false, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + retryCondition = RetryCondition{ + MaxAttempts: Int(1), + Backoff: &backoffPolicy, + ErrorCode: []*string{String("Throttling"), String("Throttling.User"), String("Throttling.Api")}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition}, + NoRetryCondition: nil, + } + utils.AssertEqual(t, false, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "code": "Throttling", + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "code": "Throttling.User", + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, true, BoolValue(ShouldRetry(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "code": "Throttling.Api", + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, true, 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))) + + retryCondition4 = RetryCondition{ + MaxAttempts: Int(20), + Backoff: nil, + Exception: []*string{String("AErrorTest")}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition4}, + } + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(10), + Error: &AErrorTest{}, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) +} + +func Test_GetThrottlingBackoffDelay(t *testing.T) { + var backoffPolicy BackoffPolicy + backoffPolicy = &ExponentialBackoffPolicy{ + Period: Int(200), + Cap: Int64(60 * 1000), + } + retryCondition := RetryCondition{ + MaxAttempts: Int(1), + Backoff: &backoffPolicy, + Exception: []*string{String("ThrottlingError")}, + } + + retryPolicyContext := RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: NewError("", map[string]interface{}{}), + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(nil, &retryPolicyContext))) + + retryOptions := RetryOptions{ + Retryable: Bool(false), + RetryCondition: []*RetryCondition{&retryCondition}, + NoRetryCondition: nil, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition}, + NoRetryCondition: nil, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + throttlingError := NewError("ThrottlingError", map[string]interface{}{}) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, int64(400), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, int64(2000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "retryable": true, + "retryAfter": int64(320 * 1000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, int64(120000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + retryCondition = RetryCondition{ + MaxAttempts: Int(1), + Backoff: &backoffPolicy, + ErrorCode: []*string{String("Throttling"), String("Throttling.User"), String("Throttling.Api")}, + } + retryOptions = RetryOptions{ + Retryable: Bool(true), + RetryCondition: []*RetryCondition{&retryCondition}, + NoRetryCondition: nil, + } + utils.AssertEqual(t, int64(100), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "code": "Throttling", + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, int64(2000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "code": "Throttling.User", + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, int64(2000), Int64Value(GetBackoffDelay(&retryOptions, &retryPolicyContext))) + + throttlingError = NewError("ThrottlingError", map[string]interface{}{ + "code": "Throttling.Api", + "retryable": true, + "retryAfter": int64(2000), + }) + retryPolicyContext = RetryPolicyContext{ + RetriesAttempted: Int(1), + Error: throttlingError, + } + utils.AssertEqual(t, int64(2000), 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..d25c5ee 100644 --- a/tea/trans_test.go +++ b/tea/trans_test.go @@ -1,6 +1,8 @@ package tea import ( + "io/ioutil" + "strings" "testing" "github.com/alibabacloud-go/tea/utils" @@ -161,3 +163,85 @@ func Test_Trans(t *testing.T) { utils.AssertNil(t, Uint64Slice(nil)) utils.AssertNil(t, Uint64ValueSlice(nil)) } + +func Test_TransInterfaceToInt(t *testing.T) { + a := TransInterfaceToInt(nil) + utils.AssertNil(t, a) + + a = TransInterfaceToInt(10) + utils.AssertEqual(t, IntValue(a), 10) +} + +func Test_TransInterfaceToInt64(t *testing.T) { + a := TransInterfaceToInt64(nil) + utils.AssertNil(t, a) + + a = TransInterfaceToInt64(int64(10)) + utils.AssertEqual(t, Int64Value(a), int64(10)) +} + +func Test_TransInterfaceToString(t *testing.T) { + a := TransInterfaceToString(nil) + utils.AssertNil(t, a) + + a = TransInterfaceToString("10") + utils.AssertEqual(t, StringValue(a), "10") +} + +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)) +}