diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b4700de..05e2d52 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,13 +24,13 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v2 - - name: Build Tea - run: go build ./tea + - name: Build Darabonba + run: go build ./dara - name: Build Util run: go build ./utils - name: Test - run: go test -race -coverprofile=coverage.txt -covermode=atomic ./tea/... ./utils/... + run: go test -race -coverprofile=coverage.txt -covermode=atomic ./dara/... ./utils/... ./tea/... - name: CodeCov run: bash <(curl -s https://codecov.io/bash) diff --git a/.gitignore b/.gitignore index 4497994..21c226d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ coverage.html coverage.txt coverage.out .DS_Store -.history/ \ No newline at end of file +.history/ +vendor/ \ No newline at end of file diff --git a/Makefile b/Makefile index 5e9a8dc..70ea100 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ all: fmt: - go fmt ./ + go fmt ./tea ./dara ./utils test: - go test -race -coverprofile=coverage.txt -covermode=atomic ./tea ./utils + go test -race -coverprofile=coverage.txt -covermode=atomic ./tea ./utils ./dara go tool cover -html=coverage.txt -o coverage.html diff --git a/dara/array.go b/dara/array.go new file mode 100644 index 0000000..6e7b4fc --- /dev/null +++ b/dara/array.go @@ -0,0 +1,419 @@ +package dara + +import ( + "fmt" + "reflect" + "sort" + "strings" +) + +// ArrContains checks if an element is in the array +func ArrContains(arr interface{}, value interface{}) bool { + arrValue := reflect.ValueOf(arr) + valueValue := reflect.ValueOf(value) + + // Ensure arr is a slice + if arrValue.Kind() != reflect.Slice { + return false + } + + for i := 0; i < arrValue.Len(); i++ { + elem := arrValue.Index(i) + + // Ensure the array element is a pointer + if elem.Kind() == reflect.Ptr { + if valueValue.Kind() == reflect.Ptr && elem.Pointer() == valueValue.Pointer() { + return true + } + if elem.Elem().Interface() == valueValue.Interface() { + return true + } + } else if elem.Kind() == reflect.Interface { + elem = elem.Elem() + if valueValue.Kind() == reflect.Ptr && elem.Interface() == valueValue.Pointer() { + return true + } + if elem.Interface() == valueValue.Interface() { + return true // Return the index if found + } + } + } + + return false +} + +// ArrIndex returns the index of the element in the array +func ArrIndex(arr interface{}, value interface{}) int { + arrValue := reflect.ValueOf(arr) + valueValue := reflect.ValueOf(value) + + // Ensure arr is a slice + if arrValue.Kind() != reflect.Slice { + return -1 + } + + for i := 0; i < arrValue.Len(); i++ { + elem := arrValue.Index(i) + + // Ensure the array element is a pointer + if elem.Kind() == reflect.Ptr { + // Dereference the pointer to get the underlying value + if valueValue.Kind() == reflect.Ptr && elem.Pointer() == valueValue.Pointer() { + return i + } + if elem.Elem().Interface() == valueValue.Interface() { + return i // Return the index if found + } + } else if elem.Kind() == reflect.Interface { + elem = elem.Elem() + if valueValue.Kind() == reflect.Ptr && elem.Interface() == valueValue.Pointer() { + return i + } + if elem.Interface() == valueValue.Interface() { + return i // Return the index if found + } + } + } + + return -1 // Return -1 if not found +} + +func handlePointer(elem reflect.Value) string { + if elem.IsNil() { + return "" // Skip nil pointers + } + + // Dereference the pointer + elem = elem.Elem() + return handleValue(elem) +} + +func handleValue(elem reflect.Value) string { + switch elem.Kind() { + case reflect.String: + return elem.String() + case reflect.Int: + return fmt.Sprintf("%d", elem.Interface()) + case reflect.Float64: + return fmt.Sprintf("%f", elem.Interface()) + case reflect.Bool: + return fmt.Sprintf("%t", elem.Interface()) + default: + return "" // Skip unsupported types + } +} + +func ArrJoin(arr interface{}, sep string) string { + var strSlice []string + var str string + + arrValue := reflect.ValueOf(arr) + + // Ensure arr is a slice + if arrValue.Kind() != reflect.Slice { + return "" + } + + for i := 0; i < arrValue.Len(); i++ { + elem := arrValue.Index(i) + + if elem.Kind() == reflect.Ptr { + str = handlePointer(elem) + } else if elem.Kind() == reflect.Interface { + str = handleValue(elem.Elem()) + } else { + str = handleValue(elem) + } + + if str != "" { + strSlice = append(strSlice, str) + } + } + + return strings.Join(strSlice, sep) +} + +// ArrShift removes the first element from the array +func ArrShift(arr interface{}) interface{} { + arrValue := reflect.ValueOf(arr) + + // Ensure arr is a pointer to a slice + if arrValue.Kind() != reflect.Ptr || arrValue.Elem().Kind() != reflect.Slice { + return nil + } + + // Get the slice from the pointer + sliceValue := arrValue.Elem() + + // Ensure the slice is not empty + if sliceValue.Len() == 0 { + return nil + } + + // Get the first element + firstElem := sliceValue.Index(0) + + // Create a new slice with one less element + newArrValue := reflect.MakeSlice(sliceValue.Type(), sliceValue.Len()-1, sliceValue.Cap()) + + // Copy the elements after the first one to the new slice + reflect.Copy(newArrValue, sliceValue.Slice(1, sliceValue.Len())) + + // Set the original slice to the new slice + sliceValue.Set(newArrValue) + + // Return the removed first element + return firstElem.Interface() +} + +// ArrPop removes the last element from the array +func ArrPop(arr interface{}) interface{} { + arrValue := reflect.ValueOf(arr) + + // Ensure arr is a pointer to a slice + if arrValue.Kind() != reflect.Ptr || arrValue.Elem().Kind() != reflect.Slice { + return nil + } + + // Get the slice from the pointer + sliceValue := arrValue.Elem() + + // Ensure the slice is not empty + if sliceValue.Len() == 0 { + return nil + } + + // Get the last element + lastIndex := sliceValue.Len() - 1 + lastElem := sliceValue.Index(lastIndex) + + // Create a new slice with one less element + newArrValue := reflect.MakeSlice(sliceValue.Type(), sliceValue.Len()-1, sliceValue.Cap()-1) + + // Copy the elements before the last one to the new slice + reflect.Copy(newArrValue, sliceValue.Slice(0, lastIndex)) + + // Set the original slice to the new slice + sliceValue.Set(newArrValue) + + // Return the removed last element + return lastElem.Interface() +} + +// ArrUnshift adds an element to the beginning of the array +func ArrUnshift(arr interface{}, value interface{}) int { + arrValue := reflect.ValueOf(arr) + + // Ensure arr is a pointer to a slice + if arrValue.Kind() != reflect.Ptr || arrValue.Elem().Kind() != reflect.Slice { + return 0 + } + + // Get the slice from the pointer + sliceValue := arrValue.Elem() + + // Create a new slice with one additional element + newArrValue := reflect.MakeSlice(sliceValue.Type(), sliceValue.Len()+1, sliceValue.Cap()+1) + + // Set the new element as the first element + newArrValue.Index(0).Set(reflect.ValueOf(value)) + + // Copy the old elements to the new slice, starting at index 1 + reflect.Copy(newArrValue.Slice(1, newArrValue.Len()), sliceValue) + + // Set the original slice to the new slice + sliceValue.Set(newArrValue) + + // Return the new length of the slice + return newArrValue.Len() +} + +// ArrPush adds an element to the end of the array +func ArrPush(arr interface{}, value interface{}) int { + arrValue := reflect.ValueOf(arr) + + // Ensure arr is a pointer to a slice + if arrValue.Kind() != reflect.Ptr || arrValue.Elem().Kind() != reflect.Slice { + return 0 + } + + // Get the slice from the pointer + sliceValue := arrValue.Elem() + + // Create a new slice with one additional element + newArrValue := reflect.MakeSlice(sliceValue.Type(), sliceValue.Len()+1, sliceValue.Cap()+1) + + // Copy the old elements to the new slice + reflect.Copy(newArrValue, sliceValue) + + // Set the new element as the last element + newArrValue.Index(sliceValue.Len()).Set(reflect.ValueOf(value)) + + // Set the original slice to the new slice + sliceValue.Set(newArrValue) + + // Return the new length of the slice + return newArrValue.Len() +} + +// ConcatArr concatenates two arrays +func ConcatArr(arr1 interface{}, arr2 interface{}) interface{} { + var result []interface{} + value1 := reflect.ValueOf(arr1) + value2 := reflect.ValueOf(arr2) + + // 检查 arr1 和 arr2 是否为切片 + if value1.Kind() != reflect.Slice || value2.Kind() != reflect.Slice { + panic("ConcatArr: both inputs must be slices") + } + + // 如果两个切片的类型相同 + if value1.Type() == value2.Type() { + // 创建一个新的切片,类型与输入切片相同 + result := reflect.MakeSlice(value1.Type(), 0, value1.Len()+value2.Len()) + + // 复制第一个切片的元素 + for i := 0; i < value1.Len(); i++ { + result = reflect.Append(result, value1.Index(i)) + } + // 复制第二个切片的元素 + for i := 0; i < value2.Len(); i++ { + result = reflect.Append(result, value2.Index(i)) + } + return result.Interface() // 返回类型相同的切片 + } + + // 否则返回 []interface{} + for i := 0; i < value1.Len(); i++ { + result = append(result, value1.Index(i).Interface()) + } + for i := 0; i < value2.Len(); i++ { + result = append(result, value2.Index(i).Interface()) + } + return result +} + +// ArrAppend inserts a new pointer at a specified index in a pointer array. +func ArrAppend(arr interface{}, value interface{}) { + ArrPush(arr, value) +} + +// ArrRemove removes an element from the array +func ArrRemove(arr interface{}, value interface{}) { + arrValue := reflect.ValueOf(arr) + + // Ensure arr is a pointer to a slice + if arrValue.Kind() != reflect.Ptr || arrValue.Elem().Kind() != reflect.Slice { + return + } + + // Get the slice from the pointer + slice := arrValue.Elem() + index := ArrIndex(slice.Interface(), value) + // If index is found, remove the element at that index + if index != -1 { + // Remove the element at the specified index + newSlice := reflect.MakeSlice(slice.Type(), 0, slice.Len()-1) + + // Copy elements before the index + newSlice = reflect.AppendSlice(slice.Slice(0, index), slice.Slice(index+1, slice.Len())) + // Set the new slice back to the original reference + slice.Set(newSlice) + } +} + +func SortArr(arr interface{}, order string) interface{} { + v := reflect.ValueOf(arr) + if v.Kind() != reflect.Slice { + panic("SortArr: input must be a slice") + } + + // 创建一个新的切片来存储排序结果 + newSlice := reflect.MakeSlice(v.Type(), v.Len(), v.Cap()) + for i := 0; i < v.Len(); i++ { + newSlice.Index(i).Set(v.Index(i)) + } + + order = strings.ToLower(order) + + sort.SliceStable(newSlice.Interface(), func(i, j int) bool { + return compare(newSlice.Index(i), newSlice.Index(j), order) + }) + + return newSlice.Interface() +} + +func compare(elemI, elemJ reflect.Value, order string) bool { + valI := reflect.Indirect(elemI) + valJ := reflect.Indirect(elemJ) + + // 对interface{}类型处理实际类型 + if elemI.Kind() == reflect.Interface { + valI = reflect.Indirect(elemI.Elem()) + } + if elemJ.Kind() == reflect.Interface { + valJ = reflect.Indirect(elemJ.Elem()) + } + + if valI.Kind() != valJ.Kind() { + + if order == "asc" { + return valI.Kind() < valJ.Kind() + } + return valI.Kind() > valJ.Kind() + + } + + switch kind := valI.Kind(); kind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return compareNumbers(valI.Int(), valJ.Int(), order) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return compareNumbers(int64(valI.Uint()), int64(valJ.Uint()), order) + case reflect.Float32, reflect.Float64: + return compareNumbers(valI.Float(), valJ.Float(), order) + case reflect.String: + return compareStrings(valI.String(), valJ.String(), order) + case reflect.Struct: + return compareStructs(valI, valJ, order) + default: + panic("SortArr: unsupported element type") + } +} + +func compareNumbers(a, b interface{}, order string) bool { + switch order { + case "asc": + return a.(int64) < b.(int64) + case "desc": + return a.(int64) > b.(int64) + default: + return a.(int64) > b.(int64) + } +} + +func compareStrings(a, b string, order string) bool { + switch order { + case "asc": + return a < b + case "desc": + return a > b + default: + return a > b + } +} + +func compareStructs(valI, valJ reflect.Value, order string) bool { + if valI.NumField() > 0 && valJ.NumField() > 0 { + fieldI := reflect.Indirect(valI.Field(0)) + fieldJ := reflect.Indirect(valJ.Field(0)) + if fieldI.Kind() == fieldJ.Kind() { + switch fieldI.Kind() { + case reflect.String: + return compareStrings(fieldI.String(), fieldJ.String(), order) + case reflect.Int: + return compareNumbers(fieldI.Int(), fieldJ.Int(), order) + } + } + } + return false +} diff --git a/dara/array_test.go b/dara/array_test.go new file mode 100644 index 0000000..291695b --- /dev/null +++ b/dara/array_test.go @@ -0,0 +1,549 @@ +package dara + +import ( + "reflect" + "testing" +) + +type MyStruct struct { + Name string +} + +func TestArrContains(t *testing.T) { + // Create test data + str1 := "Hello" + str2 := "World" + ptrStrArr := []*string{&str1, &str2} + + // Test with string pointer array + if !ArrContains(ptrStrArr, "World") { + t.Errorf("Expected true, but got false") + } + + if ArrContains(ptrStrArr, "Go") { + t.Errorf("Expected false, but got true") + } + + // Create integer pointer values + num1 := 1 + num2 := 2 + ptrIntArr := []*int{&num1, &num2} + + // Test with integer pointer array + if !ArrContains(ptrIntArr, 2) { + t.Errorf("Expected true, but got false") + } + + if ArrContains(ptrIntArr, 3) { + t.Errorf("Expected false, but got true") + } + + // Create struct pointers + struct1 := &MyStruct{Name: "One"} + struct2 := &MyStruct{Name: "Two"} + structPtrArr := []*MyStruct{struct1, struct2} + + // Test struct pointer array + if ArrContains(structPtrArr, &MyStruct{Name: "One"}) { + t.Errorf("Expected false, but got true") + } + + // Check for existence by value + if ArrContains(structPtrArr, "One") { + t.Errorf("Expected false, but got true") + } + + if !ArrContains(structPtrArr, struct1) { + t.Errorf("Expected true, but got false") + } + + interfaceArr := []interface{}{str1, num1, struct1} + + if !ArrContains(interfaceArr, "Hello") { + t.Errorf("Expected true, but got false") + } + + if ArrContains(interfaceArr, "World") { + t.Errorf("Expected false, but got true") + } + + if !ArrContains(interfaceArr, 1) { + t.Errorf("Expected true, but got false") + } + + if ArrContains(interfaceArr, 2) { + t.Errorf("Expected false, but got true") + } + + if ArrContains(interfaceArr, &MyStruct{Name: "One"}) { + t.Errorf("Expected false, but got true") + } + + if !ArrContains(interfaceArr, struct1) { + t.Errorf("Expected true, but got false") + } +} + +func TestArrIndex(t *testing.T) { + // Create test data for string pointer array + str1 := "Hello" + str2 := "World" + ptrStrArr := []*string{&str1, &str2} + + // Test with string pointer array + if index := ArrIndex(ptrStrArr, "World"); index != 1 { + t.Errorf("Expected index 1, but got %d", index) + } + + if index := ArrIndex(ptrStrArr, "Go"); index != -1 { + t.Errorf("Expected index -1, but got %d", index) + } + + // Create integer pointer values + num1 := 1 + num2 := 2 + ptrIntArr := []*int{&num1, &num2} + + // Test with integer pointer array + if index := ArrIndex(ptrIntArr, 2); index != 1 { + t.Errorf("Expected index 1, but got %d", index) + } + + if index := ArrIndex(ptrIntArr, 3); index != -1 { + t.Errorf("Expected index -1, but got %d", index) + } + + // Create struct pointers + struct1 := &MyStruct{Name: "One"} + struct2 := &MyStruct{Name: "Two"} + structPtrArr := []*MyStruct{struct1, struct2} + + // Test struct pointer array + if index := ArrIndex(structPtrArr, &MyStruct{Name: "One"}); index != -1 { + t.Errorf("Expected index -1, but got %d", index) + } + + interfaceArr := []interface{}{str1, num1, struct1} + + if index := ArrIndex(interfaceArr, 1); index != 1 { + t.Errorf("Expected index 1, but got %d", index) + } + + if index := ArrIndex(interfaceArr, "Hello"); index != 0 { + t.Errorf("Expected index 0, but got %d", index) + } + + if index := ArrIndex(interfaceArr, struct1); index != 2 { + t.Errorf("Expected index 2, but got %d", index) + } +} + +func TestArrJoin(t *testing.T) { + // Create test data + str1 := "Hello" + str2 := "World" + ptrStrArr := []*string{&str1, &str2} + + // Test joining strings + result := ArrJoin(ptrStrArr, ", ") + expected := "Hello, World" + if result != expected { + t.Errorf("Expected '%s', but got '%s'", expected, result) + } + + // Create integer pointer values + num1 := 1 + num2 := 2 + ptrIntArr := []*int{&num1, &num2} + + // Test joining integers + result = ArrJoin(ptrIntArr, " + ") + expected = "1 + 2" + if result != expected { + t.Errorf("Expected '%s', but got '%s'", expected, result) + } + + // Create mixed types (if needed) + struct1 := &MyStruct{Name: "One"} + ptrMixedArr := []interface{}{str1, num1, struct1} + + // Test joining mixed types + result = ArrJoin(ptrMixedArr, " | ") + expected = "Hello | 1" + if result != expected { + t.Errorf("Expected '%s', but got '%s'", expected, result) + } +} + +func TestArrShift(t *testing.T) { + // Create test data for string pointer array + str1 := "Hello" + str2 := "World" + ptrStrArr := []*string{&str1, &str2} + + // Test shifting strings + removed := ArrShift(&ptrStrArr) + if removed != &str1 { + t.Errorf("Expected '%v', but got '%v'", &str1, removed) + } + + // After shifting, the first element should now be "World" + if ptrStrArr[0] != &str2 { + t.Errorf("Expected next element to be '%v', but got '%v'", &str2, ptrStrArr[0]) + } + + // Create integer pointer values + num1 := 1 + num2 := 2 + ptrIntArr := []*int{&num1, &num2} + + // Test shifting integers + removedInt := ArrShift(&ptrIntArr) + if removedInt != &num1 { + t.Errorf("Expected '%v', but got '%v'", &num1, removedInt) + } + + // Create struct pointers + struct1 := &MyStruct{Name: "One"} + struct2 := &MyStruct{Name: "Two"} + structPtrArr := []*MyStruct{struct1, struct2} + + // Test struct pointer array + removedStruct := ArrShift(&structPtrArr) + if removedStruct != struct1 { + t.Errorf("Expected '%v', but got '%v'", struct1, removedStruct) + } + + interfaceArr := []interface{}{str1, num1, struct1} + + removedStr := ArrShift(&interfaceArr) + + if removedStr != str1 { + t.Errorf("Expected '%v', but got '%v'", str1, removedStr) + } + + removedInt = ArrShift(&interfaceArr) + + if removedInt != num1 { + t.Errorf("Expected '%v', but got '%v'", removedInt, num1) + } + + if interfaceArr[0] != struct1 { + t.Errorf("Expected next element to be '%v', but got '%v'", struct1, interfaceArr[0]) + } +} + +func TestArrPop(t *testing.T) { + // Create test data for string pointer array + str1 := "Hello" + str2 := "World" + ptrStrArr := []*string{&str1, &str2} + + // Test popping strings + removed := ArrPop(&ptrStrArr) + if removed != &str2 { + t.Errorf("Expected '%v', but got '%v'", &str2, removed) + } + + // After popping, the array should only contain "Hello" + if len(ptrStrArr) != 1 || ptrStrArr[0] != &str1 { + t.Errorf("Expected remaining element to be '%v', but got '%v'", &str1, ptrStrArr) + } + + // Create integer pointer values + num1 := 1 + num2 := 2 + ptrIntArr := []*int{&num1, &num2} + + // Test popping integers + removedInt := ArrPop(&ptrIntArr) + if removedInt != &num2 { + t.Errorf("Expected '%v', but got '%v'", &num2, removedInt) + } + + // Create struct pointers + struct1 := &MyStruct{Name: "One"} + struct2 := &MyStruct{Name: "Two"} + structPtrArr := []*MyStruct{struct1, struct2} + + // Test struct pointer array + removedStruct := ArrPop(&structPtrArr) + if removedStruct != struct2 { + t.Errorf("Expected '%v', but got '%v'", struct2, removedStruct) + } +} + +func TestArrUnshift(t *testing.T) { + // Create test data for string pointer array + str1 := "World" + ptrStrArr := []*string{&str1} + + // New string to be added + str2 := "Hello" + + // Test unshifting strings + length := ArrUnshift(&ptrStrArr, &str2) + if length != 2 { + t.Fatalf("Expected array length is 2 but %d", length) + } + + if ptrStrArr[0] != &str2 || ptrStrArr[1] != &str1 { + t.Errorf("Expected '%v', '%v', but got '%v'", &str2, &str1, ptrStrArr) + } + + // Test unshifting integers + num1 := 2 + ptrIntArr := []*int{&num1} + num2 := 1 + + length = ArrUnshift(&ptrIntArr, &num2) + if length != 2 { + t.Fatalf("Expected array length is 2 but %d", length) + } + + if ptrIntArr[0] != &num2 || ptrIntArr[1] != &num1 { + t.Errorf("Expected '%v', '%v', but got '%v'", &num2, &num1, ptrIntArr) + } + + ptrMixedArr := []interface{}{str1, num1} + struct1 := &MyStruct{Name: "One"} + + length = ArrUnshift(&ptrMixedArr, struct1) + + if length != 3 { + t.Fatalf("Expected array length is 3 but %d", length) + } + + if ptrMixedArr[0] != struct1 { + t.Errorf("Expected ptrMixedArr index 2 is '%v', but got '%v'", struct1, ptrMixedArr[0]) + } +} + +func TestArrPush(t *testing.T) { + // Create test data for string pointer array + str1 := "Hello" + ptrStrArr := []*string{&str1} + + // New string to be added + str2 := "World" + + // Test pushing strings + length := ArrPush(&ptrStrArr, &str2) + if length != 2 { + t.Fatalf("Expected array length is 2 but %d", length) + } + + if ptrStrArr[0] != &str1 || ptrStrArr[1] != &str2 { + t.Errorf("Expected '%v', '%v', but got '%v'", &str1, &str2, ptrStrArr) + } + + // Test pushing integers + num1 := 1 + ptrIntArr := []*int{&num1} + num2 := 2 + + length = ArrPush(&ptrIntArr, &num2) + if length != 2 { + t.Fatalf("Expected array length is 2 but %d", length) + } + + if ptrIntArr[0] != &num1 || ptrIntArr[1] != &num2 { + t.Errorf("Expected '%v', '%v', but got '%v'", &num1, &num2, ptrIntArr) + } + + ptrMixedArr := []interface{}{str1, num1} + struct1 := &MyStruct{Name: "One"} + + length = ArrPush(&ptrMixedArr, struct1) + + if length != 3 { + t.Fatalf("Expected array length is 3 but %d", length) + } + + if ptrMixedArr[2] != struct1 { + t.Errorf("Expected ptrMixedArr index 2 is '%v', but got '%v'", struct1, ptrMixedArr[2]) + } +} + +func TestConcatArr(t *testing.T) { + str1, str2, str3, str4 := "A", "B", "C", "D" + // String arrays + strArr1 := []*string{&str1, &str2} + strArr2 := []*string{&str3, &str4} + + // Test concatenating string arrays + result := ConcatArr(strArr1, strArr2) + expected := []*string{&str1, &str2, &str3, &str4} + + if !reflect.DeepEqual(result, expected) { + t.Errorf("Expected '%v', but got '%v'", expected, result) + } + + num1, num2, num3, num4 := 1, 2, 3, 4 + // Integer arrays + intArr1 := []*int{&num1, &num2} + intArr2 := []*int{&num3, &num4} + + // Test concatenating integer arrays + result = ConcatArr(intArr1, intArr2) + expectedInts := []*int{&num1, &num2, &num3, &num4} + + if !reflect.DeepEqual(result, expectedInts) { + t.Errorf("Expected '%v', but got '%v'", expectedInts, result) + } + + // Mixed type arrays + mixedArr1 := []interface{}{1, "two"} + mixedArr2 := []interface{}{3.0, "four"} + + // Test concatenating mixed type arrays + result = ConcatArr(mixedArr1, mixedArr2) + expectedMixed := []interface{}{1, "two", 3.0, "four"} + + if !reflect.DeepEqual(result, expectedMixed) { + t.Errorf("Expected '%v', but got '%v'", expectedMixed, result) + } +} + +func TestArrRemove(t *testing.T) { + // Create test data for string pointer array + str1 := "A" + str2 := "B" + str3 := "C" + ptrStrArr := []*string{&str1, &str2, &str3} + + // Test removing an element in the middle + ArrRemove(&ptrStrArr, "B") + + if len(ptrStrArr) != 2 || ptrStrArr[0] != &str1 || ptrStrArr[1] != &str3 { + t.Errorf("Expected '%v', '%v', but got '%v'", &str1, &str3, ptrStrArr) + } + + // Test removing the first element + ArrRemove(&ptrStrArr, "A") + if len(ptrStrArr) != 1 || ptrStrArr[0] != &str3 { + t.Errorf("Expected '%v', but got '%v'", &str3, ptrStrArr) + } + + num1 := 1 + num2 := 2 + num3 := 3 + ptrIntArr := []*int{&num1, &num2, &num3} + + ArrRemove(&ptrIntArr, 2) + + if len(ptrIntArr) != 2 || ptrIntArr[0] != &num1 || ptrIntArr[1] != &num3 { + t.Errorf("Expected '%v', '%v', but got '%v'", &num1, &num3, ptrIntArr) + } + + // Test removing the first element + ArrRemove(&ptrIntArr, 3) + if len(ptrIntArr) != 1 || ptrIntArr[0] != &num1 { + t.Errorf("Expected '%v', but got '%v'", &num1, ptrIntArr) + } + + struct1 := &MyStruct{Name: "One"} + struct2 := &MyStruct{Name: "Two"} + struct3 := &MyStruct{Name: "Three"} + structPtrArr := []*MyStruct{struct1, struct2, struct3} + + ArrRemove(&structPtrArr, struct2) + if len(structPtrArr) != 2 || structPtrArr[0] != struct1 || structPtrArr[1] != struct3 { + t.Errorf("Expected '%v', '%v', but got '%v'", struct1, struct3, structPtrArr) + } + + interfaceArr := []interface{}{str1, num1, struct1} + + ArrRemove(&interfaceArr, num1) + if len(interfaceArr) != 2 || interfaceArr[0] != str1 || interfaceArr[1] != struct1 { + t.Errorf("Expected '%s', '%v', but got '%v'", str1, struct1, interfaceArr) + } + + ArrRemove(&interfaceArr, struct1) + if len(interfaceArr) != 1 || interfaceArr[0] != str1 { + t.Errorf("Expected '%s', but got '%v'", str1, interfaceArr) + } +} +func TestSortArrIntPtr(t *testing.T) { + num1, num2, num3, num4 := 5, 3, 4, 6 + intPtrArr := []*int{&num1, &num2, &num3, &num4} + sortedAsc := SortArr(intPtrArr, "ASC").([]*int) // 获取排序结果 + if !reflect.DeepEqual([]int{*sortedAsc[0], *sortedAsc[1], *sortedAsc[2]}, []int{3, 4, 5}) { + t.Errorf("Ascending sort failed, expected: %v, but got %v", []*int{&num1, &num3, &num2}, sortedAsc) + } + sortedDesc := SortArr(intPtrArr, "DESC").([]*int) // 获取排序结果 + if !reflect.DeepEqual([]int{*sortedDesc[0], *sortedDesc[1], *sortedDesc[2]}, []int{6, 5, 4}) { + t.Errorf("Descending sort failed, got %v", sortedDesc) + } +} + +// TestSortArrStrPtr tests the SortArr function with an array of string pointers +func TestSortArrStrPtr(t *testing.T) { + str1, str2, str3 := "banana", "apple", "cherry" + strPtrArr := []*string{&str1, &str2, &str3} + sortedAsc := SortArr(strPtrArr, "AsC").([]*string) // 获取排序结果 + if !reflect.DeepEqual([]string{*sortedAsc[0], *sortedAsc[1], *sortedAsc[2]}, []string{"apple", "banana", "cherry"}) { + t.Errorf("Ascending sort failed, got %v", sortedAsc) + } + sortedDesc := SortArr(strPtrArr, "dEsC").([]*string) // 获取排序结果 + if !reflect.DeepEqual([]string{*sortedDesc[0], *sortedDesc[1], *sortedDesc[2]}, []string{"cherry", "banana", "apple"}) { + t.Errorf("Descending sort failed, got %v", sortedDesc) + } +} + +// TestSortArrStructPtr tests the SortArr function with an array of struct pointers +func TestSortArrStructPtr(t *testing.T) { + type Person struct { + Name string + Age int + } + person1 := &Person{"Alice", 30} + person2 := &Person{"Bob", 25} + person3 := &Person{"Charlie", 35} + personArr := []*Person{person2, person1, person3} + // Ascending sort by Age (first field) + sortedAsc := SortArr(personArr, "aSc").([]*Person) // 获取排序结果 + expectedAsc := []*Person{person1, person2, person3} + for i, p := range expectedAsc { + if sortedAsc[i].Name != p.Name || sortedAsc[i].Age != p.Age { + t.Errorf("Ascending sort failed, got %v", sortedAsc) + } + } + // Descending sort by Age (first field) + sortedDesc := SortArr(personArr, "DEsc").([]*Person) // 获取排序结果 + expectedDesc := []*Person{person3, person2, person1} + for i, p := range expectedDesc { + if sortedDesc[i].Name != p.Name || sortedDesc[i].Age != p.Age { + t.Errorf("Descending sort failed, got %v", sortedDesc) + } + } +} + +// TestSortArrInterface tests the SortArr function with an array of mixed interface{} types +func TestSortArrInterface(t *testing.T) { + type Person struct { + Name string + Age int + } + person1 := &Person{"Alice", 30} + person2 := &Person{"Bob", 25} + str1 := "banana" + str2 := "apple" + num1 := 5 + num2 := 3 + interfaceArr := []interface{}{str1, num1, person1, str2, num2, person2} + sortedAsc := SortArr(interfaceArr, "asc").([]interface{}) // 获取排序结果 + expectedAsc := []interface{}{num2, num1, str2, str1, person1, person2} + for i, v := range expectedAsc { + if !reflect.DeepEqual(sortedAsc[i], v) { + t.Errorf("Ascending sort failed, got %v", sortedAsc) + } + } + sortedDesc := SortArr(interfaceArr, "desc").([]interface{}) // 获取排序结果 + expectedDesc := []interface{}{person2, person1, str1, str2, num1, num2} + for i, v := range expectedDesc { + if !reflect.DeepEqual(sortedDesc[i], v) { + t.Errorf("Descending sort failed, got %v", sortedDesc) + } + } +} diff --git a/dara/core.go b/dara/core.go new file mode 100644 index 0000000..49c6471 --- /dev/null +++ b/dara/core.go @@ -0,0 +1,928 @@ +package dara + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "math/rand" + "net" + "net/http" + "net/url" + "os" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/alibabacloud-go/debug/debug" + "github.com/alibabacloud-go/tea/utils" + + "golang.org/x/net/proxy" +) + +var debugLog = debug.Init("dara") + +var hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { + return fn +} + +var basicTypes = []string{ + "int", "int16", "int64", "int32", "float32", "float64", "string", "bool", "uint64", "uint32", "uint16", +} + +// Verify whether the parameters meet the requirements +var validateParams = []string{"require", "pattern", "maxLength", "minLength", "maximum", "minimum", "maxItems", "minItems"} + +var clientPool = &sync.Map{} + +// Request is used wrap http request +type Request struct { + Protocol *string + Port *int + Method *string + Pathname *string + Domain *string + Headers map[string]*string + Query map[string]*string + Body io.Reader +} + +// Response is use d wrap http response +type Response struct { + Body io.ReadCloser + StatusCode *int + StatusMessage *string + Headers map[string]*string +} + +type ExtendsParameters struct { + Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty"` + Queries map[string]*string `json:"queries,omitempty" xml:"queries,omitempty"` +} + +// RuntimeObject is used for converting http configuration +type RuntimeObject struct { + IgnoreSSL *bool `json:"ignoreSSL" xml:"ignoreSSL"` + ReadTimeout *int `json:"readTimeout" xml:"readTimeout"` + ConnectTimeout *int `json:"connectTimeout" xml:"connectTimeout"` + LocalAddr *string `json:"localAddr" xml:"localAddr"` + HttpProxy *string `json:"httpProxy" xml:"httpProxy"` + HttpsProxy *string `json:"httpsProxy" xml:"httpsProxy"` + NoProxy *string `json:"noProxy" xml:"noProxy"` + MaxIdleConns *int `json:"maxIdleConns" xml:"maxIdleConns"` + Key *string `json:"key" xml:"key"` + Cert *string `json:"cert" xml:"cert"` + Ca *string `json:"ca" xml:"ca"` + Socks5Proxy *string `json:"socks5Proxy" xml:"socks5Proxy"` + Socks5NetWork *string `json:"socks5NetWork" xml:"socks5NetWork"` + Listener utils.ProgressListener `json:"listener" xml:"listener"` + Tracker *utils.ReaderTracker `json:"tracker" xml:"tracker"` + Logger *utils.Logger `json:"logger" xml:"logger"` + RetryOptions *RetryOptions `json:"retryOptions" xml:"retryOptions"` + ExtendsParameters *ExtendsParameters `json:"extendsParameters,omitempty" xml:"extendsParameters,omitempty"` +} + +type daraClient struct { + sync.Mutex + httpClient *http.Client + ifInit bool +} + +func (r *RuntimeObject) getClientTag(domain string) string { + return strconv.FormatBool(BoolValue(r.IgnoreSSL)) + strconv.Itoa(IntValue(r.ReadTimeout)) + + strconv.Itoa(IntValue(r.ConnectTimeout)) + StringValue(r.LocalAddr) + StringValue(r.HttpProxy) + + StringValue(r.HttpsProxy) + StringValue(r.NoProxy) + StringValue(r.Socks5Proxy) + StringValue(r.Socks5NetWork) + domain +} + +// NewRuntimeObject is used for shortly create runtime object +func NewRuntimeObject(runtime map[string]interface{}) *RuntimeObject { + if runtime == nil { + return &RuntimeObject{} + } + + runtimeObject := &RuntimeObject{ + IgnoreSSL: TransInterfaceToBool(runtime["ignoreSSL"]), + ReadTimeout: TransInterfaceToInt(runtime["readTimeout"]), + ConnectTimeout: TransInterfaceToInt(runtime["connectTimeout"]), + LocalAddr: TransInterfaceToString(runtime["localAddr"]), + HttpProxy: TransInterfaceToString(runtime["httpProxy"]), + HttpsProxy: TransInterfaceToString(runtime["httpsProxy"]), + NoProxy: TransInterfaceToString(runtime["noProxy"]), + MaxIdleConns: TransInterfaceToInt(runtime["maxIdleConns"]), + Socks5Proxy: TransInterfaceToString(runtime["socks5Proxy"]), + Socks5NetWork: TransInterfaceToString(runtime["socks5NetWork"]), + Key: TransInterfaceToString(runtime["key"]), + Cert: TransInterfaceToString(runtime["cert"]), + Ca: TransInterfaceToString(runtime["ca"]), + } + if runtime["listener"] != nil { + runtimeObject.Listener = runtime["listener"].(utils.ProgressListener) + } + if runtime["tracker"] != nil { + runtimeObject.Tracker = runtime["tracker"].(*utils.ReaderTracker) + } + if runtime["logger"] != nil { + runtimeObject.Logger = runtime["logger"].(*utils.Logger) + } + return runtimeObject +} + +// NewRequest is used shortly create Request +func NewRequest() (req *Request) { + return &Request{ + Headers: map[string]*string{}, + Query: map[string]*string{}, + } +} + +// NewResponse is create response with http response +func NewResponse(httpResponse *http.Response) (res *Response) { + res = &Response{} + res.Body = httpResponse.Body + res.Headers = make(map[string]*string) + res.StatusCode = Int(httpResponse.StatusCode) + res.StatusMessage = String(httpResponse.Status) + return +} + +// 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) { + var buffer [512]byte + defer response.Body.Close() + result := bytes.NewBuffer(nil) + + for { + n, err := response.Body.Read(buffer[0:]) + result.Write(buffer[0:n]) + if err != nil && err == io.EOF { + break + } else if err != nil { + return nil, err + } + } + return result.Bytes(), nil +} + +func getDaraClient(tag string) *daraClient { + client, ok := clientPool.Load(tag) + if client == nil && !ok { + client = &daraClient{ + httpClient: &http.Client{}, + ifInit: false, + } + clientPool.Store(tag, client) + } + return client.(*daraClient) +} + +// DoRequest is used send request to server +func DoRequest(request *Request, requestRuntime map[string]interface{}) (response *Response, err error) { + runtimeObject := NewRuntimeObject(requestRuntime) + fieldMap := make(map[string]string) + utils.InitLogMsg(fieldMap) + defer func() { + if runtimeObject.Logger != nil { + runtimeObject.Logger.PrintLog(fieldMap, err) + } + }() + if request.Method == nil { + request.Method = String("GET") + } + + if request.Protocol == nil { + request.Protocol = String("http") + } else { + request.Protocol = String(strings.ToLower(StringValue(request.Protocol))) + } + + requestURL := "" + request.Domain = request.Headers["host"] + if request.Port != nil { + request.Domain = String(fmt.Sprintf("%s:%d", StringValue(request.Domain), IntValue(request.Port))) + } + requestURL = fmt.Sprintf("%s://%s%s", StringValue(request.Protocol), StringValue(request.Domain), StringValue(request.Pathname)) + queryParams := request.Query + // sort QueryParams by key + q := url.Values{} + for key, value := range queryParams { + q.Add(key, StringValue(value)) + } + querystring := q.Encode() + if len(querystring) > 0 { + if strings.Contains(requestURL, "?") { + requestURL = fmt.Sprintf("%s&%s", requestURL, querystring) + } else { + requestURL = fmt.Sprintf("%s?%s", requestURL, querystring) + } + } + debugLog("> %s %s", StringValue(request.Method), requestURL) + + httpRequest, err := http.NewRequest(StringValue(request.Method), requestURL, request.Body) + if err != nil { + return + } + httpRequest.Host = StringValue(request.Domain) + + client := getDaraClient(runtimeObject.getClientTag(StringValue(request.Domain))) + client.Lock() + if !client.ifInit { + trans, err := getHttpTransport(request, runtimeObject) + if err != nil { + return nil, err + } + client.httpClient.Timeout = time.Duration(IntValue(runtimeObject.ReadTimeout)) * time.Millisecond + client.httpClient.Transport = trans + client.ifInit = true + } + client.Unlock() + for key, value := range request.Headers { + if value == nil || key == "content-length" { + continue + } else if key == "host" { + httpRequest.Header["Host"] = []string{*value} + delete(httpRequest.Header, "host") + } else if key == "user-agent" { + httpRequest.Header["User-Agent"] = []string{*value} + delete(httpRequest.Header, "user-agent") + } else { + httpRequest.Header[key] = []string{*value} + } + debugLog("> %s: %s", key, StringValue(value)) + } + contentlength, _ := strconv.Atoi(StringValue(request.Headers["content-length"])) + event := utils.NewProgressEvent(utils.TransferStartedEvent, 0, int64(contentlength), 0) + utils.PublishProgress(runtimeObject.Listener, event) + + putMsgToMap(fieldMap, httpRequest) + startTime := time.Now() + fieldMap["{start_time}"] = startTime.Format("2006-01-02 15:04:05") + res, err := hookDo(client.httpClient.Do)(httpRequest) + fieldMap["{cost}"] = time.Since(startTime).String() + completedBytes := int64(0) + if runtimeObject.Tracker != nil { + completedBytes = runtimeObject.Tracker.CompletedBytes + } + if err != nil { + event = utils.NewProgressEvent(utils.TransferFailedEvent, completedBytes, int64(contentlength), 0) + utils.PublishProgress(runtimeObject.Listener, event) + return + } + + event = utils.NewProgressEvent(utils.TransferCompletedEvent, completedBytes, int64(contentlength), 0) + utils.PublishProgress(runtimeObject.Listener, event) + + response = NewResponse(res) + fieldMap["{code}"] = strconv.Itoa(res.StatusCode) + fieldMap["{res_headers}"] = Stringify(res.Header) + debugLog("< HTTP/1.1 %s", res.Status) + for key, value := range res.Header { + debugLog("< %s: %s", key, strings.Join(value, "")) + if len(value) != 0 { + response.Headers[strings.ToLower(key)] = String(value[0]) + } + } + return +} + +func getHttpTransport(req *Request, runtime *RuntimeObject) (*http.Transport, error) { + trans := new(http.Transport) + httpProxy, err := getHttpProxy(StringValue(req.Protocol), StringValue(req.Domain), runtime) + if err != nil { + return nil, err + } + if strings.ToLower(*req.Protocol) == "https" { + if BoolValue(runtime.IgnoreSSL) != true { + trans.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: false, + } + if runtime.Key != nil && runtime.Cert != nil && StringValue(runtime.Key) != "" && StringValue(runtime.Cert) != "" { + cert, err := tls.X509KeyPair([]byte(StringValue(runtime.Cert)), []byte(StringValue(runtime.Key))) + if err != nil { + return nil, err + } + trans.TLSClientConfig.Certificates = []tls.Certificate{cert} + } + if runtime.Ca != nil && StringValue(runtime.Ca) != "" { + clientCertPool := x509.NewCertPool() + ok := clientCertPool.AppendCertsFromPEM([]byte(StringValue(runtime.Ca))) + if !ok { + return nil, errors.New("Failed to parse root certificate") + } + trans.TLSClientConfig.RootCAs = clientCertPool + } + } else { + trans.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } + } + if httpProxy != nil { + trans.Proxy = http.ProxyURL(httpProxy) + if httpProxy.User != nil { + password, _ := httpProxy.User.Password() + auth := httpProxy.User.Username() + ":" + password + basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + req.Headers["Proxy-Authorization"] = String(basic) + } + } + if runtime.Socks5Proxy != nil && StringValue(runtime.Socks5Proxy) != "" { + socks5Proxy, err := getSocks5Proxy(runtime) + if err != nil { + return nil, err + } + if socks5Proxy != nil { + var auth *proxy.Auth + if socks5Proxy.User != nil { + password, _ := socks5Proxy.User.Password() + auth = &proxy.Auth{ + User: socks5Proxy.User.Username(), + Password: password, + } + } + dialer, err := proxy.SOCKS5(strings.ToLower(StringValue(runtime.Socks5NetWork)), socks5Proxy.String(), auth, + &net.Dialer{ + Timeout: time.Duration(IntValue(runtime.ConnectTimeout)) * time.Millisecond, + DualStack: true, + LocalAddr: getLocalAddr(StringValue(runtime.LocalAddr)), + }) + if err != nil { + return nil, err + } + trans.Dial = dialer.Dial + } + } else { + trans.DialContext = setDialContext(runtime) + } + if runtime.MaxIdleConns != nil && *runtime.MaxIdleConns > 0 { + trans.MaxIdleConns = IntValue(runtime.MaxIdleConns) + trans.MaxIdleConnsPerHost = IntValue(runtime.MaxIdleConns) + } + return trans, nil +} + +func putMsgToMap(fieldMap map[string]string, request *http.Request) { + fieldMap["{host}"] = request.Host + fieldMap["{method}"] = request.Method + fieldMap["{uri}"] = request.URL.RequestURI() + fieldMap["{pid}"] = strconv.Itoa(os.Getpid()) + fieldMap["{version}"] = strings.Split(request.Proto, "/")[1] + hostname, _ := os.Hostname() + fieldMap["{hostname}"] = hostname + fieldMap["{req_headers}"] = Stringify(request.Header) + fieldMap["{target}"] = request.URL.Path + request.URL.RawQuery +} + +func getNoProxy(protocol string, runtime *RuntimeObject) []string { + var urls []string + if runtime.NoProxy != nil && StringValue(runtime.NoProxy) != "" { + urls = strings.Split(StringValue(runtime.NoProxy), ",") + } else if rawurl := os.Getenv("NO_PROXY"); rawurl != "" { + urls = strings.Split(rawurl, ",") + } else if rawurl := os.Getenv("no_proxy"); rawurl != "" { + urls = strings.Split(rawurl, ",") + } + + 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 { + switch v := val.(type) { + case []byte: + return string(v) // 将 []byte 转换为字符串 + default: + return fmt.Sprintf("%v", v) // 处理其他类型 + } +} + +func getHttpProxy(protocol, host string, runtime *RuntimeObject) (proxy *url.URL, err error) { + urls := getNoProxy(protocol, runtime) + for _, url := range urls { + if url == host { + return nil, nil + } + } + if protocol == "https" { + if runtime.HttpsProxy != nil && StringValue(runtime.HttpsProxy) != "" { + proxy, err = url.Parse(StringValue(runtime.HttpsProxy)) + } else if rawurl := os.Getenv("HTTPS_PROXY"); rawurl != "" { + proxy, err = url.Parse(rawurl) + } else if rawurl := os.Getenv("https_proxy"); rawurl != "" { + proxy, err = url.Parse(rawurl) + } + } else { + if runtime.HttpProxy != nil && StringValue(runtime.HttpProxy) != "" { + proxy, err = url.Parse(StringValue(runtime.HttpProxy)) + } else if rawurl := os.Getenv("HTTP_PROXY"); rawurl != "" { + proxy, err = url.Parse(rawurl) + } else if rawurl := os.Getenv("http_proxy"); rawurl != "" { + proxy, err = url.Parse(rawurl) + } + } + + return proxy, err +} + +func getSocks5Proxy(runtime *RuntimeObject) (proxy *url.URL, err error) { + if runtime.Socks5Proxy != nil && StringValue(runtime.Socks5Proxy) != "" { + proxy, err = url.Parse(StringValue(runtime.Socks5Proxy)) + } + return proxy, err +} + +func getLocalAddr(localAddr string) (addr *net.TCPAddr) { + if localAddr != "" { + addr = &net.TCPAddr{ + IP: []byte(localAddr), + } + } + return addr +} + +func setDialContext(runtime *RuntimeObject) func(cxt context.Context, net, addr string) (c net.Conn, err error) { + return func(ctx context.Context, network, address string) (net.Conn, error) { + if runtime.LocalAddr != nil && StringValue(runtime.LocalAddr) != "" { + netAddr := &net.TCPAddr{ + IP: []byte(StringValue(runtime.LocalAddr)), + } + return (&net.Dialer{ + Timeout: time.Duration(IntValue(runtime.ConnectTimeout)) * time.Second, + DualStack: true, + LocalAddr: netAddr, + }).DialContext(ctx, network, address) + } + return (&net.Dialer{ + Timeout: time.Duration(IntValue(runtime.ConnectTimeout)) * time.Second, + DualStack: true, + }).DialContext(ctx, network, address) + } +} + +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(val interface{}) bool { + defer func() { + recover() + }() + if val == nil { + return true + } + + v := reflect.ValueOf(val) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map { + return v.IsNil() + } + + valType := reflect.TypeOf(val) + valZero := reflect.Zero(valType) + return valZero == v +} + +func isNil(a interface{}) bool { + defer func() { + recover() + }() + vi := reflect.ValueOf(a) + return vi.IsNil() +} + +func Default(inputValue interface{}, defaultValue interface{}) (_result interface{}) { + if IsNil(inputValue) { + _result = defaultValue + return _result + } + + _result = inputValue + return _result +} + +func ToMap(args ...interface{}) map[string]interface{} { + isNotNil := false + finalArg := make(map[string]interface{}) + for _, obj := range args { + if obj == nil { + continue + } + + if isNil(obj) { + continue + } + isNotNil = true + + switch obj.(type) { + case map[string]*string: + arg := obj.(map[string]*string) + for key, value := range arg { + if value != nil { + finalArg[key] = StringValue(value) + } + } + case map[string]interface{}: + arg := obj.(map[string]interface{}) + for key, value := range arg { + if value != nil { + finalArg[key] = value + } + } + case *string: + str := obj.(*string) + arg := make(map[string]interface{}) + err := json.Unmarshal([]byte(StringValue(str)), &arg) + if err == nil { + for key, value := range arg { + if value != nil { + finalArg[key] = value + } + } + } + tmp := make(map[string]string) + err = json.Unmarshal([]byte(StringValue(str)), &tmp) + if err == nil { + for key, value := range arg { + if value != "" { + finalArg[key] = value + } + } + } + case []byte: + byt := obj.([]byte) + arg := make(map[string]interface{}) + err := json.Unmarshal(byt, &arg) + if err == nil { + for key, value := range arg { + if value != nil { + finalArg[key] = value + } + } + break + } + default: + val := reflect.ValueOf(obj) + res := structToMap(val) + for key, value := range res { + if value != nil { + finalArg[key] = value + } + } + } + } + + if !isNotNil { + return nil + } + return finalArg +} + +func structToMap(dataValue reflect.Value) map[string]interface{} { + out := make(map[string]interface{}) + if !dataValue.IsValid() { + return out + } + if dataValue.Kind().String() == "ptr" { + if dataValue.IsNil() { + return out + } + dataValue = dataValue.Elem() + } + if !dataValue.IsValid() { + return out + } + dataType := dataValue.Type() + if dataType.Kind().String() != "struct" { + return out + } + for i := 0; i < dataType.NumField(); i++ { + field := dataType.Field(i) + name, containsNameTag := field.Tag.Lookup("json") + if !containsNameTag { + name = field.Name + } else { + strs := strings.Split(name, ",") + name = strs[0] + } + fieldValue := dataValue.FieldByName(field.Name) + if !fieldValue.IsValid() || fieldValue.IsNil() { + continue + } + if field.Type.String() == "io.Reader" || field.Type.String() == "io.Writer" { + continue + } else if field.Type.Kind().String() == "struct" { + out[name] = structToMap(fieldValue) + } else if field.Type.Kind().String() == "ptr" && + field.Type.Elem().Kind().String() == "struct" { + if fieldValue.Elem().IsValid() { + out[name] = structToMap(fieldValue) + } + } else if field.Type.Kind().String() == "ptr" { + if fieldValue.IsValid() && !fieldValue.IsNil() { + out[name] = fieldValue.Elem().Interface() + } + } else if field.Type.Kind().String() == "slice" { + tmp := make([]interface{}, 0) + num := fieldValue.Len() + for i := 0; i < num; i++ { + value := fieldValue.Index(i) + if !value.IsValid() { + continue + } + if value.Type().Kind().String() == "ptr" && + value.Type().Elem().Kind().String() == "struct" { + if value.IsValid() && !value.IsNil() { + tmp = append(tmp, structToMap(value)) + } + } else if value.Type().Kind().String() == "struct" { + tmp = append(tmp, structToMap(value)) + } else if value.Type().Kind().String() == "ptr" { + if value.IsValid() && !value.IsNil() { + tmp = append(tmp, value.Elem().Interface()) + } + } else { + tmp = append(tmp, value.Interface()) + } + } + if len(tmp) > 0 { + out[name] = tmp + } + } else { + out[name] = fieldValue.Interface() + } + + } + return out +} + +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(backoffTime) * time.Second + time.Sleep(sleeptime) +} + +// Determines whether realType is in filterTypes +func isFilterType(realType string, filterTypes []string) bool { + for _, value := range filterTypes { + if value == realType { + return true + } + } + 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))) +} + +func ToBytes(s, encodingType string) []byte { + switch encodingType { + case "utf8": + return []byte(s) + case "base64": + data, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return nil + } + return data + case "hex": + data, err := hex.DecodeString(s) + if err != nil { + return nil + } + return data + default: + return nil + } +} + +func BytesFromString(str string, typeStr string) []byte { + switch typeStr { + case "utf8": + return []byte(str) + case "hex": + bytes, err := hex.DecodeString(str) + if err == nil { + return bytes + } + case "base64": + bytes, err := base64.StdEncoding.DecodeString(str) + if err == nil { + return bytes + } + } + + // 对于不支持的类型或解码失败,返回 nil + return nil +} + +func ForceInt(a interface{}) int { + num, _ := a.(int) + return num +} + +func ForceBoolean(a interface{}) bool { + b, _ := a.(bool) + return b +} + +func ForceInt64(a interface{}) int64 { + b, _ := a.(int64) + return b +} + +func ForceUint64(a interface{}) uint64 { + b, _ := a.(uint64) + return b +} + +// ForceInt32 attempts to assert that a is of type int32. +func ForceInt32(a interface{}) int32 { + b, _ := a.(int32) + return b +} + +// ForceUInt32 attempts to assert that a is of type uint32. +func ForceUInt32(a interface{}) uint32 { + b, _ := a.(uint32) + return b +} + +// ForceInt16 attempts to assert that a is of type int16. +func ForceInt16(a interface{}) int16 { + b, _ := a.(int16) + return b +} + +// ForceUInt16 attempts to assert that a is of type uint16. +func ForceUInt16(a interface{}) uint16 { + b, _ := a.(uint16) + return b +} + +// ForceInt8 attempts to assert that a is of type int8. +func ForceInt8(a interface{}) int8 { + b, _ := a.(int8) + return b +} + +// ForceUInt8 attempts to assert that a is of type uint8. +func ForceUInt8(a interface{}) uint8 { + b, _ := a.(uint8) + return b +} + +// ForceFloat32 attempts to assert that a is of type float32. +func ForceFloat32(a interface{}) float32 { + b, _ := a.(float32) + return b +} + +// ForceFloat64 attempts to assert that a is of type float64. +func ForceFloat64(a interface{}) float64 { + b, _ := a.(float64) + return b +} diff --git a/dara/core_test.go b/dara/core_test.go new file mode 100644 index 0000000..7df24e5 --- /dev/null +++ b/dara/core_test.go @@ -0,0 +1,1161 @@ +package dara + +import ( + "bytes" + "context" + "errors" + "io" + "io/ioutil" + "net/http" + "os" + "reflect" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/alibabacloud-go/tea/utils" +) + +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}"), +} + +var key = `-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN5I1VCLYr2IlTLrFpwUGcnwl8yi6V8Mdw+myxfusNgEWiH/FQ4T +AZsIveiLOz9Gcc8m2mZSxst2qGII00scpiECAwEAAQJBAJZEhnA8yjN28eXKJy68 +J/LsQrKEL1+h/ZsHFqTHJ6XfiA0CXjbjPsa4jEbpyilMTSgUyoKdJ512ioeco2n6 +xUECIQD/JUHaKSuxz55t3efKdppqfopb92mJ2NuPJgrJI70OCwIhAN8HZ0bzr/4a +DLvYCDUKvOj3GzsV1dtBwWuHBaZEafQDAiEAtTnrel//7z5/U55ow4BW0gmrkQM9 +bXIhEZ59zryZzl0CIQDFmBqRCu9eshecCP7kd3n88IjopSTOV4iUypBfyXcRnwIg +eXNxUx+BCu2We36+c0deE2+vizL1s6f5XhE6l4bqtiM= +-----END RSA PRIVATE KEY-----` + +var cert = `-----BEGIN CERTIFICATE----- +MIIBvDCCAWYCCQDKjNYQxar0mjANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJh +czEMMAoGA1UECAwDYXNmMQwwCgYDVQQHDANzYWQxCzAJBgNVBAoMAnNkMQ0wCwYD +VQQLDARxd2VyMQswCQYDVQQDDAJzZjERMA8GCSqGSIb3DQEJARYCd2UwHhcNMjAx +MDE5MDI0MDMwWhcNMzAxMDE3MDI0MDMwWjBlMQswCQYDVQQGEwJhczEMMAoGA1UE +CAwDYXNmMQwwCgYDVQQHDANzYWQxCzAJBgNVBAoMAnNkMQ0wCwYDVQQLDARxd2Vy +MQswCQYDVQQDDAJzZjERMA8GCSqGSIb3DQEJARYCd2UwXDANBgkqhkiG9w0BAQEF +AANLADBIAkEA3kjVUItivYiVMusWnBQZyfCXzKLpXwx3D6bLF+6w2ARaIf8VDhMB +mwi96Is7P0ZxzybaZlLGy3aoYgjTSxymIQIDAQABMA0GCSqGSIb3DQEBCwUAA0EA +ZjePopbFugNK0US1MM48V1S2petIsEcxbZBEk/wGqIzrY4RCFKMtbtPSgTDUl3D9 +XePemktG22a54ItVJ5FpcQ== +-----END CERTIFICATE-----` + +var ca = `-----BEGIN CERTIFICATE----- +MIIBuDCCAWICCQCLw4OWpjlJCDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJm +ZDEMMAoGA1UECAwDYXNkMQswCQYDVQQHDAJxcjEKMAgGA1UECgwBZjEMMAoGA1UE +CwwDc2RhMQswCQYDVQQDDAJmZDESMBAGCSqGSIb3DQEJARYDYXNkMB4XDTIwMTAx +OTAyNDQwNFoXDTIzMDgwOTAyNDQwNFowYzELMAkGA1UEBhMCZmQxDDAKBgNVBAgM +A2FzZDELMAkGA1UEBwwCcXIxCjAIBgNVBAoMAWYxDDAKBgNVBAsMA3NkYTELMAkG +A1UEAwwCZmQxEjAQBgkqhkiG9w0BCQEWA2FzZDBcMA0GCSqGSIb3DQEBAQUAA0sA +MEgCQQCxXZTl5IO61Lqd0fBBOSy7ER1gsdA0LkvflP5HEaQygjecLGfrAtD/DWu0 +/sxCcBVnQRoP9Yp0ijHJwgXvBnrNAgMBAAEwDQYJKoZIhvcNAQELBQADQQBJF+/4 +DEMilhlFY+o9mqCygFVxuvHtQVhpPS938H2h7/P6pXN65jK2Y5hHefZEELq9ulQe +91iBwaQ4e9racCgP +-----END CERTIFICATE-----` + +type test struct { + Key string `json:"key,omitempty"` + Body []byte `json:"body,omitempty"` +} + +type PrettifyTest struct { + name string + Strs []string + Nums8 []int8 + Unum8 []uint8 + Value string + Mapvalue map[string]string +} + +type validateTest struct { + Num1 *int `json:"num1,omitempty" require:"true" minimum:"2"` + Num2 *int `json:"num2,omitempty" maximum:"6"` + Name1 *string `json:"name1,omitempty" maxLength:"4"` + Name2 *string `json:"name2,omitempty" minLength:"2"` + Str *string `json:"str,omitempty" pattern:"[a-d]*" maxLength:"4"` + MaxLength *errMaxLength `json:"MaxLength,omitempty"` + MinLength *errMinLength `json:"MinLength,omitempty"` + Maximum *errMaximum `json:"Maximum,omitempty"` + Minimum *errMinimum `json:"Minimum,omitempty"` + MaxItems *errMaxItems `json:"MaxItems,omitempty"` + MinItems *errMinItems `json:"MinItems,omitempty"` + List []*string `json:"list,omitempty" pattern:"[a-d]*" minItems:"2" maxItems:"3" maxLength:"4"` +} + +type errMaxLength struct { + Num *int `json:"num" maxLength:"a"` +} + +type errMinLength struct { + Num *int `json:"num" minLength:"a"` +} + +type errMaximum struct { + Num *int `json:"num" maximum:"a"` +} + +type errMinimum struct { + Num *int `json:"num" minimum:"a"` +} + +type errMaxItems struct { + NumMax []*int `json:"num" maxItems:"a"` +} + +type errMinItems struct { + NumMin []*int `json:"num" minItems:"a"` +} + +type Progresstest struct { +} + +func (progress *Progresstest) ProgressChanged(event *utils.ProgressEvent) { +} + +func mockResponse(statusCode int, content string, mockerr error) (res *http.Response, err error) { + status := strconv.Itoa(statusCode) + res = &http.Response{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + Header: map[string][]string{"TEA": []string{"test"}}, + StatusCode: statusCode, + Status: status + " " + http.StatusText(statusCode), + } + res.Body = ioutil.NopCloser(bytes.NewReader([]byte(content))) + err = mockerr + 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) +} + +func TestResponse(t *testing.T) { + httpresponse := &http.Response{ + Body: ioutil.NopCloser(strings.NewReader("response")), + } + response := NewResponse(httpresponse) + utils.AssertNotNil(t, response) + + body, err := response.ReadBody() + utils.AssertEqual(t, "response", string(body)) + 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) +} + +type Test struct { + Msg *string `json:"Msg,omitempty"` + Cast *CastError `json:"Cast,omitempty"` + ListPtr []*string `json:"ListPtr,omitempty"` + List []string `json:"List,omitempty"` + CastList []CastError `json:"CastList,omitempty"` + CastListPtr []*CastError `json:"CastListPtr,omitempty"` + Reader io.Reader + Inter interface{} +} + +func TestToMap(t *testing.T) { + in := map[string]*string{ + "tea": String("test"), + "nil": nil, + } + result := ToMap(in) + utils.AssertEqual(t, "test", result["tea"]) + utils.AssertNil(t, result["nil"]) + + validMap := map[string]interface{}{ + "valid": "test", + } + result = ToMap(validMap) + utils.AssertEqual(t, "test", result["valid"]) + + valid := &Test{ + Msg: String("tea"), + Cast: &CastError{ + Message: String("message"), + }, + ListPtr: StringSlice([]string{"test", ""}), + List: []string{"list"}, + CastListPtr: []*CastError{ + &CastError{ + Message: String("CastListPtr"), + }, + nil, + }, + CastList: []CastError{ + CastError{ + Message: String("CastList"), + }, + }, + Reader: strings.NewReader(""), + Inter: 10, + } + result = ToMap(valid) + utils.AssertEqual(t, "tea", result["Msg"]) + utils.AssertNil(t, result["Reader"]) + utils.AssertEqual(t, map[string]interface{}{"Message": "message"}, result["Cast"]) + utils.AssertEqual(t, []interface{}{"test", ""}, result["ListPtr"]) + utils.AssertEqual(t, []interface{}{"list"}, result["List"]) + utils.AssertEqual(t, []interface{}{map[string]interface{}{"Message": "CastListPtr"}}, result["CastListPtr"]) + utils.AssertEqual(t, []interface{}{map[string]interface{}{"Message": "CastList"}}, result["CastList"]) + + valid1 := &Test{ + Msg: String("tea"), + } + result = ToMap(valid1) + utils.AssertEqual(t, "tea", result["Msg"]) + + validStr := String(`{"test":"ok"}`) + result = ToMap(validStr) + utils.AssertEqual(t, "ok", result["test"]) + + validStr1 := String(`{"test":"ok","num":1}`) + result = ToMap(validStr1) + utils.AssertEqual(t, "ok", result["test"]) + + result = ToMap([]byte(StringValue(validStr))) + utils.AssertEqual(t, "ok", result["test"]) + + result = ToMap([]byte(StringValue(validStr1))) + utils.AssertEqual(t, "ok", result["test"]) + + invalidStr := "sdfdg" + result = ToMap(invalidStr) + utils.AssertEqual(t, map[string]interface{}{}, result) + + result = ToMap(10) + utils.AssertEqual(t, map[string]interface{}{}, result) + + result = ToMap(nil) + 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(1) + + backoff["period"] = 3 + ms = GetBackoffTime(backoff, Int(1)) + utils.AssertEqual(t, true, IntValue(ms) <= 3) +} + +func Test_DoRequest(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 := DoRequest(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" + resp, err = DoRequest(request, runtimeObj) + 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" + resp, err = DoRequest(request, runtimeObj) + 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") + resp, err = DoRequest(request, runtimeObj) + utils.AssertNil(t, resp) + utils.AssertEqual(t, `Internal error`, err.Error()) + + runtimeObj["socks5Proxy"] = "# #%gfdf" + resp, err = DoRequest(request, runtimeObj) + 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" + resp, err = DoRequest(request, runtimeObj) + 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 + resp, err = DoRequest(request, runtimeObj) + 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 + resp, err = DoRequest(request, runtimeObj) + 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" + _, err = DoRequest(request, runtimeObj) + 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 + resp, err = DoRequest(request, runtimeObj) + utils.AssertNil(t, err) + utils.AssertEqual(t, "test", StringValue(resp.Headers["tea"])) + + request.Protocol = String("HTTP") + runtimeObj["ignoreSSL"] = false + resp, err = DoRequest(request, runtimeObj) + 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") + resp, err = DoRequest(request, runtimeObj) + utils.AssertNil(t, resp) + utils.AssertEqual(t, `Internal error`, err.Error()) +} + +func Test_DoRequestWithConcurrent(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, ``, nil) + } + } + var wg sync.WaitGroup + for i := 0; i < 50; i++ { + wg.Add(1) + go func(readTimeout int) { + runtime := map[string]interface{}{ + "readTimeout": readTimeout, + } + for j := 0; j < 50; j++ { + wg.Add(1) + go func() { + request := NewRequest() + resp, err := DoRequest(request, runtime) + utils.AssertNil(t, err) + utils.AssertNotNil(t, resp) + wg.Done() + }() + } + wg.Done() + }(i) + } + wg.Wait() +} + +func Test_getHttpProxy(t *testing.T) { + originHttpProxy := os.Getenv("HTTP_PROXY") + originHttpsProxy := os.Getenv("HTTPS_PROXY") + originhttpproxy := os.Getenv("http_proxy") + originhttpsproxy := os.Getenv("https_proxy") + originNoProxy := os.Getenv("NO_PROXY") + originnoproxy := os.Getenv("no_proxy") + defer func() { + os.Setenv("HTTP_PROXY", originHttpProxy) + os.Setenv("HTTPS_PROXY", originHttpsProxy) + os.Setenv("http_proxy", originhttpproxy) + os.Setenv("https_proxy", originhttpsproxy) + os.Setenv("NO_PROXY", originNoProxy) + os.Setenv("no_proxy", originnoproxy) + }() + runtime := &RuntimeObject{ + NoProxy: String("www.aliyun.com"), + } + proxy, err := getHttpProxy("http", "www.aliyun.com", runtime) + utils.AssertNil(t, proxy) + utils.AssertNil(t, err) + + runtime.NoProxy = nil + os.Setenv("no_proxy", "tea") + os.Setenv("http_proxy", "tea.aliyun.com") + proxy, err = getHttpProxy("http", "www.aliyun.com", runtime) + utils.AssertEqual(t, "tea.aliyun.com", proxy.Path) + utils.AssertNil(t, err) + + os.Setenv("NO_PROXY", "tea") + os.Setenv("HTTP_PROXY", "tea1.aliyun.com") + proxy, err = getHttpProxy("http", "www.aliyun.com", runtime) + utils.AssertEqual(t, "tea1.aliyun.com", proxy.Path) + utils.AssertNil(t, err) + + runtime.HttpProxy = String("tea2.aliyun.com") + proxy, err = getHttpProxy("http", "www.aliyun.com", runtime) + utils.AssertEqual(t, "tea2.aliyun.com", proxy.Path) + utils.AssertNil(t, err) + + os.Setenv("no_proxy", "tea") + os.Setenv("https_proxy", "tea.aliyun.com") + proxy, err = getHttpProxy("https", "www.aliyun.com", runtime) + utils.AssertEqual(t, "tea.aliyun.com", proxy.Path) + utils.AssertNil(t, err) + + os.Setenv("NO_PROXY", "tea") + os.Setenv("HTTPS_PROXY", "tea1.aliyun.com") + proxy, err = getHttpProxy("https", "www.aliyun.com", runtime) + utils.AssertEqual(t, "tea1.aliyun.com", proxy.Path) + utils.AssertNil(t, err) +} + +func Test_SetDialContext(t *testing.T) { + runtime := &RuntimeObject{} + dialcontext := setDialContext(runtime) + ctx, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second) + utils.AssertNotNil(t, cancelFunc) + c, err := dialcontext(ctx, "127.0.0.1", "127.0.0.2") + utils.AssertNil(t, c) + utils.AssertEqual(t, "dial 127.0.0.1: unknown network 127.0.0.1", err.Error()) + + runtime.LocalAddr = String("127.0.0.1") + c, err = dialcontext(ctx, "127.0.0.1", "127.0.0.2") + utils.AssertNil(t, c) + utils.AssertEqual(t, "dial 127.0.0.1: unknown network 127.0.0.1", err.Error()) +} + +func Test_hookdo(t *testing.T) { + fn := func(req *http.Request) (*http.Response, error) { + return nil, errors.New("hookdo") + } + result := hookDo(fn) + resp, err := result(nil) + utils.AssertNil(t, resp) + 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) { + var tmp *validateTest + num := 3 + config := &validateTest{ + Num1: &num, + } + err := Validate(config) + utils.AssertNil(t, err) + + err = Validate(new(validateTest)) + utils.AssertEqual(t, err.Error(), "num1 should be setted") + err = Validate(tmp) + utils.AssertNil(t, err) + + err = Validate(nil) + 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)) + utils.AssertNil(t, err) + + num := 3 + str0, str1 := "abc", "abcddd" + val := &validateTest{ + Num1: &num, + Num2: &num, + Str: &str0, + } + + err = validate(reflect.ValueOf(val)) + utils.AssertNil(t, err) + + val.Str = &str1 + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "The length of Str is 6 which is more than 4", err.Error()) + + val.Num1 = nil + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "num1 should be setted", err.Error()) + + val.Name1 = String("最大长度") + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "num1 should be setted", err.Error()) + + val.Num1 = &num + val.Str = &str0 + val.List = []*string{&str0} + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "The length of List is 1 which is less than 2", err.Error()) + + val.List = []*string{&str0, &str1} + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "The length of List is 6 which is more than 4", err.Error()) + + val.List = []*string{&str0, &str0} + err = validate(reflect.ValueOf(val)) + utils.AssertNil(t, err) + + val.MaxItems = &errMaxItems{ + NumMax: []*int{&num}, + } + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `strconv.Atoi: parsing "a": invalid syntax`, err.Error()) + + val.MaxItems = nil + val.MinItems = &errMinItems{ + NumMin: []*int{&num}, + } + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `strconv.Atoi: parsing "a": invalid syntax`, err.Error()) + + val.MinItems = nil + val.List = []*string{&str0, &str0, &str0, &str0} + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "The length of List is 4 which is more than 3", err.Error()) + + str2 := "test" + val.Str = &str2 + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, "test is not matched [a-d]*", err.Error()) + + val.Str = &str0 + val.List = []*string{&str0} + val.MaxLength = &errMaxLength{ + Num: &num, + } + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `strconv.Atoi: parsing "a": invalid syntax`, err.Error()) + + val.List = nil + val.MaxLength = nil + val.MinLength = &errMinLength{ + Num: &num, + } + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `strconv.Atoi: parsing "a": invalid syntax`, err.Error()) + + val.Name2 = String("tea") + val.MinLength = nil + val.Maximum = &errMaximum{ + Num: &num, + } + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `strconv.ParseFloat: parsing "a": invalid syntax`, err.Error()) + + val.Maximum = nil + val.Minimum = &errMinimum{ + Num: &num, + } + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `strconv.ParseFloat: parsing "a": invalid syntax`, err.Error()) + + val.Minimum = nil + val.Num2 = Int(10) + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `The size of Num2 is 10.000000 which is greater than 6.000000`, err.Error()) + + val.Num2 = nil + val.Name1 = String("maxLengthTouch") + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `The length of Name1 is 14 which is more than 4`, err.Error()) + + val.Name1 = nil + val.Name2 = String("") + err = validate(reflect.ValueOf(val)) + utils.AssertEqual(t, `The length of Name2 is 0 which is less than 2`, err.Error()) + + val.Name2 = String("tea") + val.Num1 = Int(0) + 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)) +} + +func Test_Default(t *testing.T) { + a := ToInt(Int32(10)) + utils.AssertEqual(t, IntValue(a), 10) + + b := ToInt32(a) + utils.AssertEqual(t, Int32Value(b), int32(10)) +} + +func TestToBytes(t *testing.T) { + tests := []struct { + input string + encodingType string + expected []byte + }{ + {"Hello, World!", "utf8", []byte("Hello, World!")}, + {"SGVsbG8sIFdvcmxkIQ==", "base64", []byte("Hello, World!")}, + {"48656c6c6f2c20576f726c6421", "hex", []byte("Hello, World!")}, + {"invalid base64", "base64", nil}, + {"invalid hex", "hex", nil}, + {"unsupported", "unsupported", nil}, + } + + for _, tt := range tests { + result := ToBytes(tt.input, tt.encodingType) + + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("ToBytes(%q, %q) = %v, want %v", + tt.input, tt.encodingType, result, tt.expected) + } + } +} + +func TestForceInt(t *testing.T) { + tests := []struct { + input interface{} + expected int + }{ + {int(42), 42}, + {int(-10), -10}, + {nil, 0}, // nil should return zero value for int + } + + for _, test := range tests { + result := ForceInt(test.input) + if result != test.expected { + t.Errorf("ForceInt(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceBoolean(t *testing.T) { + tests := []struct { + input interface{} + expected bool + }{ + {true, true}, + {false, false}, + {nil, false}, // nil should return zero value for bool + } + + for _, test := range tests { + result := ForceBoolean(test.input) + if result != test.expected { + t.Errorf("ForceBoolean(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceInt32(t *testing.T) { + tests := []struct { + input interface{} + expected int32 + }{ + {int32(42), 42}, + {int32(-10), -10}, + {nil, 0}, // nil should return zero value for int32 + } + + for _, test := range tests { + result := ForceInt32(test.input) + if result != test.expected { + t.Errorf("ForceInt32(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceUInt32(t *testing.T) { + tests := []struct { + input interface{} + expected uint32 + }{ + {uint32(100), 100}, + {uint32(0), 0}, + {nil, 0}, // nil should return zero value for uint32 + } + + for _, test := range tests { + result := ForceUInt32(test.input) + if result != test.expected { + t.Errorf("ForceUInt32(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceInt16(t *testing.T) { + tests := []struct { + input interface{} + expected int16 + }{ + {int16(12345), 12345}, + {int16(-543), -543}, + {nil, 0}, // nil should return zero value for int16 + } + + for _, test := range tests { + result := ForceInt16(test.input) + if result != test.expected { + t.Errorf("ForceInt16(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceUInt16(t *testing.T) { + tests := []struct { + input interface{} + expected uint16 + }{ + {uint16(65535), 65535}, + {uint16(1), 1}, + {nil, 0}, // nil should return zero value for uint16 + } + + for _, test := range tests { + result := ForceUInt16(test.input) + if result != test.expected { + t.Errorf("ForceUInt16(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceInt8(t *testing.T) { + tests := []struct { + input interface{} + expected int8 + }{ + {int8(127), 127}, + {int8(-128), -128}, + {nil, 0}, // nil should return zero value for int8 + } + + for _, test := range tests { + result := ForceInt8(test.input) + if result != test.expected { + t.Errorf("ForceInt8(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceUInt8(t *testing.T) { + tests := []struct { + input interface{} + expected uint8 + }{ + {uint8(255), 255}, + {uint8(0), 0}, + {nil, 0}, // nil should return zero value for uint8 + } + + for _, test := range tests { + result := ForceUInt8(test.input) + if result != test.expected { + t.Errorf("ForceUInt8(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceFloat32(t *testing.T) { + tests := []struct { + input interface{} + expected float32 + }{ + {float32(3.14), 3.14}, + {float32(-2.71), -2.71}, + {nil, 0}, // nil should return zero value for float32 + } + + for _, test := range tests { + result := ForceFloat32(test.input) + if result != test.expected { + t.Errorf("ForceFloat32(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceFloat64(t *testing.T) { + tests := []struct { + input interface{} + expected float64 + }{ + {float64(2.718), 2.718}, + {float64(-3.14), -3.14}, + {nil, 0}, // nil should return zero value for float64 + } + + for _, test := range tests { + result := ForceFloat64(test.input) + if result != test.expected { + t.Errorf("ForceFloat64(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceInt64(t *testing.T) { + tests := []struct { + input interface{} + expected int64 + }{ + {int64(123456789), 123456789}, + {int64(-987654321), -987654321}, + {nil, 0}, // nil should return zero value for int64 + } + + for _, test := range tests { + result := ForceInt64(test.input) + if result != test.expected { + t.Errorf("ForceInt64(%v) = %v; want %v", test.input, result, test.expected) + } + } +} + +func TestForceUint64(t *testing.T) { + tests := []struct { + input interface{} + expected uint64 + }{ + {uint64(123456789), 123456789}, + {uint64(0), 0}, + {nil, 0}, // nil should return zero value for uint64 + } + + for _, test := range tests { + result := ForceUint64(test.input) + if result != test.expected { + t.Errorf("ForceUint64(%v) = %v; want %v", test.input, result, test.expected) + } + } +} diff --git a/dara/date.go b/dara/date.go new file mode 100644 index 0000000..e2b8995 --- /dev/null +++ b/dara/date.go @@ -0,0 +1,162 @@ +package dara + +import ( + "fmt" + "strings" + "time" +) + +type Date struct { + date time.Time +} + +func NewDate(dateInput string) (*Date, error) { + var t time.Time + var err error + // 解析输入时间,如果输入格式不对,返回错误 + formats := []string{ + "2006-01-02 15:04:05", + "2006-01-02 15:04:05.999999999 -0700 MST", + "2006-01-02T15:04:05-07:00", + "2006-01-02T15:04:05Z", + } + + for _, format := range formats { + t, err = time.Parse(format, dateInput) + if err == nil { + return &Date{date: t}, nil + } + } + + return nil, fmt.Errorf("unable to parse date: %v", dateInput) +} + +func (t *Date) Format(layout string) string { + + layout = strings.Replace(layout, "yyyy", "2006", 1) + layout = strings.Replace(layout, "MM", "01", 1) + layout = strings.Replace(layout, "dd", "02", 1) + // layout = strings.Replace(layout, "HH", "15", 1) + layout = strings.Replace(layout, "hh", "15", 1) + layout = strings.Replace(layout, "mm", "04", 1) + layout = strings.Replace(layout, "ss", "05", 1) + layout = strings.Replace(layout, "a", "PM", 1) + layout = strings.Replace(layout, "EEEE", "Monday", 1) + layout = strings.Replace(layout, "E", "Mon", 1) + return t.date.Format(layout) +} + +func (t *Date) Unix() int64 { + return t.date.Unix() +} + +func (t *Date) UTC() string { + return t.date.UTC().Format("2006-01-02 15:04:05.000000000 -0700 MST") +} + +func (t *Date) Sub(amount int, unit string) *Date { + var duration time.Duration + switch unit { + case "second", "seconds": + duration = time.Duration(-amount) * time.Second + case "minute", "minutes": + duration = time.Duration(-amount) * time.Minute + case "hour", "hours": + duration = time.Duration(-amount) * time.Hour + case "day", "days": + duration = time.Duration(-amount) * 24 * time.Hour + case "week", "weeks": + duration = time.Duration(-amount) * 7 * 24 * time.Hour + case "month", "months": + return &Date{date: t.date.AddDate(0, -amount, 0)} + case "year", "years": + return &Date{date: t.date.AddDate(-amount, 0, 0)} + default: + return nil + } + newDate := t.date.Add(duration) + return &Date{date: newDate} +} + +func (t *Date) Add(amount int, unit string) *Date { + var duration time.Duration + switch unit { + case "second", "seconds": + duration = time.Duration(amount) * time.Second + case "minute", "minutes": + duration = time.Duration(amount) * time.Minute + case "hour", "hours": + duration = time.Duration(amount) * time.Hour + case "day", "days": + duration = time.Duration(amount) * 24 * time.Hour + case "week", "weeks": + duration = time.Duration(amount) * 7 * 24 * time.Hour + case "month", "months": + return &Date{date: t.date.AddDate(0, amount, 0)} + case "year", "years": + return &Date{date: t.date.AddDate(amount, 0, 0)} + default: + return nil + } + + newDate := t.date.Add(duration) + return &Date{date: newDate} +} + +func (t *Date) Diff(amount string, diffDate *Date) int64 { + switch amount { + case "second", "seconds": + return int64(t.date.Sub(diffDate.date).Seconds()) + case "minute", "minutes": + return int64(t.date.Sub(diffDate.date).Minutes()) + case "hour", "hours": + return int64(t.date.Sub(diffDate.date).Hours()) + case "day", "days": + return int64(t.date.Sub(diffDate.date).Hours() / 24) + case "week", "weeks": + return int64(t.date.Sub(diffDate.date).Hours() / (24 * 7)) + case "month", "months": + return int64(diffDate.date.Year()*12 + int(diffDate.date.Month()) - (t.date.Year()*12 + int(t.date.Month()))) + case "year", "years": + return int64(t.date.Year() - diffDate.date.Year()) + default: + return 0 + } +} + +func (t *Date) Hour() int { + return t.date.Hour() +} + +func (t *Date) Minute() int { + return t.date.Minute() +} + +func (t *Date) Second() int { + return t.date.Second() +} + +func (t *Date) Month() int { + return int(t.date.Month()) +} + +func (t *Date) Year() int { + return t.date.Year() +} + +func (t *Date) DayOfMonth() int { + return t.date.Day() +} + +func (t *Date) DayOfWeek() int { + weekday := int(t.date.Weekday()) + if weekday == 0 { + return 7 // Sunday + } + return weekday +} + +func (t *Date) WeekOfYear() int { + _, week := t.date.ISOWeek() + return week +} diff --git a/dara/date_test.go b/dara/date_test.go new file mode 100644 index 0000000..56a9d5b --- /dev/null +++ b/dara/date_test.go @@ -0,0 +1,160 @@ +package dara + +import ( + "testing" + "time" +) + +func TestConstructWithNow(t *testing.T) { + date := &Date{date: time.Now()} + currentTime := time.Now() + if currentTime.Format("2006-01-02 15:04:05") != date.Format("yyyy-MM-dd hh:mm:ss") { + t.Errorf("Expected %v, got %v", currentTime.Format("2006-01-02 15:04:05"), date.Format("yyyy-MM-dd hh:mm:ss")) + } +} + +func TestConstructWithDateTimeString(t *testing.T) { + datetime := "2023-03-01T12:00:00Z" // Use RFC3339 format + date, err := NewDate(datetime) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if datetime != date.Format("yyyy-MM-ddThh:mm:ssZ") { + t.Errorf("Expected %v, got %v", datetime, date.Format("yyyy-MM-ddThh:mm:ssZ")) + } +} + +func TestConstructWithWrongType(t *testing.T) { + _, err := NewDate("20230301 12:00:00 +0000 UTC") + if err == nil { + t.Errorf("Expected error, but got nil") + } +} + +func TestConstructWithUTC(t *testing.T) { + datetimeUTC := "2023-03-01T12:00:00Z" + dateWithUTC, err := NewDate(datetimeUTC) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + referenceDateTime, _ := time.Parse(time.RFC3339, datetimeUTC) + if referenceDateTime.Unix() != dateWithUTC.Unix() { + t.Errorf("Expected %v, got %v", referenceDateTime.Unix(), dateWithUTC.Unix()) + } + + formattedDateTime := dateWithUTC.UTC() + expectedFormattedDateTime := referenceDateTime.UTC().Format("2006-01-02 15:04:05.000000000 -0700 MST") + if formattedDateTime != expectedFormattedDateTime { + t.Errorf("Expected %v, got %v", expectedFormattedDateTime, formattedDateTime) + } +} + +func TestFormat(t *testing.T) { + datetime := "2023-03-01T12:00:00Z" + date, _ := NewDate(datetime) + expected := "2023-03-01 12:00 PM" + if result := date.Format("yyyy-MM-dd hh:mm a"); result != expected { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestUTC(t *testing.T) { + datetime := "2023-03-01T12:00:00+08:00" + date, _ := NewDate(datetime) + expected := "2023-03-01 04:00:00.000000000 +0000 UTC" + if result := date.UTC(); result != expected { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestUnix(t *testing.T) { + datetime := "1970-01-01T00:00:00Z" + date, _ := NewDate(datetime) + if result := date.Unix(); result != 0 { + t.Errorf("Expected 0, got %v", result) + } + + datetime = "2023-12-31T08:00:00+08:00" + date, _ = NewDate(datetime) + if result := date.Unix(); result != 1703980800 { + t.Errorf("Expected 1703980800, got %v", result) + } +} + +func TestAddSub(t *testing.T) { + datetime := "2023-03-01T12:00:00Z" + date, _ := NewDate(datetime) + date = date.Add(1, "day") + expectedDate := time.Date(2023, 3, 2, 12, 0, 0, 0, time.UTC) + if date.date != expectedDate { + t.Errorf("Expected %v, got %v", expectedDate, date.date) + } + date = date.Sub(1, "day") // Subtract 1 day + expectedDate = time.Date(2023, 3, 1, 12, 0, 0, 0, time.UTC) + if date.date != expectedDate { + t.Errorf("Expected %v, got %v", expectedDate, date.date) + } +} + +func TestDiff(t *testing.T) { + datetime1 := "2023-03-01T12:00:00Z" + datetime2 := "2023-04-01T12:00:00Z" + date1, _ := NewDate(datetime1) + date2, _ := NewDate(datetime2) + diffInSeconds := date1.Diff("seconds", date2) + if diffInSeconds != -31*24*60*60 { + t.Errorf("Expected %v, got %v", -31*24*60*60, diffInSeconds) + } +} + +func TestHourMinuteSecond(t *testing.T) { + datetime := "2023-03-01T12:34:56Z" + date, _ := NewDate(datetime) + if result := date.Hour(); result != 12 { + t.Errorf("Expected 12, got %d", result) + } + if result := date.Minute(); result != 34 { + t.Errorf("Expected 34, got %d", result) + } + if result := date.Second(); result != 56 { + t.Errorf("Expected 56, got %d", result) + } +} + +func TestMonthYearDay(t *testing.T) { + datetime := "2023-03-01T12:00:00Z" + date, _ := NewDate(datetime) + if result := date.Month(); result != 3 { + t.Errorf("Expected 3, got %d", result) + } + if result := date.Year(); result != 2023 { + t.Errorf("Expected 2023, got %d", result) + } + if result := date.DayOfMonth(); result != 1 { + t.Errorf("Expected 1, got %d", result) + } +} + +func TestDayOfWeekWeekOfYear(t *testing.T) { + datetime := "2023-03-01 00:00:00" + date, _ := NewDate(datetime) + if result := date.DayOfWeek(); result != 3 { + t.Errorf("Expected 3, got %d", result) + } + if result := date.WeekOfYear(); result != 9 { + t.Errorf("Expected 9, got %d", result) + } + + datetime1 := "2023-12-31T12:00:00Z" + date1, _ := NewDate(datetime1) + if result := date1.DayOfMonth(); result != 31 { + t.Errorf("Expected 31, got %d", result) + } + if result := date1.DayOfWeek(); result != 7 { + t.Errorf("Expected 7, got %d", result) + } + if result := date1.WeekOfYear(); result != 52 { + t.Errorf("Expected 52, got %d", result) + } +} diff --git a/dara/error.go b/dara/error.go new file mode 100644 index 0000000..cd5cb91 --- /dev/null +++ b/dara/error.go @@ -0,0 +1,165 @@ +package dara + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "reflect" + "strconv" +) + +type BaseError interface { + error + ErrorName() *string + ErrorCode() *string +} + +type ResponseError interface { + BaseError + ErrorRetryAfter() *int + ErrorStatusCode() *int +} + +// SDKError struct is used save error code and message +type SDKError struct { + BaseError + Code *string + Name *string + StatusCode *int + Message *string + Data *string + Stack *string + errMsg *string + Description *string + AccessDeniedDetail map[string]interface{} +} + +// CastError is used for cast type fails +type CastError struct { + Message *string +} + +// NewSDKError is used for shortly create SDKError object +func NewSDKError(obj map[string]interface{}) *SDKError { + err := &SDKError{} + err.Name = String("BaseError") + 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["name"] != nil { + + } + + 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 (err *SDKError) ErrorName() *string { + return err.Name +} + +func (err *SDKError) ErrorMessage() *string { + return err.Message +} + +func (err *SDKError) ErrorCode() *string { + return err.Code +} + +// 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) +} + +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) +} + +// NewCastError is used for cast type fails +func NewCastError(message *string) (err error) { + return &CastError{ + Message: message, + } +} diff --git a/dara/file.go b/dara/file.go new file mode 100644 index 0000000..c2611f4 --- /dev/null +++ b/dara/file.go @@ -0,0 +1,146 @@ +package dara + +import ( + "os" +) + +// File struct to represent the file +type DaraFile struct { + path string + fileInfo os.FileInfo + file *os.File + position int64 +} + +// NewFile creates a new instance of File +func NewDaraFile(path string) *DaraFile { + return &DaraFile{ + path: path, + position: 0, + } +} + +// Path returns the path of the file +func (tf *DaraFile) Path() string { + return tf.path +} + +// CreateTime returns the creation time of the file +func (tf *DaraFile) CreateTime() (*Date, error) { + if tf.fileInfo == nil { + var err error + tf.fileInfo, err = os.Stat(tf.path) + if err != nil { + return nil, err + } + } + return &Date{tf.fileInfo.ModTime()}, nil +} + +// ModifyTime returns the modification time of the file +func (tf *DaraFile) ModifyTime() (*Date, error) { + if tf.fileInfo == nil { + var err error + tf.fileInfo, err = os.Stat(tf.path) + if err != nil { + return nil, err + } + } + return &Date{tf.fileInfo.ModTime()}, nil +} + +// Length returns the size of the file +func (tf *DaraFile) Length() (int64, error) { + if tf.fileInfo == nil { + var err error + tf.fileInfo, err = os.Stat(tf.path) + if err != nil { + return 0, err + } + } + return tf.fileInfo.Size(), nil +} + +// Read reads a specified number of bytes from the file +func (tf *DaraFile) Read(size int) ([]byte, error) { + if tf.file == nil { + file, err := os.OpenFile(tf.path, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return nil, err + } + tf.file = file + } + + fileInfo, err := tf.file.Stat() + if err != nil { + return nil, err + } + + // 获取文件大小 + fileSize := fileInfo.Size() + + // 计算可以读取的实际大小 + if tf.position >= fileSize { + return nil, nil // End of file reached + } + + // 确保 size 不超过剩余文件大小 + actualSize := size + if tf.position+int64(size) > fileSize { + actualSize = int(fileSize - tf.position) + } + + buf := make([]byte, actualSize) + bytesRead, err := tf.file.ReadAt(buf, tf.position) + if err != nil { + return nil, err + } + tf.position += int64(bytesRead) + return buf[:bytesRead], nil +} + +// Write writes data to the file +func (tf *DaraFile) Write(data []byte) error { + if tf.file == nil { + file, err := os.OpenFile(tf.path, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return err + } + tf.file = file + } + + _, err := tf.file.Write(data) + if err != nil { + return err + } + + tf.fileInfo, err = os.Stat(tf.path) // Update fileInfo after write + return err +} + +// Close closes the file +func (tf *DaraFile) Close() error { + if tf.file == nil { + return nil + } + return tf.file.Close() +} + +// Exists checks if the file exists +func Exists(path string) (bool, error) { + _, err := os.Stat(path) + if os.IsNotExist(err) { + return false, nil + } + return err == nil, err +} + +// CreateReadStream would typically return an os.File or similar +func CreateReadStream(path string) (*os.File, error) { + return os.Open(path) +} + +// CreateWriteStream would typically return an os.File or similar +func CreateWriteStream(path string) (*os.File, error) { + return os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) +} diff --git a/dara/file_test.go b/dara/file_test.go new file mode 100644 index 0000000..3d538b4 --- /dev/null +++ b/dara/file_test.go @@ -0,0 +1,183 @@ +package dara + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +// TestNewDaraFile tests the NewDaraFile function +func TestNewDaraFile(t *testing.T) { + path := "testfile.txt" + tf := NewDaraFile(path) + if tf.Path() != path { + t.Errorf("Expected path to be %s, got %s", path, tf.Path()) + } +} + +// TestCreateTime tests the CreateTime method +func TestCreateTime(t *testing.T) { + path := "testfile.txt" + ioutil.WriteFile(path, []byte("test"), 0644) // 创建文件以确保它存在 + defer os.Remove(path) + + tf := NewDaraFile(path) + date, err := tf.CreateTime() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if date == nil { + t.Error("expected a valid TeaDate, got nil") + } +} + +// TestModifyTime tests the ModifyTime method +func TestModifyTime(t *testing.T) { + path := "testfile.txt" + ioutil.WriteFile(path, []byte("test"), 0644) + defer os.Remove(path) + + tf := NewDaraFile(path) + date, err := tf.ModifyTime() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if date == nil { + t.Error("expected a valid TeaDate, got nil") + } +} + +// TestLength tests the Length method +func TestLength(t *testing.T) { + path := "testfile.txt" + content := []byte("Hello, World!") + ioutil.WriteFile(path, content, 0644) + defer os.Remove(path) + + tf := NewDaraFile(path) + length, err := tf.Length() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if length != int64(len(content)) { + t.Errorf("expected length %d, got %d", len(content), length) + } +} + +// TestRead tests the Read method +func TestRead(t *testing.T) { + path := "testfile.txt" + content := []byte("Hello, World!") + ioutil.WriteFile(path, content, 0644) + defer os.Remove(path) + + tf := NewDaraFile(path) + data, err := tf.Read(5) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(data) != "Hello" { + t.Errorf("expected 'Hello', got '%s'", string(data)) + } + + // Read the rest of the file + data, err = tf.Read(10) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(data) != ", World!" { + t.Errorf("expected ', World!', got '%s'", string(data)) + } +} + +// TestWrite tests the Write method +func TestWrite(t *testing.T) { + path := "testfile.txt" + tf := NewDaraFile(path) + + data := []byte("Hello, Write Test!") + err := tf.Write(data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Validate the content of the file + readData, _ := ioutil.ReadFile(path) + if string(readData) != "Hello, Write Test!" { + t.Errorf("expected file content to be %s, got %s", string(data), string(readData)) + } +} + +// TestClose tests the Close method +func TestClose(t *testing.T) { + path := "testfile.txt" + tf := NewDaraFile(path) + err := tf.Close() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +// TestExists tests the Exists function +func TestExists(t *testing.T) { + path := "testfile.txt" + ioutil.WriteFile(path, []byte("test"), 0644) + defer os.Remove(path) + + exists, err := Exists(path) + if err != nil || !exists { + t.Errorf("expected file to exist, got error: %v", err) + } + + exists, err = Exists("nonexistent.txt") + if err != nil || exists { + t.Errorf("expected file to not exist, got error: %v", err) + } +} + +func TestCreateReadWriteStream(t *testing.T) { + tempDir, err := ioutil.TempDir("", "example") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + testFile := filepath.Join(tempDir, "test.txt") + testWFile := filepath.Join(tempDir, "test2.txt") + + // Prepare the test file + originalContent := "Hello, World!" + if err := ioutil.WriteFile(testFile, []byte(originalContent), 0644); err != nil { + t.Fatalf("failed to write test file: %v", err) + } + + // Test CreateReadStream + rs, err := CreateReadStream(testFile) + if err != nil { + t.Fatalf("failed to create read stream: %v", err) + } + defer rs.Close() + + // Test CreateWriteStream + ws, err := CreateWriteStream(testWFile) + if err != nil { + t.Fatalf("failed to create write stream: %v", err) + } + defer ws.Close() + + // Pipe data from read stream to write stream + if _, err := io.Copy(ws, rs); err != nil { + t.Fatalf("failed to copy data from read stream to write stream: %v", err) + } + + // Read back the content to check if it's correct + data, err := ioutil.ReadFile(testWFile) + if err != nil { + t.Fatalf("failed to read back test file: %v", err) + } + + if string(data) != originalContent { + t.Fatalf("expected %q but got %q", originalContent, string(data)) + } +} diff --git a/dara/form.go b/dara/form.go new file mode 100644 index 0000000..81931a1 --- /dev/null +++ b/dara/form.go @@ -0,0 +1,177 @@ +package dara + +import ( + "bytes" + "fmt" + "io" + "math/rand" + "net/url" + "strings" +) + +type FileField struct { + Filename *string `json:"filename" xml:"filename" require:"true"` + ContentType *string `json:"content-type" xml:"content-type" require:"true"` + Content io.Reader `json:"content" xml:"content" require:"true"` +} + +func (s *FileField) SetFilename(v string) *FileField { + s.Filename = &v + return s +} + +func (s *FileField) SetContentType(v string) *FileField { + s.ContentType = &v + return s +} + +func (s *FileField) SetContent(v io.Reader) *FileField { + s.Content = v + return s +} + +type FileFormReader struct { + formFiles []*formFile + formField io.Reader + index int + streaming bool + ifField bool +} + +type formFile struct { + StartField io.Reader + EndField io.Reader + File io.Reader + start bool + end bool +} + +const numBytes = "1234567890" + +func GetBoundary() string { + b := make([]byte, 14) + for i := range b { + b[i] = numBytes[rand.Intn(len(numBytes))] + } + return string(b) +} + +func ToFileForm(body map[string]interface{}, boundary string) io.Reader { + out := bytes.NewBuffer(nil) + line := "--" + boundary + "\r\n" + forms := make(map[string]string) + files := make(map[string]map[string]interface{}) + for key, value := range body { + switch value.(type) { + case *FileField: + if val, ok := value.(*FileField); ok { + out := make(map[string]interface{}) + out["filename"] = StringValue(val.Filename) + out["content-type"] = StringValue(val.ContentType) + out["content"] = val.Content + files[key] = out + } + case map[string]interface{}: + if val, ok := value.(map[string]interface{}); ok { + files[key] = val + } + default: + forms[key] = fmt.Sprintf("%v", value) + } + } + for key, value := range forms { + if value != "" { + out.Write([]byte(line)) + out.Write([]byte("Content-Disposition: form-data; name=\"" + key + "\"" + "\r\n\r\n")) + out.Write([]byte(value + "\r\n")) + } + } + formFiles := make([]*formFile, 0) + for key, value := range files { + var file io.Reader + start := line + start += "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + value["filename"].(string) + "\"\r\n" + start += "Content-Type: " + value["content-type"].(string) + "\r\n\r\n" + if content, ok := value["content"].(io.Reader); ok { + file = content + } else { + file = strings.NewReader("") + } + formFile := &formFile{ + File: file, + start: true, + StartField: strings.NewReader(start), + } + if len(files) == len(formFiles)+1 { + end := "\r\n\r\n--" + boundary + "--\r\n" + formFile.EndField = strings.NewReader(end) + } else { + formFile.EndField = strings.NewReader("\r\n\r\n") + } + formFiles = append(formFiles, formFile) + } + return &FileFormReader{ + formFiles: formFiles, + formField: out, + ifField: true, + } +} + +func (f *FileFormReader) Read(p []byte) (n int, err error) { + if f.ifField { + n, err = f.formField.Read(p) + if err != nil && err != io.EOF { + return n, err + } else if err == io.EOF { + err = nil + f.ifField = false + f.streaming = true + } + } else if f.streaming { + form := f.formFiles[f.index] + if form.start { + n, err = form.StartField.Read(p) + if err != nil && err != io.EOF { + return n, err + } else if err == io.EOF { + err = nil + form.start = false + } + } else if form.end { + n, err = form.EndField.Read(p) + if err != nil && err != io.EOF { + return n, err + } else if err == io.EOF { + f.index++ + form.end = false + if f.index < len(f.formFiles) { + err = nil + } + } + } else { + n, err = form.File.Read(p) + if err != nil && err != io.EOF { + return n, err + } else if err == io.EOF { + err = nil + form.end = true + } + } + } + + return n, err +} + +func ToFormString(a map[string]interface{}) string { + if a == nil { + return "" + } + res := "" + urlEncoder := url.Values{} + for key, value := range a { + v := fmt.Sprintf("%v", value) + urlEncoder.Add(key, v) + } + res = urlEncoder.Encode() + return res +} diff --git a/dara/form_test.go b/dara/form_test.go new file mode 100644 index 0000000..3e690ef --- /dev/null +++ b/dara/form_test.go @@ -0,0 +1,64 @@ +package dara + +import ( + "io/ioutil" + "strings" + "testing" + + "github.com/alibabacloud-go/tea/utils" +) + +func Test_ToFormString(t *testing.T) { + str := ToFormString(nil) + utils.AssertEqual(t, "", str) + + a := map[string]interface{}{ + "key1": "value1", + "key2": "value2", + } + str = ToFormString(a) + utils.AssertEqual(t, str, "key1=value1&key2=value2") +} + +type TestForm struct { + Ak *string `json:"ak"` + File1 *FileField `json:"file1"` + File2 *FileField `json:"file2"` +} + +func Test_ToFileForm(t *testing.T) { + file1 := new(FileField). + SetContent(strings.NewReader("ok")). + SetContentType("jpg"). + SetFilename("a.jpg") + body := map[string]interface{}{ + "ak": "accesskey", + "file1": file1, + } + res := ToFileForm(ToMap(body), "28802961715230") + byt, err := ioutil.ReadAll(res) + utils.AssertNil(t, err) + utils.AssertEqual(t, string(byt), "--28802961715230\r\nContent-Disposition: "+ + "form-data; name=\"ak\"\r\n\r\naccesskey\r\n--28802961715230\r\nContent-Disposition: "+ + "form-data; name=\"file1\"; filename=\"a.jpg\"\r\nContent-Type: jpg\r\n\r\nok\r\n\r\n--28802961715230--\r\n") + + body1 := &TestForm{ + Ak: String("accesskey"), + File1: &FileField{ + Filename: String("a.jpg"), + ContentType: String("jpg"), + Content: strings.NewReader("ok"), + }, + } + res = ToFileForm(ToMap(body1), "28802961715230") + byt, err = ioutil.ReadAll(res) + utils.AssertNil(t, err) + utils.AssertEqual(t, string(byt), "--28802961715230\r\nContent-Disposition: form-data; "+ + "name=\"ak\"\r\n\r\naccesskey\r\n--28802961715230\r\nContent-Disposition: "+ + "form-data; name=\"file1\"; filename=\"a.jpg\"\r\nContent-Type: jpg\r\n\r\n\r\n\r\n--28802961715230--\r\n") +} + +func Test_GetBoundary(t *testing.T) { + bound := GetBoundary() + utils.AssertEqual(t, len(bound), 14) +} diff --git a/dara/json_parser.go b/dara/json_parser.go new file mode 100644 index 0000000..d98aca8 --- /dev/null +++ b/dara/json_parser.go @@ -0,0 +1,369 @@ +package dara + +import ( + "bytes" + "encoding/json" + jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" + "io" + "math" + "reflect" + "strconv" + "strings" + "unsafe" +) + +const maxUint = ^uint(0) +const maxInt = int(maxUint >> 1) +const minInt = -maxInt - 1 + +var jsonParser jsoniter.API + +func init() { + jsonParser = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, + CaseSensitive: true, + }.Froze() + + jsonParser.RegisterExtension(newBetterFuzzyExtension()) +} + +func newBetterFuzzyExtension() jsoniter.DecoderExtension { + return jsoniter.DecoderExtension{ + reflect2.DefaultTypeOfKind(reflect.String): &nullableFuzzyStringDecoder{}, + reflect2.DefaultTypeOfKind(reflect.Bool): &fuzzyBoolDecoder{}, + reflect2.DefaultTypeOfKind(reflect.Float32): &nullableFuzzyFloat32Decoder{}, + reflect2.DefaultTypeOfKind(reflect.Float64): &nullableFuzzyFloat64Decoder{}, + reflect2.DefaultTypeOfKind(reflect.Int): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(maxInt) || val < float64(minInt) { + iter.ReportError("fuzzy decode int", "exceed range") + return + } + *((*int)(ptr)) = int(val) + } else { + *((*int)(ptr)) = iter.ReadInt() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Uint): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(maxUint) || val < 0 { + iter.ReportError("fuzzy decode uint", "exceed range") + return + } + *((*uint)(ptr)) = uint(val) + } else { + *((*uint)(ptr)) = iter.ReadUint() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Int8): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxInt8) || val < float64(math.MinInt8) { + iter.ReportError("fuzzy decode int8", "exceed range") + return + } + *((*int8)(ptr)) = int8(val) + } else { + *((*int8)(ptr)) = iter.ReadInt8() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Uint8): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxUint8) || val < 0 { + iter.ReportError("fuzzy decode uint8", "exceed range") + return + } + *((*uint8)(ptr)) = uint8(val) + } else { + *((*uint8)(ptr)) = iter.ReadUint8() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Int16): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxInt16) || val < float64(math.MinInt16) { + iter.ReportError("fuzzy decode int16", "exceed range") + return + } + *((*int16)(ptr)) = int16(val) + } else { + *((*int16)(ptr)) = iter.ReadInt16() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Uint16): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxUint16) || val < 0 { + iter.ReportError("fuzzy decode uint16", "exceed range") + return + } + *((*uint16)(ptr)) = uint16(val) + } else { + *((*uint16)(ptr)) = iter.ReadUint16() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Int32): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxInt32) || val < float64(math.MinInt32) { + iter.ReportError("fuzzy decode int32", "exceed range") + return + } + *((*int32)(ptr)) = int32(val) + } else { + *((*int32)(ptr)) = iter.ReadInt32() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Uint32): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxUint32) || val < 0 { + iter.ReportError("fuzzy decode uint32", "exceed range") + return + } + *((*uint32)(ptr)) = uint32(val) + } else { + *((*uint32)(ptr)) = iter.ReadUint32() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Int64): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxInt64) || val < float64(math.MinInt64) { + iter.ReportError("fuzzy decode int64", "exceed range") + return + } + *((*int64)(ptr)) = int64(val) + } else { + *((*int64)(ptr)) = iter.ReadInt64() + } + }}, + reflect2.DefaultTypeOfKind(reflect.Uint64): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { + if isFloat { + val := iter.ReadFloat64() + if val > float64(math.MaxUint64) || val < 0 { + iter.ReportError("fuzzy decode uint64", "exceed range") + return + } + *((*uint64)(ptr)) = uint64(val) + } else { + *((*uint64)(ptr)) = iter.ReadUint64() + } + }}, + } +} + +type nullableFuzzyStringDecoder struct { +} + +func (decoder *nullableFuzzyStringDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + valueType := iter.WhatIsNext() + switch valueType { + case jsoniter.NumberValue: + var number json.Number + iter.ReadVal(&number) + *((*string)(ptr)) = string(number) + case jsoniter.StringValue: + *((*string)(ptr)) = iter.ReadString() + case jsoniter.BoolValue: + *((*string)(ptr)) = strconv.FormatBool(iter.ReadBool()) + case jsoniter.NilValue: + iter.ReadNil() + *((*string)(ptr)) = "" + default: + iter.ReportError("fuzzyStringDecoder", "not number or string or bool") + } +} + +type fuzzyBoolDecoder struct { +} + +func (decoder *fuzzyBoolDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + valueType := iter.WhatIsNext() + switch valueType { + case jsoniter.BoolValue: + *((*bool)(ptr)) = iter.ReadBool() + case jsoniter.NumberValue: + var number json.Number + iter.ReadVal(&number) + num, err := number.Int64() + if err != nil { + iter.ReportError("fuzzyBoolDecoder", "get value from json.number failed") + } + if num == 0 { + *((*bool)(ptr)) = false + } else { + *((*bool)(ptr)) = true + } + case jsoniter.StringValue: + strValue := strings.ToLower(iter.ReadString()) + if strValue == "true" { + *((*bool)(ptr)) = true + } else if strValue == "false" || strValue == "" { + *((*bool)(ptr)) = false + } else { + iter.ReportError("fuzzyBoolDecoder", "unsupported bool value: "+strValue) + } + case jsoniter.NilValue: + iter.ReadNil() + *((*bool)(ptr)) = false + default: + iter.ReportError("fuzzyBoolDecoder", "not number or string or nil") + } +} + +type nullableFuzzyIntegerDecoder struct { + fun func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) +} + +func (decoder *nullableFuzzyIntegerDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + valueType := iter.WhatIsNext() + var str string + switch valueType { + case jsoniter.NumberValue: + var number json.Number + iter.ReadVal(&number) + str = string(number) + case jsoniter.StringValue: + str = iter.ReadString() + // support empty string + if str == "" { + str = "0" + } + case jsoniter.BoolValue: + if iter.ReadBool() { + str = "1" + } else { + str = "0" + } + case jsoniter.NilValue: + iter.ReadNil() + str = "0" + default: + iter.ReportError("fuzzyIntegerDecoder", "not number or string") + } + newIter := iter.Pool().BorrowIterator([]byte(str)) + defer iter.Pool().ReturnIterator(newIter) + isFloat := strings.IndexByte(str, '.') != -1 + decoder.fun(isFloat, ptr, newIter) + if newIter.Error != nil && newIter.Error != io.EOF { + iter.Error = newIter.Error + } +} + +type nullableFuzzyFloat32Decoder struct { +} + +func (decoder *nullableFuzzyFloat32Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + valueType := iter.WhatIsNext() + var str string + switch valueType { + case jsoniter.NumberValue: + *((*float32)(ptr)) = iter.ReadFloat32() + case jsoniter.StringValue: + str = iter.ReadString() + // support empty string + if str == "" { + *((*float32)(ptr)) = 0 + return + } + newIter := iter.Pool().BorrowIterator([]byte(str)) + defer iter.Pool().ReturnIterator(newIter) + *((*float32)(ptr)) = newIter.ReadFloat32() + if newIter.Error != nil && newIter.Error != io.EOF { + iter.Error = newIter.Error + } + case jsoniter.BoolValue: + // support bool to float32 + if iter.ReadBool() { + *((*float32)(ptr)) = 1 + } else { + *((*float32)(ptr)) = 0 + } + case jsoniter.NilValue: + iter.ReadNil() + *((*float32)(ptr)) = 0 + default: + iter.ReportError("nullableFuzzyFloat32Decoder", "not number or string") + } +} + +type nullableFuzzyFloat64Decoder struct { +} + +func (decoder *nullableFuzzyFloat64Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + valueType := iter.WhatIsNext() + var str string + switch valueType { + case jsoniter.NumberValue: + *((*float64)(ptr)) = iter.ReadFloat64() + case jsoniter.StringValue: + str = iter.ReadString() + // support empty string + if str == "" { + *((*float64)(ptr)) = 0 + return + } + newIter := iter.Pool().BorrowIterator([]byte(str)) + defer iter.Pool().ReturnIterator(newIter) + *((*float64)(ptr)) = newIter.ReadFloat64() + if newIter.Error != nil && newIter.Error != io.EOF { + iter.Error = newIter.Error + } + case jsoniter.BoolValue: + // support bool to float64 + if iter.ReadBool() { + *((*float64)(ptr)) = 1 + } else { + *((*float64)(ptr)) = 0 + } + case jsoniter.NilValue: + // support empty string + iter.ReadNil() + *((*float64)(ptr)) = 0 + default: + iter.ReportError("nullableFuzzyFloat64Decoder", "not number or string") + } +} + +func Stringify(m interface{}) string { + byt, _ := json.Marshal(m) + return string(byt) +} + +func ParseJSON(a string) interface{} { + mapTmp := make(map[string]interface{}) + d := json.NewDecoder(bytes.NewReader([]byte(a))) + d.UseNumber() + err := d.Decode(&mapTmp) + if err == nil { + return mapTmp + } + + sliceTmp := make([]interface{}, 0) + d = json.NewDecoder(bytes.NewReader([]byte(a))) + d.UseNumber() + err = d.Decode(&sliceTmp) + if err == nil { + return sliceTmp + } + + if num, err := strconv.Atoi(a); err == nil { + return num + } + + if ok, err := strconv.ParseBool(a); err == nil { + return ok + } + + if floa64tVal, err := strconv.ParseFloat(a, 64); err == nil { + return floa64tVal + } + return nil +} diff --git a/dara/json_parser_test.go b/dara/json_parser_test.go new file mode 100644 index 0000000..f3998a3 --- /dev/null +++ b/dara/json_parser_test.go @@ -0,0 +1,880 @@ +package dara + +import ( + "reflect" + "testing" + + "github.com/alibabacloud-go/tea/utils" + jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" +) + +func TestUnmarshal(t *testing.T) { + from := []byte(`{}`) + to := &struct{}{} + // support auto json type trans + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{}`, string(str)) +} + +func TestUnmarshal_int(t *testing.T) { + to := &struct { + INT int + }{} + from := []byte(`{"INT":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 100, to.INT) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":100}`, string(str)) + + from = []byte(`{"INT":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 100, to.INT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":100}`, string(str)) + + // string to int + from = []byte(`{"INT":"100"}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 100, to.INT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":100}`, string(str)) + + from = []byte(`{"INT":""}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 0, to.INT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":0}`, string(str)) + + // bool to int + from = []byte(`{"INT":true}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 1, to.INT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":1}`, string(str)) + + from = []byte(`{"INT":false}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 0, to.INT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":0}`, string(str)) + + // nil to int + from = []byte(`{"INT":null}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 0, to.INT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT":0}`, string(str)) + + // fuzzy decode int + from = []byte(`{"INT":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "INT: fuzzy decode int: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) + + from = []byte(`{"INT":{}}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "INT: readUint64: unexpected character: \xff, error found in #0 byte of ...||..., bigger context ...||...", err.Error()) +} + +func TestUnmarshal_uint(t *testing.T) { + to := &struct { + UINT uint + }{} + from := []byte(`{"UINT":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint(100), to.UINT) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT":100}`, string(str)) + + from = []byte(`{"UINT":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint(100), to.UINT) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"UINT":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "UINT: fuzzy decode uint: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_int8(t *testing.T) { + to := &struct { + INT8 int8 + }{} + from := []byte(`{"INT8":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int8(100), to.INT8) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT8":100}`, string(str)) + + from = []byte(`{"INT8":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int8(100), to.INT8) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT8":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"INT8":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "INT8: fuzzy decode int8: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_uint8(t *testing.T) { + to := &struct { + UINT8 uint8 + }{} + from := []byte(`{"UINT8":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint8(100), to.UINT8) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT8":100}`, string(str)) + + from = []byte(`{"UINT8":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint8(100), to.UINT8) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT8":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"UINT8":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "UINT8: fuzzy decode uint8: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_int16(t *testing.T) { + to := &struct { + INT16 int16 + }{} + from := []byte(`{"INT16":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int16(100), to.INT16) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT16":100}`, string(str)) + + from = []byte(`{"INT16":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int16(100), to.INT16) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT16":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"INT16":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "INT16: fuzzy decode int16: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_uint16(t *testing.T) { + to := &struct { + UINT16 uint16 + }{} + from := []byte(`{"UINT16":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint16(100), to.UINT16) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT16":100}`, string(str)) + + from = []byte(`{"UINT16":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint16(100), to.UINT16) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT16":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"UINT16":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "UINT16: fuzzy decode uint16: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_int32(t *testing.T) { + to := &struct { + INT32 int32 + }{} + from := []byte(`{"INT32":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int32(100), to.INT32) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT32":100}`, string(str)) + + from = []byte(`{"INT32":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int32(100), to.INT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT32":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"INT32":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "INT32: fuzzy decode int32: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_uint32(t *testing.T) { + to := &struct { + UINT32 uint32 + }{} + from := []byte(`{"UINT32":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint32(100), to.UINT32) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT32":100}`, string(str)) + + from = []byte(`{"UINT32":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint32(100), to.UINT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT32":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"UINT32":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "UINT32: fuzzy decode uint32: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_int64(t *testing.T) { + to := &struct { + INT64 int64 + }{} + from := []byte(`{"INT64":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int64(100), to.INT64) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT64":100}`, string(str)) + + from = []byte(`{"INT64":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, int64(100), to.INT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"INT64":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"INT64":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "INT64: fuzzy decode int64: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_uint64(t *testing.T) { + to := &struct { + UINT64 uint64 + }{} + from := []byte(`{"UINT64":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint64(100), to.UINT64) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT64":100}`, string(str)) + + from = []byte(`{"UINT64":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, uint64(100), to.UINT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"UINT64":100}`, string(str)) + + // fuzzy decode uint + from = []byte(`{"UINT64":100000000000000000000000000.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "UINT64: fuzzy decode uint64: exceed range, error found in #10 byte of ...|00000000.1|..., bigger context ...|100000000000000000000000000.1|...", err.Error()) +} + +func TestUnmarshal_string(t *testing.T) { + to := &struct { + STRING string + }{} + // string to string + from := []byte(`{"STRING":""}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, "", to.STRING) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"STRING":""}`, string(str)) + + // number to string + from = []byte(`{"STRING":100}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, "100", to.STRING) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"STRING":"100"}`, string(str)) + + // bool to string + from = []byte(`{"STRING":true}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, "true", to.STRING) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"STRING":"true"}`, string(str)) + + // nil to string + from = []byte(`{"STRING":null}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, "", to.STRING) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"STRING":""}`, string(str)) + + // other to string + from = []byte(`{"STRING":{}}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "STRING: fuzzyStringDecoder: not number or string or bool, error found in #10 byte of ...|{\"STRING\":{}}|..., bigger context ...|{\"STRING\":{}}|...", err.Error()) +} + +func TestUnmarshal_bool(t *testing.T) { + to := &struct { + BOOL bool + }{} + // bool to bool + from := []byte(`{"BOOL":true}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, true, to.BOOL) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"BOOL":true}`, string(str)) + + // number to bool + from = []byte(`{"BOOL":100}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, true, to.BOOL) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"BOOL":true}`, string(str)) + + from = []byte(`{"BOOL":0}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, false, to.BOOL) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"BOOL":false}`, string(str)) + + // invalid number literal + from = []byte(`{"BOOL": 1000000000000000000000000000000000000000}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "BOOL: fuzzyBoolDecoder: get value from json.number failed, error found in #10 byte of ...|0000000000}|..., bigger context ...|{\"BOOL\": 1000000000000000000000000000000000000000}|...", err.Error()) + + // bool to string + from = []byte(`{"BOOL":"true"}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, true, to.BOOL) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"BOOL":true}`, string(str)) + + from = []byte(`{"BOOL":"false"}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, false, to.BOOL) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"BOOL":false}`, string(str)) + + from = []byte(`{"BOOL":"other"}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "BOOL: fuzzyBoolDecoder: unsupported bool value: other, error found in #10 byte of ...|L\":\"other\"}|..., bigger context ...|{\"BOOL\":\"other\"}|...", err.Error()) + + // nil to bool + from = []byte(`{"BOOL":null}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, false, to.BOOL) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"BOOL":false}`, string(str)) + + // other to string + from = []byte(`{"BOOL":{}}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "BOOL: fuzzyBoolDecoder: not number or string or nil, error found in #8 byte of ...|{\"BOOL\":{}}|..., bigger context ...|{\"BOOL\":{}}|...", err.Error()) +} + +func TestUnmarshal_array(t *testing.T) { + to := &struct { + Array []string + }{} + // bool to bool + from := []byte(`{"Array":[]}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, 0, len(to.Array)) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"Array":[]}`, string(str)) +} + +func TestUnmarshal_float32(t *testing.T) { + to := &struct { + FLOAT32 float32 + }{} + from := []byte(`{"FLOAT32":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(100), to.FLOAT32) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":100}`, string(str)) + + from = []byte(`{"FLOAT32":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(100.1), to.FLOAT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":100.1}`, string(str)) + + // string to float32 + from = []byte(`{"FLOAT32":"100.1"}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(100.1), to.FLOAT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":100.1}`, string(str)) + + from = []byte(`{"FLOAT32":""}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(0), to.FLOAT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":0}`, string(str)) + + // error branch + from = []byte(`{"FLOAT32":"."}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "FLOAT32: readFloat32: leading dot is invalid, error found in #0 byte of ...|.|..., bigger context ...|.|...", err.Error()) + + // bool to float32 + from = []byte(`{"FLOAT32":true}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(1), to.FLOAT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":1}`, string(str)) + + from = []byte(`{"FLOAT32":false}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(0), to.FLOAT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":0}`, string(str)) + + // nil to float32 + from = []byte(`{"FLOAT32":null}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float32(0), to.FLOAT32) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT32":0}`, string(str)) + + // others to float32 + from = []byte(`{"FLOAT32":{}}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "FLOAT32: nullableFuzzyFloat32Decoder: not number or string, error found in #10 byte of ...|\"FLOAT32\":{}}|..., bigger context ...|{\"FLOAT32\":{}}|...", err.Error()) +} + +func TestUnmarshal_float64(t *testing.T) { + to := &struct { + FLOAT64 float64 + }{} + from := []byte(`{"FLOAT64":100}`) + + err := jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(100), to.FLOAT64) + str, err := jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":100}`, string(str)) + + from = []byte(`{"FLOAT64":100.1}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(100.1), to.FLOAT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":100.1}`, string(str)) + + // string to float64 + from = []byte(`{"FLOAT64":"100.1"}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(100.1), to.FLOAT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":100.1}`, string(str)) + + from = []byte(`{"FLOAT64":""}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(0), to.FLOAT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":0}`, string(str)) + + // error branch + from = []byte(`{"FLOAT64":"."}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "FLOAT64: readFloat64: leading dot is invalid, error found in #0 byte of ...|.|..., bigger context ...|.|...", err.Error()) + + // bool to float64 + from = []byte(`{"FLOAT64":true}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(1), to.FLOAT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":1}`, string(str)) + + from = []byte(`{"FLOAT64":false}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(0), to.FLOAT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":0}`, string(str)) + + // nil to float64 + from = []byte(`{"FLOAT64":null}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNil(t, err) + utils.AssertEqual(t, float64(0), to.FLOAT64) + str, err = jsonParser.Marshal(to) + utils.AssertNil(t, err) + utils.AssertEqual(t, `{"FLOAT64":0}`, string(str)) + + // others to float64 + from = []byte(`{"FLOAT64":{}}`) + + err = jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) + utils.AssertEqual(t, "FLOAT64: nullableFuzzyFloat64Decoder: not number or string, error found in #10 byte of ...|\"FLOAT64\":{}}|..., bigger context ...|{\"FLOAT64\":{}}|...", err.Error()) +} + +func TestUnmarshalWithArray(t *testing.T) { + from := []byte(`[]`) + to := &struct{}{} + // TODO: Must support Array + // support auto json type trans + err := jsonParser.Unmarshal(from, to) + utils.AssertNotNil(t, err) +} + +func TestNewBetterFuzzyExtension(t *testing.T) { + betterFuzzyExtension := newBetterFuzzyExtension() + utils.AssertNotNil(t, betterFuzzyExtension) + + decoder := betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.String)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Bool)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Float32)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Float64)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Int)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Uint)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Int8)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Uint8)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Int16)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Uint16)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Int32)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Uint32)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Int64)] + utils.AssertNotNil(t, decoder) + + decoder = betterFuzzyExtension[reflect2.DefaultTypeOfKind(reflect.Uint64)] + utils.AssertNotNil(t, decoder) +} + +func TestUnmarshalWithDefaultDecoders(t *testing.T) { + // should not be valid with default decoders + from := []byte(`{"STRING":true}`) + toString := &struct { + STRING string + }{} + + err := jsoniter.Unmarshal(from, toString) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"BOOL":""}`) + toBool := &struct { + BOOL bool + }{} + + err = jsoniter.Unmarshal(from, toBool) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"FLOAT32":""}`) + toFloat32 := &struct { + FLOAT32 float32 + }{} + + err = jsoniter.Unmarshal(from, toFloat32) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"FLOAT64":""}`) + toFloat64 := &struct { + FLOAT64 float64 + }{} + + err = jsoniter.Unmarshal(from, toFloat64) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"INT":""}`) + toInt := &struct { + INT int + }{} + + err = jsoniter.Unmarshal(from, toInt) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"UINT":""}`) + toUint := &struct { + UINT uint + }{} + + err = jsoniter.Unmarshal(from, toUint) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"INT8":""}`) + toInt8 := &struct { + INT8 int8 + }{} + + err = jsoniter.Unmarshal(from, toInt8) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"UINT8":""}`) + toUint8 := &struct { + UINT8 uint8 + }{} + + err = jsoniter.Unmarshal(from, toUint8) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"INT16":""}`) + toInt16 := &struct { + INT16 int16 + }{} + + err = jsoniter.Unmarshal(from, toInt16) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"UINT16":""}`) + toUint16 := &struct { + UINT16 uint16 + }{} + + err = jsoniter.Unmarshal(from, toUint16) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"INT32":""}`) + toInt32 := &struct { + INT32 int32 + }{} + + err = jsoniter.Unmarshal(from, toInt32) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"UINT32":""}`) + toUint32 := &struct { + UINT32 uint32 + }{} + + err = jsoniter.Unmarshal(from, toUint32) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"INT64":""}`) + toInt64 := &struct { + INT64 int64 + }{} + + err = jsoniter.Unmarshal(from, toInt64) + utils.AssertNotNil(t, err) + + // should not be valid with default decoders + from = []byte(`{"UINT64":""}`) + toUint64 := &struct { + UINT64 uint64 + }{} + + err = jsoniter.Unmarshal(from, toUint64) + utils.AssertNotNil(t, err) +} diff --git a/dara/map.go b/dara/map.go new file mode 100644 index 0000000..9556c4d --- /dev/null +++ b/dara/map.go @@ -0,0 +1,49 @@ +package dara + +import ( + "reflect" +) + +type Entry struct { + Key string + Value interface{} +} + +// toFloat64 converts a numeric value to float64 for comparison +func Entries(m interface{}) []*Entry { + v := reflect.ValueOf(m) + if v.Kind() != reflect.Map { + panic("Entries: input must be a map") + } + + entries := make([]*Entry, 0, v.Len()) + for _, key := range v.MapKeys() { + // 确保 Key 是字符串类型 + if key.Kind() != reflect.String { + panic("Entries: map keys must be of type string") + } + + value := v.MapIndex(key) + entries = append(entries, &Entry{ + Key: key.String(), + Value: value.Interface(), + }) + } + return entries +} + +func KeySet(m interface{}) []string { + v := reflect.ValueOf(m) + if v.Kind() != reflect.Map { + panic("KeySet: input must be a map") + } + + keys := make([]string, 0, v.Len()) + for _, key := range v.MapKeys() { + if key.Kind() != reflect.String { + panic("KeySet: map keys must be of type string") + } + keys = append(keys, key.String()) + } + return keys +} diff --git a/dara/map_test.go b/dara/map_test.go new file mode 100644 index 0000000..65bfefe --- /dev/null +++ b/dara/map_test.go @@ -0,0 +1,59 @@ +package dara + +import ( + "reflect" + "testing" +) + +type Person struct { + Name string + Age int +} + +func TestEntries(t *testing.T) { + // 定义一个包含多种类型的 map + type Person struct { + Name string + Age int + } + + testMap := map[string]interface{}{ + "one": 1, + "two": "two", + "three": &Person{Name: "Alice", Age: 30}, + } + + entries := Entries(testMap) + + if len(entries) != 3 { + t.Errorf("expected %d entries, got %d", 3, len(entries)) + } + + for _, entry := range entries { + if !reflect.DeepEqual(entry.Value, testMap[entry.Key]) { + t.Errorf("expected entry %s to be %v, got %v", entry.Key, testMap[entry.Key], entry.Value) + } + } +} + +func TestKeySet(t *testing.T) { + testMap := map[string]interface{}{ + "one": 1, + "two": "two", + "three": &Person{Name: "Alice", Age: 30}, + } + + keys := KeySet(testMap) + str1, str2, str3 := "one", "two", "three" + expectedKeys := []*string{&str1, &str2, &str3} + + if len(keys) != len(expectedKeys) { + t.Errorf("expected %d keys, got %d", len(expectedKeys), len(keys)) + } + + for _, key := range keys { + if !ArrContains(expectedKeys, key) { + t.Errorf("expected key %s to be in the array %v, but not", key, expectedKeys) + } + } +} diff --git a/dara/math.go b/dara/math.go new file mode 100644 index 0000000..64820f6 --- /dev/null +++ b/dara/math.go @@ -0,0 +1,45 @@ +package dara + +import ( + "math" + "math/rand" + "reflect" + "time" +) + +// Number is an interface that can be implemented by any numeric type +type Number interface{} + +// toFloat64 converts a numeric value to float64 for comparison +func toFloat64(n Number) float64 { + v := reflect.ValueOf(n) + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(v.Uint()) + case reflect.Float32, reflect.Float64: + return v.Float() + default: + panic("unsupported type") + } +} + +// Floor returns the largest integer less than or equal to the input number as int +func Floor(n Number) int { + v := toFloat64(n) + floorValue := math.Floor(v) + return int(floorValue) +} + +// Round returns the nearest integer to the input number as int +func Round(n Number) int { + v := toFloat64(n) + roundValue := math.Round(v) + return int(roundValue) +} + +func Random() float64 { + rand.Seed(time.Now().UnixNano()) + return rand.Float64() +} diff --git a/dara/math_test.go b/dara/math_test.go new file mode 100644 index 0000000..a4c8ea3 --- /dev/null +++ b/dara/math_test.go @@ -0,0 +1,62 @@ +package dara + +import ( + "testing" +) + +// TestRandom tests the Random function to ensure it returns a value in the range [0.0, 1.0) +func TestRandom(t *testing.T) { + for i := 0; i < 100; i++ { + val := Random() + if val < 0.0 || val >= 1.0 { + t.Errorf("Random() = %v, want [0.0, 1.0)", val) + } + } +} + +// TestFloor tests the Floor function with various numeric inputs +func TestFloor(t *testing.T) { + tests := []struct { + input Number + expected int + }{ + {3.7, 3}, + {-3.7, -4}, + {0.9, 0}, + {0.0, 0}, + {-0.9, -1}, + {int64(3), 3}, + {int32(-3), -3}, + } + + for _, tt := range tests { + result := Floor(tt.input) + if result != tt.expected { + t.Errorf("Floor(%v) = %v, want %v", tt.input, result, tt.expected) + } + } +} + +// TestRound tests the Round function with various numeric inputs +func TestRound(t *testing.T) { + tests := []struct { + input Number + expected int + }{ + {3.7, 4}, + {-3.7, -4}, + {2.5, 3}, + {2.4, 2}, + {-2.5, -3}, + {0.0, 0}, + {int64(4), 4}, + {int32(-4), -4}, + } + + for _, tt := range tests { + result := Round(tt.input) + if result != tt.expected { + t.Errorf("Round(%v) = %v, want %v", tt.input, result, tt.expected) + } + } +} diff --git a/dara/model.go b/dara/model.go new file mode 100644 index 0000000..9e4c1d5 --- /dev/null +++ b/dara/model.go @@ -0,0 +1,280 @@ +package dara + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" +) + +type Model interface { + Validate() error + ToMap() map[string]interface{} + copyWithouStream() Model +} + +func Validate(params interface{}) error { + if params == nil { + return nil + } + requestValue := reflect.ValueOf(params) + if requestValue.IsNil() { + return nil + } + err := validate(requestValue.Elem()) + return err +} + +// Verify whether the parameters meet the requirements +func validate(dataValue reflect.Value) error { + if strings.HasPrefix(dataValue.Type().String(), "*") { // Determines whether the input is a structure object or a pointer object + if dataValue.IsNil() { + return nil + } + dataValue = dataValue.Elem() + } + dataType := dataValue.Type() + for i := 0; i < dataType.NumField(); i++ { + field := dataType.Field(i) + valueField := dataValue.Field(i) + for _, value := range validateParams { + err := validateParam(field, valueField, value) + if err != nil { + return err + } + } + } + return nil +} + +func validateParam(field reflect.StructField, valueField reflect.Value, tagName string) error { + tag, containsTag := field.Tag.Lookup(tagName) // Take out the checked regular expression + if containsTag && tagName == "require" { + err := checkRequire(field, valueField) + if err != nil { + return err + } + } + if strings.HasPrefix(field.Type.String(), "[]") { // Verify the parameters of the array type + err := validateSlice(field, valueField, containsTag, tag, tagName) + if err != nil { + return err + } + } else if valueField.Kind() == reflect.Ptr { // Determines whether it is a pointer object + err := validatePtr(field, valueField, containsTag, tag, tagName) + if err != nil { + return err + } + } + return nil +} + +func validateSlice(field reflect.StructField, valueField reflect.Value, containsregexpTag bool, tag, tagName string) error { + if valueField.IsValid() && !valueField.IsNil() { // Determines whether the parameter has a value + if containsregexpTag { + if tagName == "maxItems" { + err := checkMaxItems(field, valueField, tag) + if err != nil { + return err + } + } + + if tagName == "minItems" { + err := checkMinItems(field, valueField, tag) + if err != nil { + return err + } + } + } + + for m := 0; m < valueField.Len(); m++ { + elementValue := valueField.Index(m) + if elementValue.Type().Kind() == reflect.Ptr { // Determines whether the child elements of an array are of a basic type + err := validatePtr(field, elementValue, containsregexpTag, tag, tagName) + if err != nil { + return err + } + } + } + } + return nil +} + +func validatePtr(field reflect.StructField, elementValue reflect.Value, containsregexpTag bool, tag, tagName string) error { + if elementValue.IsNil() { + return nil + } + if isFilterType(elementValue.Elem().Type().String(), basicTypes) { + if containsregexpTag { + if tagName == "pattern" { + err := checkPattern(field, elementValue.Elem(), tag) + if err != nil { + return err + } + } + + if tagName == "maxLength" { + err := checkMaxLength(field, elementValue.Elem(), tag) + if err != nil { + return err + } + } + + if tagName == "minLength" { + err := checkMinLength(field, elementValue.Elem(), tag) + if err != nil { + return err + } + } + + if tagName == "maximum" { + err := checkMaximum(field, elementValue.Elem(), tag) + if err != nil { + return err + } + } + + if tagName == "minimum" { + err := checkMinimum(field, elementValue.Elem(), tag) + if err != nil { + return err + } + } + } + } else { + err := validate(elementValue) + if err != nil { + return err + } + } + return nil +} + +func checkRequire(field reflect.StructField, valueField reflect.Value) error { + name, _ := field.Tag.Lookup("json") + strs := strings.Split(name, ",") + name = strs[0] + if !valueField.IsNil() && valueField.IsValid() { + return nil + } + return errors.New(name + " should be setted") +} + +func checkPattern(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() && valueField.String() != "" { + value := valueField.String() + r, _ := regexp.Compile("^" + tag + "$") + if match := r.MatchString(value); !match { // Determines whether the parameter value satisfies the regular expression or not, and throws an error + return errors.New(value + " is not matched " + tag) + } + } + return nil +} + +func checkMaxItems(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() && valueField.String() != "" { + maxItems, err := strconv.Atoi(tag) + if err != nil { + return err + } + length := valueField.Len() + if maxItems < length { + errMsg := fmt.Sprintf("The length of %s is %d which is more than %d", field.Name, length, maxItems) + return errors.New(errMsg) + } + } + return nil +} + +func checkMinItems(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() { + minItems, err := strconv.Atoi(tag) + if err != nil { + return err + } + length := valueField.Len() + if minItems > length { + errMsg := fmt.Sprintf("The length of %s is %d which is less than %d", field.Name, length, minItems) + return errors.New(errMsg) + } + } + return nil +} + +func checkMaxLength(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() && valueField.String() != "" { + maxLength, err := strconv.Atoi(tag) + if err != nil { + return err + } + length := valueField.Len() + if valueField.Kind().String() == "string" { + length = strings.Count(valueField.String(), "") - 1 + } + if maxLength < length { + errMsg := fmt.Sprintf("The length of %s is %d which is more than %d", field.Name, length, maxLength) + return errors.New(errMsg) + } + } + return nil +} + +func checkMinLength(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() { + minLength, err := strconv.Atoi(tag) + if err != nil { + return err + } + length := valueField.Len() + if valueField.Kind().String() == "string" { + length = strings.Count(valueField.String(), "") - 1 + } + if minLength > length { + errMsg := fmt.Sprintf("The length of %s is %d which is less than %d", field.Name, length, minLength) + return errors.New(errMsg) + } + } + return nil +} + +func checkMaximum(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() && valueField.String() != "" { + maximum, err := strconv.ParseFloat(tag, 64) + if err != nil { + return err + } + byt, _ := json.Marshal(valueField.Interface()) + num, err := strconv.ParseFloat(string(byt), 64) + if err != nil { + return err + } + if maximum < num { + errMsg := fmt.Sprintf("The size of %s is %f which is greater than %f", field.Name, num, maximum) + return errors.New(errMsg) + } + } + return nil +} + +func checkMinimum(field reflect.StructField, valueField reflect.Value, tag string) error { + if valueField.IsValid() && valueField.String() != "" { + minimum, err := strconv.ParseFloat(tag, 64) + if err != nil { + return err + } + + byt, _ := json.Marshal(valueField.Interface()) + num, err := strconv.ParseFloat(string(byt), 64) + if err != nil { + return err + } + if minimum > num { + errMsg := fmt.Sprintf("The size of %s is %f which is less than %f", field.Name, num, minimum) + return errors.New(errMsg) + } + } + return nil +} diff --git a/dara/retry.go b/dara/retry.go new file mode 100644 index 0000000..d4b14e1 --- /dev/null +++ b/dara/retry.go @@ -0,0 +1,373 @@ +package dara + +import ( + "fmt" + "math" + "math/rand" +) + +const ( + MAX_DELAY_TIME = 120 * 1000 // 120 seconds + MIN_DELAY_TIME = 100 // 100 milliseconds + DEFAULT_MAX_CAP = 3 * 24 * 60 * 60 * 1000 // 3 days in milliseconds + MAX_ATTEMPTS = 3 +) + +// RetryPolicyContext holds context for the retry operation +type RetryPolicyContext struct { + Key string + RetriesAttempted int + HttpRequest *Request // placeholder for actual http.Request type + HttpResponse *Response // placeholder for actual http.Response type + Exception BaseError +} + +// BackoffPolicy interface with a method to get delay time +type BackoffPolicy interface { + GetDelayTime(ctx *RetryPolicyContext) int +} + +// BackoffPolicyFactory creates a BackoffPolicy based on the option +func BackoffPolicyFactory(option map[string]interface{}) (BackoffPolicy, error) { + + switch option["policy"] { + case "Fixed": + return NewFixedBackoffPolicy(option), nil + case "Random": + return NewRandomBackoffPolicy(option), nil + case "Exponential": + return NewExponentialBackoffPolicy(option), nil + case "EqualJitter", "ExponentialWithEqualJitter": + return NewEqualJitterBackoffPolicy(option), nil + case "FullJitter", "ExponentialWithFullJitter": + return NewFullJitterBackoffPolicy(option), nil + } + return nil, fmt.Errorf("unknown policy type") +} + +// FixedBackoffPolicy implementation +type FixedBackoffPolicy struct { + Period int +} + +func NewFixedBackoffPolicy(option map[string]interface{}) *FixedBackoffPolicy { + var period int + if v, ok := option["period"]; ok { + period = v.(int) + } + return &FixedBackoffPolicy{ + Period: period, + } +} + +func (f *FixedBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int { + return f.Period +} + +// RandomBackoffPolicy implementation +type RandomBackoffPolicy struct { + Period int + Cap int +} + +func NewRandomBackoffPolicy(option map[string]interface{}) *RandomBackoffPolicy { + var capValue int + var period int + if v, ok := option["cap"]; ok { + capValue = v.(int) + } else { + capValue = 20 * 1000 + } + if v, ok := option["period"]; ok { + period = v.(int) + } + return &RandomBackoffPolicy{ + Period: period, + Cap: capValue, + } +} + +func (r *RandomBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int { + randomSeed := int64(ctx.RetriesAttempted * r.Period) + randomTime := int(rand.Int63n(randomSeed)) + if randomTime > r.Cap { + return r.Cap + } + return randomTime +} + +// ExponentialBackoffPolicy implementation +type ExponentialBackoffPolicy struct { + Period int + Cap int +} + +func NewExponentialBackoffPolicy(option map[string]interface{}) *ExponentialBackoffPolicy { + var capValue int + var period int + if v, ok := option["cap"]; ok { + capValue = v.(int) + } else { + capValue = DEFAULT_MAX_CAP + } + if v, ok := option["period"]; ok { + period = v.(int) + } + return &ExponentialBackoffPolicy{ + Period: period, + Cap: capValue, + } +} + +func (e *ExponentialBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int { + randomTime := int(math.Pow(2, float64(ctx.RetriesAttempted)*float64(e.Period))) + if randomTime > e.Cap { + return e.Cap + } + return randomTime +} + +// EqualJitterBackoffPolicy implementation +type EqualJitterBackoffPolicy struct { + Period int + Cap int +} + +func NewEqualJitterBackoffPolicy(option map[string]interface{}) *EqualJitterBackoffPolicy { + var capValue int + var period int + if v, ok := option["cap"]; ok { + capValue = v.(int) + } else { + capValue = DEFAULT_MAX_CAP + } + + if v, ok := option["period"]; ok { + period = v.(int) + } + return &EqualJitterBackoffPolicy{ + Period: period, + Cap: capValue, + } +} + +func (e *EqualJitterBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int { + ceil := int64(math.Min(float64(e.Cap), float64(math.Pow(2, float64(ctx.RetriesAttempted)*float64(e.Period))))) + randNum := rand.Int63n(ceil/2 + 1) + return int(ceil/2 + randNum) +} + +// FullJitterBackoffPolicy implementation +type FullJitterBackoffPolicy struct { + Period int + Cap int +} + +func NewFullJitterBackoffPolicy(option map[string]interface{}) *FullJitterBackoffPolicy { + var capValue int + var period int + if v, ok := option["cap"]; ok { + capValue = v.(int) + } else { + capValue = DEFAULT_MAX_CAP + } + if v, ok := option["period"]; ok { + period = v.(int) + } + return &FullJitterBackoffPolicy{ + Period: period, + Cap: capValue, + } +} + +func (f *FullJitterBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int { + ceil := int64(math.Min(float64(f.Cap), float64(math.Pow(2, float64(ctx.RetriesAttempted)*float64(f.Period))))) + return int(rand.Int63n(ceil)) +} + +// RetryCondition holds the retry conditions +type RetryCondition struct { + MaxAttempts int + Backoff BackoffPolicy + Exception []string + ErrorCode []string + MaxDelay int +} + +func NewRetryCondition(condition map[string]interface{}) *RetryCondition { + var backoff BackoffPolicy + if condition["backoff"] != nil { + backoffOption := condition["backoff"].(map[string]interface{}) + backoff, _ = BackoffPolicyFactory(backoffOption) + } + maxAttempts, ok := condition["maxAttempts"].(int) + if !ok { + maxAttempts = MAX_ATTEMPTS + } + + exception, ok := condition["exception"].([]string) + if !ok { + exception = []string{} + } + + errorCode, ok := condition["errorCode"].([]string) + if !ok { + errorCode = []string{} + } + + maxDelay, ok := condition["maxDelay"].(int) + if !ok { + maxDelay = MAX_DELAY_TIME + } + + return &RetryCondition{ + MaxAttempts: maxAttempts, + Backoff: backoff, + Exception: exception, + ErrorCode: errorCode, + MaxDelay: maxDelay, + } +} + +// RetryOptions holds the retry options +type RetryOptions struct { + Retryable bool + RetryCondition []*RetryCondition + NoRetryCondition []*RetryCondition +} + +func NewRetryOptions(options map[string]interface{}) *RetryOptions { + retryConditions := make([]*RetryCondition, 0) + for _, cond := range options["retryCondition"].([]interface{}) { + condition := NewRetryCondition(cond.(map[string]interface{})) + retryConditions = append(retryConditions, condition) + } + + noRetryConditions := make([]*RetryCondition, 0) + for _, cond := range options["noRetryCondition"].([]interface{}) { + condition := NewRetryCondition(cond.(map[string]interface{})) + noRetryConditions = append(noRetryConditions, condition) + } + + return &RetryOptions{ + Retryable: options["retryable"].(bool), + RetryCondition: retryConditions, + NoRetryCondition: noRetryConditions, + } +} + +// shouldRetry determines if a retry should be attempted +func ShouldRetry(options *RetryOptions, ctx *RetryPolicyContext) bool { + if ctx.RetriesAttempted == 0 { + return true + } + + if options == nil || !options.Retryable { + return false + } + + retriesAttempted := ctx.RetriesAttempted + ex := ctx.Exception + if baseErr, ok := ex.(BaseError); ok { + conditions := options.NoRetryCondition + + for _, condition := range conditions { + for _, exc := range condition.Exception { + if exc == StringValue(baseErr.ErrorName()) { + return false + } + } + for _, code := range condition.ErrorCode { + if code == StringValue(baseErr.ErrorCode()) { + return false + } + } + } + + conditions = options.RetryCondition + for _, condition := range conditions { + for _, exc := range condition.Exception { + if exc == StringValue(baseErr.ErrorName()) { + if retriesAttempted >= condition.MaxAttempts { + return false + } + return true + } + } + for _, code := range condition.ErrorCode { + if code == StringValue(baseErr.ErrorCode()) { + if retriesAttempted >= condition.MaxAttempts { + return false + } + return true + } + } + } + } + + return false +} + +// getBackoffDelay calculates backoff delay +func GetBackoffDelay(options *RetryOptions, ctx *RetryPolicyContext) int { + if ctx.RetriesAttempted == 0 { + return 0 + } + + if options == nil || !options.Retryable { + return MIN_DELAY_TIME + } + + ex := ctx.Exception + conditions := options.RetryCondition + if baseErr, ok := ex.(BaseError); ok { + for _, condition := range conditions { + for _, exc := range condition.Exception { + if exc == StringValue(baseErr.ErrorName()) { + maxDelay := condition.MaxDelay + // Simulated "retryAfter" from an error response + if respErr, ok := ex.(ResponseError); ok { + retryAfter := IntValue(respErr.ErrorRetryAfter()) + if retryAfter != 0 { + return min(retryAfter, maxDelay) + } + } + // This would be set properly based on your error handling + + if condition.Backoff == nil { + return MIN_DELAY_TIME + } + return min(condition.Backoff.GetDelayTime(ctx), maxDelay) + } + } + + for _, code := range condition.ErrorCode { + if code == StringValue(baseErr.ErrorCode()) { + maxDelay := condition.MaxDelay + // Simulated "retryAfter" from an error response + if respErr, ok := ex.(ResponseError); ok { + retryAfter := IntValue(respErr.ErrorRetryAfter()) + if retryAfter != 0 { + return min(retryAfter, maxDelay) + } + } + + if condition.Backoff == nil { + return MIN_DELAY_TIME + } + + return min(condition.Backoff.GetDelayTime(ctx), maxDelay) + } + } + } + } + return MIN_DELAY_TIME +} + +// helper function to find the minimum of two values +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/dara/retry_test.go b/dara/retry_test.go new file mode 100644 index 0000000..e10a367 --- /dev/null +++ b/dara/retry_test.go @@ -0,0 +1,619 @@ +package dara + +import ( + // "fmt" + // "math" + "math/rand" + "testing" +) + +type AErr struct { + BaseError + Code *string + Name *string + Message *string +} + +func (err *AErr) New(obj map[string]interface{}) *AErr { + + err.Name = String("AErr") + + if val, ok := obj["code"].(string); ok { + err.Code = String(val) + } + + if val, ok := obj["message"].(string); ok { + err.Message = String(val) + } + + return err +} + +func (err *AErr) ErrorCode() *string { + return err.Code +} + +func (err *AErr) ErrorName() *string { + return err.Name +} + +type BErr struct { + BaseError + Code *string + Name *string + Message *string +} + +func (err *BErr) New(obj map[string]interface{}) *BErr { + + err.Name = String("BErr") + + if val, ok := obj["code"].(string); ok { + err.Code = String(val) + } + + if val, ok := obj["message"].(string); ok { + err.Message = String(val) + } + + return err +} + +func (err *BErr) ErrorCode() *string { + return err.Code +} + +func (err *BErr) ErrorName() *string { + return err.Name +} + +type CErr struct { + ResponseError + Code *string + Name *string + Message *string + RetryAfter *int + StatusCode *int +} + +func (err *CErr) New(obj map[string]interface{}) *CErr { + err.Name = String("CErr") + + if val, ok := obj["code"].(string); ok { + err.Code = String(val) + } + + if val, ok := obj["message"].(string); ok { + err.Message = String(val) + } + + if statusCode, ok := obj["StatusCode"].(int); ok { + err.StatusCode = Int(statusCode) + } + + if retryAfter, ok := obj["RetryAfter"].(int); ok { + err.RetryAfter = Int(retryAfter) + } + + return err +} + +func (err *CErr) ErrorCode() *string { + return err.Code +} + +func (err *CErr) ErrorName() *string { + return err.Name +} + +func (err *CErr) ErrorRetryAfter() *int { + return err.RetryAfter +} + +func (err *CErr) ErrorStatusCode() *int { + return err.StatusCode +} + +// BackoffPolicyFactory creates a BackoffPolicy based on the option +func TestBackoffPolicyFactory(t *testing.T) { + tests := []struct { + name string + option map[string]interface{} + expectedError bool + }{ + { + name: "Fixed policy", + option: map[string]interface{}{ + "policy": "Fixed", + }, + expectedError: false, + }, + { + name: "Random policy", + option: map[string]interface{}{ + "policy": "Random", + }, + expectedError: false, + }, + { + name: "Exponential policy", + option: map[string]interface{}{ + "policy": "Exponential", + }, + expectedError: false, + }, + { + name: "EqualJitter policy", + option: map[string]interface{}{ + "policy": "EqualJitter", + }, + expectedError: false, + }, + { + name: "FullJitter policy", + option: map[string]interface{}{ + "policy": "FullJitter", + }, + expectedError: false, + }, + { + name: "Unknown policy", + option: map[string]interface{}{ + "policy": "Unknown", + }, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + backoffPolicy, err := BackoffPolicyFactory(tt.option) + if (err != nil) != tt.expectedError { + t.Errorf("expected error: %v, got: %v", tt.expectedError, err) + } + + if !tt.expectedError && backoffPolicy == nil { + t.Errorf("expected a valid BackoffPolicy, got nil") + } + }) + } +} + +func TestShouldRetry(t *testing.T) { + tests := []struct { + name string + options RetryOptions + ctx RetryPolicyContext + expected bool + }{ + { + name: "Should not retry when options are nil", + options: RetryOptions{}, + ctx: RetryPolicyContext{}, + expected: true, + }, + { + name: "Should not retry when retries exhausted", + options: RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{ + {MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"A1Err"}}, + }, + }, + ctx: RetryPolicyContext{ + RetriesAttempted: 3, + Exception: new(AErr).New(map[string]interface{}{"Code": "A1Err"}), + }, + expected: false, + }, + { + name: "Should retry when conditions match", + options: RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{ + {MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"A1Err"}}, + }, + }, + ctx: RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{"Code": "A1Err"}), + }, + expected: true, + }, + { + name: "Should retry for different exception", + options: RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{ + {MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"A1Err"}}, + }, + }, + ctx: RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(BErr).New(map[string]interface{}{"Code": "B1Err"}), + }, + expected: false, + }, + { + name: "Should not retry with no retry condition", + options: RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{ + {MaxAttempts: 3, Exception: []string{"BErr"}, ErrorCode: []string{"B1Err"}}, + }, + NoRetryCondition: []*RetryCondition{ + {MaxAttempts: 3, Exception: []string{"AErr"}, ErrorCode: []string{"B1Err"}}, + }, + }, + ctx: RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{"Code": "B1Err"}), + }, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := ShouldRetry(&test.options, &test.ctx) + if got != test.expected { + t.Errorf("expected %v, got %v", test.expected, got) + } + }) + } +} + +func TestFixedBackoffPolicy(t *testing.T) { + + condition1 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "Fixed", + "period": 1000, + }, + }) + + options := RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition1}, + } + + context := RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{ + "Code": "A1Err", + }), + } + + // Test Delay Time + expectedDelay := 1000 + if delay := GetBackoffDelay(&options, &context); delay != expectedDelay { + t.Errorf("Expected delay time %d, got %d", expectedDelay, delay) + } +} + +func TestRandomBackoffPolicy(t *testing.T) { + // Test case 1: Random backoff policy with period of 1000 and cap of 10000 + rand.Seed(42) // Set seed for reproducibility + condition1 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "Random", + "period": 1000, + "cap": 10000, + }, + }) + + options := RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition1}, + } + + context := RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{ + "Code": "A1Err", + }), + } + + // Test Delay Time + delay := GetBackoffDelay(&options, &context) + if delay >= 10000 { + t.Errorf("Expected backoff delay to be less than 10000, got %d", delay) + } + + // Test case 2: Random backoff policy with period of 10000 and cap of 10 + condition2 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "Random", + "period": 1000, + "cap": 10, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition2}, + } + + delay2 := GetBackoffDelay(&options, &context) + if delay2 != 10 { + t.Errorf("Expected backoff delay to be 10, got %d", delay2) + } +} + +func TestExponentialBackoffPolicy(t *testing.T) { + // Test case 1 + condition1 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "Exponential", + "period": 5, + "cap": 10000, + }, + }) + + options := RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition1}, + } + + context := RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{ + "Code": "A1Err", + }), + } + + // Test Delay Time + delay := GetBackoffDelay(&options, &context) + if delay != 1024 { + t.Errorf("Expected backoff delay to be 1024, got %d", delay) + } + + // Test case 2 + condition2 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "Exponential", + "period": 10, + "cap": 10000, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition2}, + } + + // Test Delay Time + delay = GetBackoffDelay(&options, &context) + if delay != 10000 { + t.Errorf("Expected backoff delay to be 10000, got %d", delay) + } +} + +func TestEqualJitterBackoff(t *testing.T) { + rand.Seed(0) // Seed random for predictable results + // Test case 1 + condition1 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "EqualJitter", + "period": 5, + "cap": 10000, + }, + }) + + options := RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition1}, + } + + context := RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{ + "Code": "A1Err", + }), + } + + // Test Delay Time + delay := GetBackoffDelay(&options, &context) + if delay <= 512 || delay >= 1024 { + t.Errorf("Expected backoff time in range (512, 1024), got: %d", delay) + } + + // Test case 2 + condition2 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "ExponentialWithEqualJitter", + "period": 10, + "cap": 10000, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition2}, + } + + // Test Delay Time + delay = GetBackoffDelay(&options, &context) + if delay <= 5000 || delay >= 10000 { + t.Errorf("Expected backoff time in range (5000, 10000), got: %d", delay) + } +} + +func TestFullJitterBackoffPolicy(t *testing.T) { + // Test case 1 + condition1 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "fullJitter", + "period": 5, + "cap": 10000, + }, + }) + + options := RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition1}, + } + + context := RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(AErr).New(map[string]interface{}{ + "Code": "A1Err", + }), + } + + // Test Delay Time + delay := GetBackoffDelay(&options, &context) + if delay < 0 || delay >= 1024 { + t.Errorf("Expected backoff time in range [0, 1024), got: %d", delay) + } + + condition2 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "ExponentialWithFullJitter", + "period": 10, + "cap": 10000, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition2}, + } + + // Test Delay Time + delay = GetBackoffDelay(&options, &context) + if delay < 0 || delay >= 10000 { + t.Errorf("Expected backoff time in range [0, 10000), got: %d", delay) + } + + // Test case 3 with maxDelay + condition3 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "MaxDelay": 1000, + "backoff": map[string]interface{}{ + "policy": "ExponentialWithFullJitter", + "period": 10, + "cap": 10000, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition3}, + } + + // Test Delay Time + delay = GetBackoffDelay(&options, &context) + if delay < 0 || delay > 10000 { + t.Errorf("Expected backoff time in range [0, 10000], got: %d", delay) + } + + // Test case 4 + condition4 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"AErr"}, + "errorCode": []string{"A1Err"}, + "backoff": map[string]interface{}{ + "policy": "ExponentialWithFullJitter", + "period": 10, + "cap": 10000 * 10000, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition4}, + } + + // Test Delay Time + delay = GetBackoffDelay(&options, &context) + if delay < 0 || delay > 120*1000 { + t.Errorf("Expected backoff time in range [0, 120000], got: %d", delay) + } + +} + +func TestRetryAfter(t *testing.T) { + condition1 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"CErr"}, + "errorCode": []string{"CErr"}, + "MaxDelay": 5000, + "backoff": map[string]interface{}{ + "policy": "EqualJitter", + "period": 10, + "cap": 10000, + }, + }) + + options := RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition1}, + } + + context := RetryPolicyContext{ + RetriesAttempted: 2, + Exception: new(CErr).New(map[string]interface{}{ + "Code": "CErr", + "RetryAfter": 3000, + }), + } + + // Test Delay Time + delay := GetBackoffDelay(&options, &context) + if delay != 3000 { + t.Errorf("Expected backoff time must be 3000, got: %d", delay) + } + + condition2 := NewRetryCondition(map[string]interface{}{ + "maxAttempts": 3, + "exception": []string{"CErr"}, + "errorCode": []string{"CErr"}, + "maxDelay": 1000, + "backoff": map[string]interface{}{ + "policy": "EqualJitter", + "period": 10, + "cap": 10000, + }, + }) + + options = RetryOptions{ + Retryable: true, + RetryCondition: []*RetryCondition{condition2}, + } + + // Test Delay Time + delay = GetBackoffDelay(&options, &context) + if delay != 1000 { + t.Errorf("Expected backoff time must be 1000, got: %d", delay) + } +} diff --git a/dara/stream.go b/dara/stream.go new file mode 100644 index 0000000..8d7c1e3 --- /dev/null +++ b/dara/stream.go @@ -0,0 +1,110 @@ +package dara + +import ( + "bufio" + "bytes" + "encoding/json" + "io" + "io/ioutil" + "strings" +) + +// 定义 Event 结构体 +type SSEEvent struct { + ID *string + Event *string + Data *string +} + +// 解析单个事件 +func parseEvent(eventLines []string) SSEEvent { + var event SSEEvent + var data string + var id string + + for _, line := range eventLines { + if strings.HasPrefix(line, "data:") { + data += strings.TrimPrefix(line, "data:") + "\n" + } else if strings.HasPrefix(line, "id:") { + id += strings.TrimPrefix(line, "data:") + "\n" + } + } + + event.Data = String(data) + event.ID = String(id) + return event +} + +func ReadAsBytes(body io.Reader) ([]byte, error) { + byt, err := ioutil.ReadAll(body) + if err != nil { + return nil, err + } + r, ok := body.(io.ReadCloser) + if ok { + r.Close() + } + return byt, nil +} + +func ReadAsJSON(body io.Reader) (result interface{}, err error) { + byt, err := ioutil.ReadAll(body) + if err != nil { + return + } + if string(byt) == "" { + return + } + r, ok := body.(io.ReadCloser) + if ok { + r.Close() + } + d := json.NewDecoder(bytes.NewReader(byt)) + d.UseNumber() + err = d.Decode(&result) + return +} + +func ReadAsString(body io.Reader) (string, error) { + byt, err := ioutil.ReadAll(body) + if err != nil { + return "", err + } + r, ok := body.(io.ReadCloser) + if ok { + r.Close() + } + return string(byt), nil +} + +func ReadAsSSE(body io.ReadCloser) (<-chan SSEEvent, <-chan error) { + eventChannel := make(chan SSEEvent) + + // 启动 Goroutine 解析 SSE 数据 + go func() { + defer body.Close() + defer close(eventChannel) + var eventLines []string + + reader := bufio.NewReader(body) + + for { + line, err := reader.ReadString('\n') + if err != nil { + return + } + + line = strings.TrimRight(line, "\n") + if line == "" { + if len(eventLines) > 0 { + event := parseEvent(eventLines) + eventChannel <- event + eventLines = []string{} + } + continue + } + eventLines = append(eventLines, line) + } + }() + return eventChannel, nil +} diff --git a/dara/stream_test.go b/dara/stream_test.go new file mode 100644 index 0000000..0774b01 --- /dev/null +++ b/dara/stream_test.go @@ -0,0 +1,47 @@ +package dara + +import ( + "io/ioutil" + "strings" + "testing" + + "github.com/alibabacloud-go/tea/utils" +) + +func Test_ReadAsBytes(t *testing.T) { + byt, err := ReadAsBytes(strings.NewReader("common")) + utils.AssertNil(t, err) + utils.AssertEqual(t, "common", string(byt)) + + byt, err = ReadAsBytes(ioutil.NopCloser(strings.NewReader("common"))) + utils.AssertNil(t, err) + utils.AssertEqual(t, "common", string(byt)) +} + +func Test_ReadAsJSON(t *testing.T) { + result, err := ReadAsJSON(strings.NewReader(`{"cleint":"test"}`)) + if res, ok := result.(map[string]interface{}); ok { + utils.AssertNil(t, err) + utils.AssertEqual(t, "test", res["cleint"]) + } + + result, err = ReadAsJSON(strings.NewReader("")) + utils.AssertNil(t, err) + utils.AssertNil(t, result) + + result, err = ReadAsJSON(ioutil.NopCloser(strings.NewReader(`{"cleint":"test"}`))) + if res, ok := result.(map[string]interface{}); ok { + utils.AssertNil(t, err) + utils.AssertEqual(t, "test", res["cleint"]) + } +} + +func Test_ReadAsString(t *testing.T) { + str, err := ReadAsString(strings.NewReader("common")) + utils.AssertNil(t, err) + utils.AssertEqual(t, "common", str) + + str, err = ReadAsString(ioutil.NopCloser(strings.NewReader("common"))) + utils.AssertNil(t, err) + utils.AssertEqual(t, "common", str) +} diff --git a/dara/trans.go b/dara/trans.go new file mode 100644 index 0000000..a43a451 --- /dev/null +++ b/dara/trans.go @@ -0,0 +1,491 @@ +package dara + +func String(a string) *string { + return &a +} + +func StringValue(a *string) string { + if a == nil { + return "" + } + return *a +} + +func Int(a int) *int { + return &a +} + +func IntValue(a *int) int { + if a == nil { + return 0 + } + return *a +} + +func Int8(a int8) *int8 { + return &a +} + +func Int8Value(a *int8) int8 { + if a == nil { + return 0 + } + return *a +} + +func Int16(a int16) *int16 { + return &a +} + +func Int16Value(a *int16) int16 { + if a == nil { + return 0 + } + return *a +} + +func Int32(a int32) *int32 { + return &a +} + +func Int32Value(a *int32) int32 { + if a == nil { + return 0 + } + return *a +} + +func Int64(a int64) *int64 { + return &a +} + +func Int64Value(a *int64) int64 { + if a == nil { + return 0 + } + return *a +} + +func Bool(a bool) *bool { + return &a +} + +func BoolValue(a *bool) bool { + if a == nil { + return false + } + return *a +} + +func Uint(a uint) *uint { + return &a +} + +func UintValue(a *uint) uint { + if a == nil { + return 0 + } + return *a +} + +func Uint8(a uint8) *uint8 { + return &a +} + +func Uint8Value(a *uint8) uint8 { + if a == nil { + return 0 + } + return *a +} + +func Uint16(a uint16) *uint16 { + return &a +} + +func Uint16Value(a *uint16) uint16 { + if a == nil { + return 0 + } + return *a +} + +func Uint32(a uint32) *uint32 { + return &a +} + +func Uint32Value(a *uint32) uint32 { + if a == nil { + return 0 + } + return *a +} + +func Uint64(a uint64) *uint64 { + return &a +} + +func Uint64Value(a *uint64) uint64 { + if a == nil { + return 0 + } + return *a +} + +func Float32(a float32) *float32 { + return &a +} + +func Float32Value(a *float32) float32 { + if a == nil { + return 0 + } + return *a +} + +func Float64(a float64) *float64 { + return &a +} + +func Float64Value(a *float64) float64 { + if a == nil { + return 0 + } + return *a +} + +func IntSlice(a []int) []*int { + if a == nil { + return nil + } + res := make([]*int, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func IntValueSlice(a []*int) []int { + if a == nil { + return nil + } + res := make([]int, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Int8Slice(a []int8) []*int8 { + if a == nil { + return nil + } + res := make([]*int8, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Int8ValueSlice(a []*int8) []int8 { + if a == nil { + return nil + } + res := make([]int8, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Int16Slice(a []int16) []*int16 { + if a == nil { + return nil + } + res := make([]*int16, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Int16ValueSlice(a []*int16) []int16 { + if a == nil { + return nil + } + res := make([]int16, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Int32Slice(a []int32) []*int32 { + if a == nil { + return nil + } + res := make([]*int32, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Int32ValueSlice(a []*int32) []int32 { + if a == nil { + return nil + } + res := make([]int32, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Int64Slice(a []int64) []*int64 { + if a == nil { + return nil + } + res := make([]*int64, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Int64ValueSlice(a []*int64) []int64 { + if a == nil { + return nil + } + res := make([]int64, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func UintSlice(a []uint) []*uint { + if a == nil { + return nil + } + res := make([]*uint, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func UintValueSlice(a []*uint) []uint { + if a == nil { + return nil + } + res := make([]uint, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Uint8Slice(a []uint8) []*uint8 { + if a == nil { + return nil + } + res := make([]*uint8, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Uint8ValueSlice(a []*uint8) []uint8 { + if a == nil { + return nil + } + res := make([]uint8, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Uint16Slice(a []uint16) []*uint16 { + if a == nil { + return nil + } + res := make([]*uint16, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Uint16ValueSlice(a []*uint16) []uint16 { + if a == nil { + return nil + } + res := make([]uint16, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Uint32Slice(a []uint32) []*uint32 { + if a == nil { + return nil + } + res := make([]*uint32, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Uint32ValueSlice(a []*uint32) []uint32 { + if a == nil { + return nil + } + res := make([]uint32, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Uint64Slice(a []uint64) []*uint64 { + if a == nil { + return nil + } + res := make([]*uint64, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Uint64ValueSlice(a []*uint64) []uint64 { + if a == nil { + return nil + } + res := make([]uint64, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Float32Slice(a []float32) []*float32 { + if a == nil { + return nil + } + res := make([]*float32, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Float32ValueSlice(a []*float32) []float32 { + if a == nil { + return nil + } + res := make([]float32, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func Float64Slice(a []float64) []*float64 { + if a == nil { + return nil + } + res := make([]*float64, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func Float64ValueSlice(a []*float64) []float64 { + if a == nil { + return nil + } + res := make([]float64, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func StringSlice(a []string) []*string { + if a == nil { + return nil + } + res := make([]*string, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func StringSliceValue(a []*string) []string { + if a == nil { + return nil + } + res := make([]string, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} + +func BoolSlice(a []bool) []*bool { + if a == nil { + return nil + } + res := make([]*bool, len(a)) + for i := 0; i < len(a); i++ { + res[i] = &a[i] + } + return res +} + +func BoolSliceValue(a []*bool) []bool { + if a == nil { + return nil + } + res := make([]bool, len(a)) + for i := 0; i < len(a); i++ { + if a[i] != nil { + res[i] = *a[i] + } + } + return res +} diff --git a/dara/trans_test.go b/dara/trans_test.go new file mode 100644 index 0000000..513f35f --- /dev/null +++ b/dara/trans_test.go @@ -0,0 +1,163 @@ +package dara + +import ( + "testing" + + "github.com/alibabacloud-go/tea/utils" +) + +func Test_Trans(t *testing.T) { + str := String("tea") + strVal := StringValue(str) + utils.AssertEqual(t, "tea", strVal) + utils.AssertEqual(t, "", StringValue(nil)) + + strSlice := StringSlice([]string{"tea"}) + strSliceVal := StringSliceValue(strSlice) + utils.AssertEqual(t, []string{"tea"}, strSliceVal) + utils.AssertNil(t, StringSlice(nil)) + utils.AssertNil(t, StringSliceValue(nil)) + + b := Bool(true) + bVal := BoolValue(b) + utils.AssertEqual(t, true, bVal) + utils.AssertEqual(t, false, BoolValue(nil)) + + bSlice := BoolSlice([]bool{false}) + bSliceVal := BoolSliceValue(bSlice) + utils.AssertEqual(t, []bool{false}, bSliceVal) + utils.AssertNil(t, BoolSlice(nil)) + utils.AssertNil(t, BoolSliceValue(nil)) + + f64 := Float64(2.00) + f64Val := Float64Value(f64) + utils.AssertEqual(t, float64(2.00), f64Val) + utils.AssertEqual(t, float64(0), Float64Value(nil)) + + f32 := Float32(2.00) + f32Val := Float32Value(f32) + utils.AssertEqual(t, float32(2.00), f32Val) + utils.AssertEqual(t, float32(0), Float32Value(nil)) + + f64Slice := Float64Slice([]float64{2.00}) + f64SliceVal := Float64ValueSlice(f64Slice) + utils.AssertEqual(t, []float64{2.00}, f64SliceVal) + utils.AssertNil(t, Float64Slice(nil)) + utils.AssertNil(t, Float64ValueSlice(nil)) + + f32Slice := Float32Slice([]float32{2.00}) + f32SliceVal := Float32ValueSlice(f32Slice) + utils.AssertEqual(t, []float32{2.00}, f32SliceVal) + utils.AssertNil(t, Float32Slice(nil)) + utils.AssertNil(t, Float32ValueSlice(nil)) + + i := Int(1) + iVal := IntValue(i) + utils.AssertEqual(t, 1, iVal) + utils.AssertEqual(t, 0, IntValue(nil)) + + i8 := Int8(int8(1)) + i8Val := Int8Value(i8) + utils.AssertEqual(t, int8(1), i8Val) + utils.AssertEqual(t, int8(0), Int8Value(nil)) + + i16 := Int16(int16(1)) + i16Val := Int16Value(i16) + utils.AssertEqual(t, int16(1), i16Val) + utils.AssertEqual(t, int16(0), Int16Value(nil)) + + i32 := Int32(int32(1)) + i32Val := Int32Value(i32) + utils.AssertEqual(t, int32(1), i32Val) + utils.AssertEqual(t, int32(0), Int32Value(nil)) + + i64 := Int64(int64(1)) + i64Val := Int64Value(i64) + utils.AssertEqual(t, int64(1), i64Val) + utils.AssertEqual(t, int64(0), Int64Value(nil)) + + iSlice := IntSlice([]int{1}) + iSliceVal := IntValueSlice(iSlice) + utils.AssertEqual(t, []int{1}, iSliceVal) + utils.AssertNil(t, IntSlice(nil)) + utils.AssertNil(t, IntValueSlice(nil)) + + i8Slice := Int8Slice([]int8{1}) + i8ValSlice := Int8ValueSlice(i8Slice) + utils.AssertEqual(t, []int8{1}, i8ValSlice) + utils.AssertNil(t, Int8Slice(nil)) + utils.AssertNil(t, Int8ValueSlice(nil)) + + i16Slice := Int16Slice([]int16{1}) + i16ValSlice := Int16ValueSlice(i16Slice) + utils.AssertEqual(t, []int16{1}, i16ValSlice) + utils.AssertNil(t, Int16Slice(nil)) + utils.AssertNil(t, Int16ValueSlice(nil)) + + i32Slice := Int32Slice([]int32{1}) + i32ValSlice := Int32ValueSlice(i32Slice) + utils.AssertEqual(t, []int32{1}, i32ValSlice) + utils.AssertNil(t, Int32Slice(nil)) + utils.AssertNil(t, Int32ValueSlice(nil)) + + i64Slice := Int64Slice([]int64{1}) + i64ValSlice := Int64ValueSlice(i64Slice) + utils.AssertEqual(t, []int64{1}, i64ValSlice) + utils.AssertNil(t, Int64Slice(nil)) + utils.AssertNil(t, Int64ValueSlice(nil)) + + ui := Uint(1) + uiVal := UintValue(ui) + utils.AssertEqual(t, uint(1), uiVal) + utils.AssertEqual(t, uint(0), UintValue(nil)) + + ui8 := Uint8(uint8(1)) + ui8Val := Uint8Value(ui8) + utils.AssertEqual(t, uint8(1), ui8Val) + utils.AssertEqual(t, uint8(0), Uint8Value(nil)) + + ui16 := Uint16(uint16(1)) + ui16Val := Uint16Value(ui16) + utils.AssertEqual(t, uint16(1), ui16Val) + utils.AssertEqual(t, uint16(0), Uint16Value(nil)) + + ui32 := Uint32(uint32(1)) + ui32Val := Uint32Value(ui32) + utils.AssertEqual(t, uint32(1), ui32Val) + utils.AssertEqual(t, uint32(0), Uint32Value(nil)) + + ui64 := Uint64(uint64(1)) + ui64Val := Uint64Value(ui64) + utils.AssertEqual(t, uint64(1), ui64Val) + utils.AssertEqual(t, uint64(0), Uint64Value(nil)) + + uiSlice := UintSlice([]uint{1}) + uiValSlice := UintValueSlice(uiSlice) + utils.AssertEqual(t, []uint{1}, uiValSlice) + utils.AssertNil(t, UintSlice(nil)) + utils.AssertNil(t, UintValueSlice(nil)) + + ui8Slice := Uint8Slice([]uint8{1}) + ui8ValSlice := Uint8ValueSlice(ui8Slice) + utils.AssertEqual(t, []uint8{1}, ui8ValSlice) + utils.AssertNil(t, Uint8Slice(nil)) + utils.AssertNil(t, Uint8ValueSlice(nil)) + + ui16Slice := Uint16Slice([]uint16{1}) + ui16ValSlice := Uint16ValueSlice(ui16Slice) + utils.AssertEqual(t, []uint16{1}, ui16ValSlice) + utils.AssertNil(t, Uint16Slice(nil)) + utils.AssertNil(t, Uint16ValueSlice(nil)) + + ui32Slice := Uint32Slice([]uint32{1}) + ui32ValSlice := Uint32ValueSlice(ui32Slice) + utils.AssertEqual(t, []uint32{1}, ui32ValSlice) + utils.AssertNil(t, Uint32Slice(nil)) + utils.AssertNil(t, Uint32ValueSlice(nil)) + + ui64Slice := Uint64Slice([]uint64{1}) + ui64ValSlice := Uint64ValueSlice(ui64Slice) + utils.AssertEqual(t, []uint64{1}, ui64ValSlice) + utils.AssertNil(t, Uint64Slice(nil)) + utils.AssertNil(t, Uint64ValueSlice(nil)) +} diff --git a/dara/url.go b/dara/url.go new file mode 100644 index 0000000..263a403 --- /dev/null +++ b/dara/url.go @@ -0,0 +1,149 @@ +package dara + +import ( + "fmt" + "net/url" + "strings" +) + +// PortMap maps protocols to their corresponding ports. +var portMap = map[string]string{ + "ftp": "21", + "gopher": "70", + "http": "80", + "https": "443", + "ws": "80", + "wss": "443", +} + +// URL is a wrapper around the URL type. +type URL struct { + _url *url.URL +} + +// NewURL constructs a new URL from a string. +func NewURL(str string) (*URL, error) { + parsedURL, err := url.Parse(str) + if err != nil { + return nil, err + } + return &URL{_url: parsedURL}, nil +} + +// Path returns the path and query of the URL. +func (t *URL) Path() string { + if t._url.RawQuery == "" { + return t._url.Path + } + return t._url.Path + "?" + t._url.RawQuery +} + +// Pathname returns the pathname of the URL. +func (t *URL) Pathname() string { + return t._url.Path +} + +// Protocol returns the protocol of the URL. +func (t *URL) Protocol() string { + return strings.TrimSuffix(t._url.Scheme, ":") +} + +// Hostname returns the hostname of the URL. +func (t *URL) Hostname() string { + return t._url.Hostname() +} + +// Host returns the host (host:port) of the URL. +func (t *URL) Host() string { + return t._url.Host +} + +// Port returns the port of the URL, or the default for the protocol if not specified. +func (t *URL) Port() string { + if p := t._url.Port(); p != "" { + return p + } + return portMap[t.Protocol()] +} + +// Hash returns the hash of the URL without the #. +func (t *URL) Hash() string { + return strings.TrimPrefix(t._url.Fragment, "#") +} + +// Search returns the search part of the URL without the ?. +func (t *URL) Search() string { + return strings.TrimPrefix(t._url.RawQuery, "?") +} + +// Href returns the full URL. +func (t *URL) Href() string { + return t._url.String() +} + +// Auth returns the username and password of the URL. +func (t *URL) Auth() string { + password := getPassword(t._url.User) + username := t._url.User.Username() + if username == "" && password == "" { + return "" + } + return fmt.Sprintf("%s:%s", username, password) +} + +// getPassword retrieves the password from a URL.User. +func getPassword(user *url.Userinfo) string { + if password, ok := user.Password(); ok { + return password + } + return "" +} + +// Parse constructs a new URL from a string. +func ParseURL(urlStr string) (*URL, error) { + return NewURL(urlStr) +} + +// EncodeURL encodes a URL string. +func EncodeURL(urlStr string) string { + if urlStr == "" { + return "" + } + strs := strings.Split(urlStr, "/") + for i, v := range strs { + strs[i] = url.QueryEscape(v) + } + urlStr = strings.Join(strs, "/") + urlStr = strings.Replace(urlStr, "+", "%20", -1) + urlStr = strings.Replace(urlStr, "*", "%2A", -1) + urlStr = strings.Replace(urlStr, "%7E", "~", -1) + return urlStr +} + +// PercentEncode encodes a string for use in URLs, replacing certain characters. +func PercentEncode(uri string) string { + if uri == "" { + return "" + } + uri = url.QueryEscape(uri) + uri = strings.Replace(uri, "+", "%20", -1) + uri = strings.Replace(uri, "*", "%2A", -1) + uri = strings.Replace(uri, "%7E", "~", -1) + return uri +} + +// PathEncode encodes each segment of a path. +func PathEncode(path string) string { + if path == "" || path == "/" { + return path + } + strs := strings.Split(path, "/") + for i, v := range strs { + strs[i] = url.QueryEscape(v) + } + path = strings.Join(strs, "/") + path = strings.Replace(path, "+", "%20", -1) + path = strings.Replace(path, "*", "%2A", -1) + path = strings.Replace(path, "%7E", "~", -1) + return path +} diff --git a/dara/url_test.go b/dara/url_test.go new file mode 100644 index 0000000..816009f --- /dev/null +++ b/dara/url_test.go @@ -0,0 +1,255 @@ +package dara + +import ( + "testing" +) + +func TestNewURL(t *testing.T) { + tests := []struct { + urlString string + wantErr bool + }{ + {"http://example.com", false}, + {"ftp://user:pass@host:21/path", false}, + {"://example.com", true}, // Invalid URL + } + + for _, tt := range tests { + _, err := NewURL(tt.urlString) + if (err != nil) != tt.wantErr { + t.Errorf("NewURL(%q) error = %v, wantErr %v", tt.urlString, err, tt.wantErr) + } + } +} + +func TestDaraURL_Path(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com/path?query=1", "/path?query=1"}, + {"https://example.com/", "/"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Path(); got != tt.want { + t.Errorf("DaraURL.Path() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Pathname(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com/path?query=1", "/path"}, + {"https://example.com/another/path/", "/another/path/"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Pathname(); got != tt.want { + t.Errorf("DaraURL.Pathname() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Protocol(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com", "http"}, + {"ftp://example.com", "ftp"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Protocol(); got != tt.want { + t.Errorf("DaraURL.Protocol() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Hostname(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com", "example.com"}, + {"https://user@subdomain.example.com:443", "subdomain.example.com"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Hostname(); got != tt.want { + t.Errorf("DaraURL.Hostname() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Host(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com", "example.com"}, + {"http://example.com:8080", "example.com:8080"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Host(); got != tt.want { + t.Errorf("DaraURL.Host() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Port(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com", "80"}, + {"https://example.com", "443"}, + {"ftp://example.com:21", "21"}, + {"gopher://example.com", "70"}, + {"ws://example.com", "80"}, + {"wss://example.com", "443"}, + {"http://example.com:8080", "8080"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Port(); got != tt.want { + t.Errorf("DaraURL.Port() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Hash(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com#section", "section"}, + {"http://example.com", ""}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Hash(); got != tt.want { + t.Errorf("DaraURL.Hash() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Search(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com?query=1", "query=1"}, + {"http://example.com", ""}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Search(); got != tt.want { + t.Errorf("DaraURL.Search() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Href(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://example.com", "http://example.com"}, + {"https://user:pass@host:443/path?query=1#section", "https://user:pass@host:443/path?query=1#section"}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Href(); got != tt.want { + t.Errorf("DaraURL.Href() = %v, want %v", got, tt.want) + } + } +} + +func TestDaraURL_Auth(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"http://user:pass@example.com", "user:pass"}, + {"http://user@example.com", "user:"}, + {"http://example.com", ""}, + } + + for _, tt := range tests { + tu, _ := NewURL(tt.urlString) + if got := tu.Auth(); got != tt.want { + t.Errorf("DaraURL.Auth() = %v, want %v", got, tt.want) + } + } +} + +func TestEncodeURL(t *testing.T) { + tests := []struct { + urlString string + want string + }{ + {"hello world", "hello%20world"}, + {"test*abcd++", "test%2Aabcd%2B%2B"}, + {"", ""}, + } + + for _, tt := range tests { + got := EncodeURL(tt.urlString) + if got != tt.want { + t.Errorf("EncodeURL(%q) = %v, want %v", tt.urlString, got, tt.want) + } + } +} + +func TestPercentEncode(t *testing.T) { + tests := []struct { + raw string + want string + }{ + {"hello world", "hello%20world"}, + {"hello*world", "hello%2Aworld"}, + {"hello~world", "hello~world"}, + {"", ""}, + } + + for _, tt := range tests { + got := PercentEncode(tt.raw) + if got != tt.want { + t.Errorf("PercentEncode(%q) = %v, want %v", tt.raw, got, tt.want) + } + } +} + +func TestPathEncode(t *testing.T) { + tests := []struct { + path string + want string + }{ + {"hello/world", "hello/world"}, + {"hello world", "hello%20world"}, + {"", ""}, + {"/", "/"}, + } + + for _, tt := range tests { + got := PathEncode(tt.path) + if got != tt.want { + t.Errorf("PathEncode(%q) = %v, want %v", tt.path, got, tt.want) + } + } +} diff --git a/dara/xml.go b/dara/xml.go new file mode 100644 index 0000000..8430478 --- /dev/null +++ b/dara/xml.go @@ -0,0 +1,104 @@ +package dara + +import ( + "bytes" + "encoding/xml" + "fmt" + "reflect" + "strings" + + v2 "github.com/clbanning/mxj/v2" +) + +func ToXML(obj map[string]interface{}) string { + return mapToXML(obj) +} + +func ParseXml(val string, result interface{}) map[string]interface{} { + resp := make(map[string]interface{}) + + start := getStartElement([]byte(val)) + if result == nil { + vm, err := v2.NewMapXml([]byte(val)) + if err != nil { + return nil + } + return vm + } + out, err := xmlUnmarshal([]byte(val), result) + if err != nil { + return resp + } + resp[start] = out + return resp +} + +func mapToXML(val map[string]interface{}) string { + res := "" + for key, value := range val { + switch value.(type) { + case []interface{}: + for _, v := range value.([]interface{}) { + switch v.(type) { + case map[string]interface{}: + res += `<` + key + `>` + res += mapToXML(v.(map[string]interface{})) + res += `` + default: + if fmt.Sprintf("%v", v) != `` { + res += `<` + key + `>` + res += fmt.Sprintf("%v", v) + res += `` + } + } + } + case map[string]interface{}: + res += `<` + key + `>` + res += mapToXML(value.(map[string]interface{})) + res += `` + default: + if fmt.Sprintf("%v", value) != `` { + res += `<` + key + `>` + res += fmt.Sprintf("%v", value) + res += `` + } + } + } + return res +} + +func getStartElement(body []byte) string { + d := xml.NewDecoder(bytes.NewReader(body)) + for { + tok, err := d.Token() + if err != nil { + return "" + } + if t, ok := tok.(xml.StartElement); ok { + return t.Name.Local + } + } +} + +func xmlUnmarshal(body []byte, result interface{}) (interface{}, error) { + start := getStartElement(body) + dataValue := reflect.ValueOf(result).Elem() + dataType := dataValue.Type() + for i := 0; i < dataType.NumField(); i++ { + field := dataType.Field(i) + name, containsNameTag := field.Tag.Lookup("xml") + name = strings.Replace(name, ",omitempty", "", -1) + if containsNameTag { + if name == start { + realType := dataValue.Field(i).Type() + realValue := reflect.New(realType).Interface() + err := xml.Unmarshal(body, realValue) + if err != nil { + return nil, err + } + return realValue, nil + } + } + } + return nil, nil +} diff --git a/dara/xml_test.go b/dara/xml_test.go new file mode 100644 index 0000000..c83a92a --- /dev/null +++ b/dara/xml_test.go @@ -0,0 +1,124 @@ +package dara + +import ( + "encoding/json" + "testing" + + "github.com/alibabacloud-go/tea/utils" +) + +type validatorTest struct { + Num *int `json:"num" require:"true"` + Str *string `json:"str" pattern:"^[a-d]*$" maxLength:"4"` + Test *errLength `json:"test"` + List []*string `json:"list" pattern:"^[a-d]*$" maxLength:"4"` +} + +type GetBucketLocationResponse struct { + RequestId *string `json:"x-oss-request-id" xml:"x-oss-request-id" require:"true"` + LocationConstraint *string `json:"LocationConstraint" xml:"LocationConstraint" require:"true"` +} + +type errLength struct { + Num *int `json:"num" maxLength:"a"` +} + +func Test_ParseXml(t *testing.T) { + str := ` + 10` + result := ParseXml(str, new(validatorTest)) + utils.AssertEqual(t, 1, len(result)) + str = ` + ` + result = ParseXml(str, new(validatorTest)) + utils.AssertEqual(t, 1, len(result)) + xmlVal := ` + + + zhangSan + 23 + male + +` + res := ParseXml(xmlVal, nil) + utils.AssertEqual(t, 1, len(res)) +} + +func Test_ToXML(t *testing.T) { + val := map[string]interface{}{ + "oss": map[string]interface{}{ + "key": "value", + }, + } + str := ToXML(val) + utils.AssertEqual(t, "value", str) +} + +func Test_getStartElement(t *testing.T) { + xmlVal := ` + + + zhangSan + 23 + male + +` + str := getStartElement([]byte(xmlVal)) + utils.AssertEqual(t, "students", str) + + xmlVal = ` + + + zhangSan + 23 + male + +` + str = getStartElement([]byte(xmlVal)) + utils.AssertEqual(t, "", str) +} + +func Test_mapToXML(t *testing.T) { + obj := map[string]interface{}{ + "struct": map[string]interface{}{ + "param1": "value1", + "list": []string{"value2", "value3"}, + "listMap": []map[string]interface{}{ + map[string]interface{}{ + "param2": "value2", + }, + map[string]interface{}{ + "param3": "value3", + }, + }, + "listMapString": []map[string]string{ + map[string]string{ + "param4": "value4", + }, + map[string]string{ + "param5": "value5", + }, + }, + "mapString": map[string]string{ + "param6": "value6", + }, + "listInterface": []interface{}{"10", 20}, + }, + } + byt, _ := json.Marshal(obj) + obj1 := make(map[string]interface{}) + json.Unmarshal(byt, &obj1) + xml := mapToXML(obj1) + utils.AssertContains(t, xml, `10`) +} + +func Test_XmlUnmarshal(t *testing.T) { + result := new(GetBucketLocationResponse) + xmlVal := ` +oss-cn-hangzhou` + out, err := xmlUnmarshal([]byte(xmlVal), result) + utils.AssertNil(t, err) + + byt, _ := json.Marshal(out) + utils.AssertEqual(t, `"oss-cn-hangzhou"`, string(byt)) +} diff --git a/go.mod b/go.mod index 8bb4a2c..5d9ab13 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.14 require ( github.com/alibabacloud-go/debug v1.0.0 + github.com/clbanning/mxj/v2 v2.7.0 + github.com/google/go-cmp v0.6.0 // indirect github.com/json-iterator/go v1.1.12 github.com/modern-go/reflect2 v1.0.2 golang.org/x/net v0.26.0 diff --git a/go.sum b/go.sum index 68689dd..ddfcafc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= 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= diff --git a/tea/json_parser.go b/tea/json_parser.go index b3f2022..a61df8d 100644 --- a/tea/json_parser.go +++ b/tea/json_parser.go @@ -13,12 +13,12 @@ import ( "github.com/modern-go/reflect2" ) +var jsonParser jsoniter.API + const maxUint = ^uint(0) const maxInt = int(maxUint >> 1) const minInt = -maxInt - 1 -var jsonParser jsoniter.API - func init() { jsonParser = jsoniter.Config{ EscapeHTML: true, diff --git a/tea/tea.go b/tea/tea.go index 3fc9b08..ae7ea9b 100644 --- a/tea/tea.go +++ b/tea/tea.go @@ -1165,6 +1165,11 @@ func ToInt(a *int32) *int { return Int(int(Int32Value(a))) } +func ForceInt(a interface{}) int { + num, _ := a.(int) + return num +} + func ToInt32(a *int) *int32 { return Int32(int32(IntValue(a))) } diff --git a/tea/tea_test.go b/tea/tea_test.go index 272a030..9d6fa3a 100644 --- a/tea/tea_test.go +++ b/tea/tea_test.go @@ -18,20 +18,6 @@ import ( "github.com/alibabacloud-go/tea/utils" ) -type test struct { - Key string `json:"key,omitempty"` - 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, @@ -46,6 +32,54 @@ var runtimeObj = map[string]interface{}{ "logger": utils.NewLogger("info", "", &bytes.Buffer{}, "{time}"), } +var key = `-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN5I1VCLYr2IlTLrFpwUGcnwl8yi6V8Mdw+myxfusNgEWiH/FQ4T +AZsIveiLOz9Gcc8m2mZSxst2qGII00scpiECAwEAAQJBAJZEhnA8yjN28eXKJy68 +J/LsQrKEL1+h/ZsHFqTHJ6XfiA0CXjbjPsa4jEbpyilMTSgUyoKdJ512ioeco2n6 +xUECIQD/JUHaKSuxz55t3efKdppqfopb92mJ2NuPJgrJI70OCwIhAN8HZ0bzr/4a +DLvYCDUKvOj3GzsV1dtBwWuHBaZEafQDAiEAtTnrel//7z5/U55ow4BW0gmrkQM9 +bXIhEZ59zryZzl0CIQDFmBqRCu9eshecCP7kd3n88IjopSTOV4iUypBfyXcRnwIg +eXNxUx+BCu2We36+c0deE2+vizL1s6f5XhE6l4bqtiM= +-----END RSA PRIVATE KEY-----` +var cert = `-----BEGIN CERTIFICATE----- +MIIBvDCCAWYCCQDKjNYQxar0mjANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJh +czEMMAoGA1UECAwDYXNmMQwwCgYDVQQHDANzYWQxCzAJBgNVBAoMAnNkMQ0wCwYD +VQQLDARxd2VyMQswCQYDVQQDDAJzZjERMA8GCSqGSIb3DQEJARYCd2UwHhcNMjAx +MDE5MDI0MDMwWhcNMzAxMDE3MDI0MDMwWjBlMQswCQYDVQQGEwJhczEMMAoGA1UE +CAwDYXNmMQwwCgYDVQQHDANzYWQxCzAJBgNVBAoMAnNkMQ0wCwYDVQQLDARxd2Vy +MQswCQYDVQQDDAJzZjERMA8GCSqGSIb3DQEJARYCd2UwXDANBgkqhkiG9w0BAQEF +AANLADBIAkEA3kjVUItivYiVMusWnBQZyfCXzKLpXwx3D6bLF+6w2ARaIf8VDhMB +mwi96Is7P0ZxzybaZlLGy3aoYgjTSxymIQIDAQABMA0GCSqGSIb3DQEBCwUAA0EA +ZjePopbFugNK0US1MM48V1S2petIsEcxbZBEk/wGqIzrY4RCFKMtbtPSgTDUl3D9 +XePemktG22a54ItVJ5FpcQ== +-----END CERTIFICATE-----` +var ca = `-----BEGIN CERTIFICATE----- +MIIBuDCCAWICCQCLw4OWpjlJCDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJm +ZDEMMAoGA1UECAwDYXNkMQswCQYDVQQHDAJxcjEKMAgGA1UECgwBZjEMMAoGA1UE +CwwDc2RhMQswCQYDVQQDDAJmZDESMBAGCSqGSIb3DQEJARYDYXNkMB4XDTIwMTAx +OTAyNDQwNFoXDTIzMDgwOTAyNDQwNFowYzELMAkGA1UEBhMCZmQxDDAKBgNVBAgM +A2FzZDELMAkGA1UEBwwCcXIxCjAIBgNVBAoMAWYxDDAKBgNVBAsMA3NkYTELMAkG +A1UEAwwCZmQxEjAQBgkqhkiG9w0BCQEWA2FzZDBcMA0GCSqGSIb3DQEBAQUAA0sA +MEgCQQCxXZTl5IO61Lqd0fBBOSy7ER1gsdA0LkvflP5HEaQygjecLGfrAtD/DWu0 +/sxCcBVnQRoP9Yp0ijHJwgXvBnrNAgMBAAEwDQYJKoZIhvcNAQELBQADQQBJF+/4 +DEMilhlFY+o9mqCygFVxuvHtQVhpPS938H2h7/P6pXN65jK2Y5hHefZEELq9ulQe +91iBwaQ4e9racCgP +-----END CERTIFICATE-----` + +type test struct { + Key string `json:"key,omitempty"` + Body []byte `json:"body,omitempty"` +} + +type PrettifyTest struct { + name string + Strs []string + Nums8 []int8 + Unum8 []uint8 + Value string + Mapvalue map[string]string +} + type validateTest struct { Num1 *int `json:"num1,omitempty" require:"true" minimum:"2"` Num2 *int `json:"num2,omitempty" maximum:"6"` @@ -453,40 +487,6 @@ func Test_GetBackoffTime(t *testing.T) { utils.AssertEqual(t, true, IntValue(ms) <= 3) } -var key = `-----BEGIN RSA PRIVATE KEY----- -MIIBPAIBAAJBAN5I1VCLYr2IlTLrFpwUGcnwl8yi6V8Mdw+myxfusNgEWiH/FQ4T -AZsIveiLOz9Gcc8m2mZSxst2qGII00scpiECAwEAAQJBAJZEhnA8yjN28eXKJy68 -J/LsQrKEL1+h/ZsHFqTHJ6XfiA0CXjbjPsa4jEbpyilMTSgUyoKdJ512ioeco2n6 -xUECIQD/JUHaKSuxz55t3efKdppqfopb92mJ2NuPJgrJI70OCwIhAN8HZ0bzr/4a -DLvYCDUKvOj3GzsV1dtBwWuHBaZEafQDAiEAtTnrel//7z5/U55ow4BW0gmrkQM9 -bXIhEZ59zryZzl0CIQDFmBqRCu9eshecCP7kd3n88IjopSTOV4iUypBfyXcRnwIg -eXNxUx+BCu2We36+c0deE2+vizL1s6f5XhE6l4bqtiM= ------END RSA PRIVATE KEY-----` -var cert = `-----BEGIN CERTIFICATE----- -MIIBvDCCAWYCCQDKjNYQxar0mjANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJh -czEMMAoGA1UECAwDYXNmMQwwCgYDVQQHDANzYWQxCzAJBgNVBAoMAnNkMQ0wCwYD -VQQLDARxd2VyMQswCQYDVQQDDAJzZjERMA8GCSqGSIb3DQEJARYCd2UwHhcNMjAx -MDE5MDI0MDMwWhcNMzAxMDE3MDI0MDMwWjBlMQswCQYDVQQGEwJhczEMMAoGA1UE -CAwDYXNmMQwwCgYDVQQHDANzYWQxCzAJBgNVBAoMAnNkMQ0wCwYDVQQLDARxd2Vy -MQswCQYDVQQDDAJzZjERMA8GCSqGSIb3DQEJARYCd2UwXDANBgkqhkiG9w0BAQEF -AANLADBIAkEA3kjVUItivYiVMusWnBQZyfCXzKLpXwx3D6bLF+6w2ARaIf8VDhMB -mwi96Is7P0ZxzybaZlLGy3aoYgjTSxymIQIDAQABMA0GCSqGSIb3DQEBCwUAA0EA -ZjePopbFugNK0US1MM48V1S2petIsEcxbZBEk/wGqIzrY4RCFKMtbtPSgTDUl3D9 -XePemktG22a54ItVJ5FpcQ== ------END CERTIFICATE-----` -var ca = `-----BEGIN CERTIFICATE----- -MIIBuDCCAWICCQCLw4OWpjlJCDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJm -ZDEMMAoGA1UECAwDYXNkMQswCQYDVQQHDAJxcjEKMAgGA1UECgwBZjEMMAoGA1UE -CwwDc2RhMQswCQYDVQQDDAJmZDESMBAGCSqGSIb3DQEJARYDYXNkMB4XDTIwMTAx -OTAyNDQwNFoXDTIzMDgwOTAyNDQwNFowYzELMAkGA1UEBhMCZmQxDDAKBgNVBAgM -A2FzZDELMAkGA1UEBwwCcXIxCjAIBgNVBAoMAWYxDDAKBgNVBAsMA3NkYTELMAkG -A1UEAwwCZmQxEjAQBgkqhkiG9w0BCQEWA2FzZDBcMA0GCSqGSIb3DQEBAQUAA0sA -MEgCQQCxXZTl5IO61Lqd0fBBOSy7ER1gsdA0LkvflP5HEaQygjecLGfrAtD/DWu0 -/sxCcBVnQRoP9Yp0ijHJwgXvBnrNAgMBAAEwDQYJKoZIhvcNAQELBQADQQBJF+/4 -DEMilhlFY+o9mqCygFVxuvHtQVhpPS938H2h7/P6pXN65jK2Y5hHefZEELq9ulQe -91iBwaQ4e9racCgP ------END CERTIFICATE-----` - func Test_DoRequest(t *testing.T) { origTestHookDo := hookDo defer func() { hookDo = origTestHookDo }() diff --git a/utils/assert_test.go b/utils/assert_test.go index b024724..13590d2 100644 --- a/utils/assert_test.go +++ b/utils/assert_test.go @@ -6,9 +6,9 @@ import ( ) func Test_isNil(t *testing.T) { + var req *http.Request isnil := isNil(nil) AssertEqual(t, true, isnil) - var req *http.Request isnil = isNil(req) AssertEqual(t, true, isnil) AssertNil(t, nil) diff --git a/utils/logger.go b/utils/logger.go index 0513668..b155609 100644 --- a/utils/logger.go +++ b/utils/logger.go @@ -7,6 +7,10 @@ import ( "time" ) +var defaultLoggerTemplate = `{time} {channel}: "{method} {uri} HTTP/{version}" {code} {cost} {hostname}` +var loggerParam = []string{"{time}", "{start_time}", "{ts}", "{channel}", "{pid}", "{host}", "{method}", "{uri}", "{version}", "{target}", "{hostname}", "{code}", "{error}", "{req_headers}", "{res_body}", "{res_headers}", "{cost}"} +var logChannel string + type Logger struct { *log.Logger formatTemplate string @@ -14,10 +18,6 @@ type Logger struct { lastLogMsg string } -var defaultLoggerTemplate = `{time} {channel}: "{method} {uri} HTTP/{version}" {code} {cost} {hostname}` -var loggerParam = []string{"{time}", "{start_time}", "{ts}", "{channel}", "{pid}", "{host}", "{method}", "{uri}", "{version}", "{target}", "{hostname}", "{code}", "{error}", "{req_headers}", "{res_body}", "{res_headers}", "{cost}"} -var logChannel string - func InitLogMsg(fieldMap map[string]string) { for _, value := range loggerParam { fieldMap[value] = ""