diff --git a/https/apiError.go b/https/apiError.go index 384e50b..fbea3c6 100644 --- a/https/apiError.go +++ b/https/apiError.go @@ -18,6 +18,11 @@ type ApiError struct { Message string } +func (a ApiError) String() string { + return fmt.Sprintf( + "ApiError[StatusCode=%v, Message=%v]", a.StatusCode, a.Message) +} + func (a ApiError) Error() string { return fmt.Sprintf("ApiError occurred: %v", a.Message) } diff --git a/https/apiError_test.go b/https/apiError_test.go index a570cfe..cc179c3 100644 --- a/https/apiError_test.go +++ b/https/apiError_test.go @@ -43,6 +43,17 @@ func TestErrorMethod(t *testing.T) { } } +func TestStringMethod(t *testing.T) { + expected := "ApiError[StatusCode=500, Message=Server Error]" + result := ApiError{ + StatusCode: 500, + Message: "Server Error", + } + if result.String() != expected { + t.Errorf("Failed:\nExpected: %v\nGot: %v", expected, result.Error()) + } +} + func TestCorrectMessageWhenDynamicErrorMessageWithStatusCode(t *testing.T) { tpl := "Error: Status Code {$statusCode}" diff --git a/https/callBuilder.go b/https/callBuilder.go index 23e9b9c..f13063e 100644 --- a/https/callBuilder.go +++ b/https/callBuilder.go @@ -40,7 +40,7 @@ type baseUrlProvider func(server string) string type CallBuilder interface { AppendPath(path string) AppendTemplateParam(param string) - AppendTemplateParams(params any) + AppendTemplateParams(params ...any) AppendErrors(errors map[string]ErrorBuilder[error]) BaseUrl(arg string) Method(httpMethodName string) @@ -183,8 +183,8 @@ func (cb *defaultCallBuilder) AppendPath(path string) { // AppendTemplateParam appends the provided parameter to the existing path in the CallBuilder as a URL template parameter. func (cb *defaultCallBuilder) AppendTemplateParam(param string) { - if strings.Contains(cb.path, "%v") { - cb.path = fmt.Sprintf(cb.path, "/"+url.QueryEscape(param)) + if index := strings.Index(cb.path, "%v"); index != -1 { + cb.path = cb.path[:index] + url.QueryEscape(param) + cb.path[index+len("%v"):] } else { cb.AppendPath(url.QueryEscape(param)) } @@ -192,18 +192,30 @@ func (cb *defaultCallBuilder) AppendTemplateParam(param string) { // AppendTemplateParams appends the provided parameters to the existing path in the CallBuilder as URL template parameters. // It accepts a slice of strings or a slice of integers as the params argument. -func (cb *defaultCallBuilder) AppendTemplateParams(params any) { - reflectValue := reflect.ValueOf(params) - if reflectValue.Type().Kind() == reflect.Slice { - for i := 0; i < reflectValue.Len(); i++ { - innerParam := reflectValue.Index(i).Interface() - switch x := innerParam.(type) { +func (cb *defaultCallBuilder) AppendTemplateParams(params ...any) { + + for _, param := range params { + paramValue := reflect.ValueOf(param) + if paramValue.Type().Kind() == reflect.Slice { + for i := 0; i < paramValue.Len(); i++ { + innerParam := paramValue.Index(i).Interface() + switch x := innerParam.(type) { + case string: + cb.AppendTemplateParam(x) + case int: + cb.AppendTemplateParam(strconv.Itoa(x)) + default: + cb.AppendTemplateParam(FormatAny(x)) + } + } + } else { + switch x := param.(type) { case string: cb.AppendTemplateParam(x) case int: cb.AppendTemplateParam(strconv.Itoa(x)) default: - cb.AppendTemplateParam(fmt.Sprintf("%v", x)) + cb.AppendTemplateParam(FormatAny(x)) } } } @@ -273,7 +285,18 @@ func (cb *defaultCallBuilder) Header( if cb.headers == nil { cb.headers = make(map[string]string) } - SetHeaders(cb.headers, strings.ToLower(name), fmt.Sprintf("%v", value)) + SetHeaders(cb.headers, strings.ToLower(name), FormatAny(value)) +} + +func FormatAny(value any) string { + if valueBytes, err := json.Marshal(value); err == nil { + return sanitizeString(valueBytes) + } + return "" +} + +func sanitizeString(valueBytes []byte) string { + return strings.Trim(string(valueBytes), "\"") } // CombineHeaders combines the provided headers with the existing headers in the CallBuilder. @@ -454,7 +477,7 @@ func (cb *defaultCallBuilder) validateJson() error { cb.setContentTypeIfNotSet(JSON_CONTENT_TYPE) return nil } - cb.Text(fmt.Sprint(cb.jsonData)) + cb.Text(FormatAny(cb.jsonData)) return nil } diff --git a/https/callBuilder_test.go b/https/callBuilder_test.go index f3cb8b4..d726858 100644 --- a/https/callBuilder_test.go +++ b/https/callBuilder_test.go @@ -11,6 +11,7 @@ import ( "reflect" "strings" "testing" + "time" ) var ctx = context.Background() @@ -134,6 +135,31 @@ func TestAppendTemplateParamsIntegers(t *testing.T) { } } +func TestAppendTemplateParamsAnySlice(t *testing.T) { + request := GetCallBuilder(ctx, "GET", "/template/%v", nil) + request.AppendTemplateParams([]any{time.Time{}}) + _, response, _ := request.CallAsJson() + + expected := 200 + + if response.StatusCode != expected { + t.Errorf("Failed:\nExpected: %v\nGot: %v", expected, response) + } +} + +func TestAppendTemplateParamsAny(t *testing.T) { + request := GetCallBuilder(ctx, "GET", "/template/%v", nil) + request.AppendTemplateParams(time.Now()) + request.AppendTemplateParams("abc") + request.AppendTemplateParams(2) + _, response, _ := request.CallAsJson() + expected := 200 + + if response.StatusCode != expected { + t.Errorf("Failed:\nExpected: %v\nGot: %v", expected, response) + } +} + func TestBaseUrlValue(t *testing.T) { request := GetCallBuilder(ctx, "", "/response/integer", nil) request.BaseUrl("https://github.com/apimatic") diff --git a/https/fileWrapper.go b/https/fileWrapper.go index 82666f2..1e2cb33 100644 --- a/https/fileWrapper.go +++ b/https/fileWrapper.go @@ -18,6 +18,10 @@ type FileWrapper struct { FileHeaders http.Header } +func (f FileWrapper) String() string { + return fmt.Sprintf("FileWrapper[FileName=%v]", f.FileName) +} + // isURL checks if the given parsedPath is a URL func isURL(parsedPath *url.URL) bool { return parsedPath.Scheme == "http" || parsedPath.Scheme == "https" diff --git a/https/fileWrapper_test.go b/https/fileWrapper_test.go index 71582c7..1f183df 100644 --- a/https/fileWrapper_test.go +++ b/https/fileWrapper_test.go @@ -55,6 +55,19 @@ func TestGetFileWithContentTypeFromLocalPath(t *testing.T) { } } +func TestGetFileStringFromLocalPath(t *testing.T) { + file, err := GetFileWithContentType("../internal/binary.png", "image/png") + if err != nil { + t.Errorf("GetFile failed: %v", err) + } + + if file.String() != "FileWrapper[FileName=binary.png]" || + file.FileHeaders.Get(CONTENT_TYPE_HEADER) != "image/png" || + len(file.File) <= 0 { + t.Errorf("Expected Image File not recieved ") + } +} + func TestGetFileErrorParsingUrl(t *testing.T) { _, err := GetFile("") if err == nil { diff --git a/types/optional.go b/types/optional.go index 661905b..cd10681 100644 --- a/types/optional.go +++ b/types/optional.go @@ -61,3 +61,7 @@ func (o *Optional[T]) UnmarshalJSON(input []byte) error { return nil } + +func (o Optional[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(o.value) +} diff --git a/types/optional_test.go b/types/optional_test.go index 6c55193..1b057a5 100644 --- a/types/optional_test.go +++ b/types/optional_test.go @@ -66,3 +66,17 @@ func TestUnmarshalJSONError(t *testing.T) { t.Errorf("Failed:\nExpected: Unmarshalling Error \nGot: %v", result) } } + +func TestMarshalJSON(t *testing.T) { + value := "Optional Value" + expected := types.Optional[string]{} + expected.SetValue(&value) + expected.ShouldSetValue(true) + optionalBytes, _ := json.Marshal(expected) + + var result string + _ = json.Unmarshal(optionalBytes, &result) + if value != result { + t.Errorf("Failed:\nExpected: %v\nGot: %v", value, result) + } +} diff --git a/utilities/unionTypeHelper_test.go b/utilities/unionTypeHelper_test.go index 08731a1..6ff8ad7 100644 --- a/utilities/unionTypeHelper_test.go +++ b/utilities/unionTypeHelper_test.go @@ -219,7 +219,7 @@ func TestOneOfDiscriminator(t *testing.T) { discriminators: []string{"4 wheeler", "2 wheeler"}, discriminatorField: "type", testValue: `{"id":2345,"roof":"BIG","type":"2 wheeler"}`, - expectedValue: `{"id":2345,"roof":"BIG","air_level":{},"type":"2 wheeler"}`, + expectedValue: `{"id":2345,"roof":"BIG","air_level":null,"type":"2 wheeler"}`, expectedType: &internal.Bike{}, }, { @@ -326,7 +326,7 @@ func TestAnyOfDiscriminator(t *testing.T) { discriminators: []string{"", "2 wheeler"}, discriminatorField: "type", testValue: `{"id":2345,"roof":"BIG","type":"2 wheeler"}`, - expectedValue: `{"id":2345,"roof":"BIG","air_level":{},"type":"2 wheeler"}`, + expectedValue: `{"id":2345,"roof":"BIG","air_level":null,"type":"2 wheeler"}`, expectedType: &internal.Bike{}, }, }