Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(go): add a fix for handling oneOf anyOf from and query parameters in the Core library. #61

Merged
merged 28 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b2533d8
feat: add form data first implementation
Shield-Jaguar Feb 26, 2024
ad9052d
feat: add fixes for all the tests
Shield-Jaguar Feb 28, 2024
da9a265
feat: add array serialization seperator support
Shield-Jaguar Feb 28, 2024
8653f88
feat: refactoring
Shield-Jaguar Feb 28, 2024
aee8590
feat: add array serialization config
Shield-Jaguar Feb 28, 2024
39d91ef
feat: add small fixes for different cases
Shield-Jaguar Feb 29, 2024
11bdb63
refactor: rename func names
Shield-Jaguar Feb 29, 2024
792789f
feat: refactor interface name
Shield-Jaguar Feb 29, 2024
85ef46b
feat: update text related errors
Shield-Jaguar Feb 29, 2024
d99de58
feat: add form data first implementation
Shield-Jaguar Feb 26, 2024
d81be92
feat: add fixes for all the tests
Shield-Jaguar Feb 28, 2024
8816854
feat: add array serialization seperator support
Shield-Jaguar Feb 28, 2024
327332f
feat: refactoring
Shield-Jaguar Feb 28, 2024
5e994a9
feat: add array serialization config
Shield-Jaguar Feb 28, 2024
863e928
feat: add small fixes for different cases
Shield-Jaguar Feb 29, 2024
fe249d7
refactor: rename func names
Shield-Jaguar Feb 29, 2024
46e1536
feat: refactor interface name
Shield-Jaguar Feb 29, 2024
84bbd8c
feat: update text related errors
Shield-Jaguar Feb 29, 2024
a387539
Merge branch 'form-data-fix' of https://github.com/apimatic/go-core-r…
Feb 29, 2024
fd9a9e4
added merging changes
Feb 29, 2024
f30bdaf
feat: add unit tests for array serialization class
Shield-Jaguar Mar 4, 2024
1179369
refactor: update variable name
Shield-Jaguar Mar 4, 2024
8d5dbc9
fix: spelling mistake in internal error
Shield-Jaguar Mar 5, 2024
3c94399
refactor: refactor to map method
Shield-Jaguar Mar 6, 2024
3fa7a26
Merge branch 'main' into form-data-fix
saeedjamshaid Mar 6, 2024
a2fa229
added changes for fixing oneOf anyOf test
Mar 6, 2024
35e4477
fixed test for Empty_Optional Func
Mar 6, 2024
9a3ae6e
added fix for form param tests
Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions https/arraySerialization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package https

import "fmt"

// ArraySerializationOption represents the type for request array serialization options.
type ArraySerializationOption int

// Constants for different request retry options.
const (
Indexed = iota
UnIndexed
Plain
Csv
Tsv
Psv
)

var ArraySerializationOptionStrings = [...]string{
"Indexed",
"UnIndexed",
"Plain",
"Csv",
"Tsv",
"Psv",
}

func (option ArraySerializationOption) getSeparator() rune {
switch option {
case Csv:
return ','
case Tsv:
return '\t'
case Psv:
return '|'
default:
return -1
}
}

func (option ArraySerializationOption) joinKey(keyPrefix string, index any) string {
if index == nil {
switch option {
case UnIndexed:
return fmt.Sprintf("%v[]", keyPrefix)
default:
return fmt.Sprintf("%v", keyPrefix)
}
}
indexedKey := fmt.Sprintf("%v", index)
return fmt.Sprintf("%v[%v]", keyPrefix, indexedKey)
}

func (option ArraySerializationOption) appendMap(result map[string][]string, param map[string][]string) {
for k, values := range param {
for _, value := range values {
option.append(result, k, value)
}
}
}

func (option ArraySerializationOption) append(result map[string][]string, key string, value string) {
separator := option.getSeparator()
if len(result[key]) > 0 && separator != -1 {
result[key][0] = fmt.Sprintf("%v%c%v", result[key][0], separator, value)
} else {
result[key] = append(result[key], value)
}
}
152 changes: 152 additions & 0 deletions https/arraySerialization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package https

import (
"reflect"
"testing"
)

func TestGetSeparator(t *testing.T) {
testCases := make(map[ArraySerializationOption]rune)
testCases[Indexed] = -1
testCases[UnIndexed] = -1
testCases[Plain] = -1
testCases[Tsv] = '\t'
testCases[Csv] = ','
testCases[Psv] = '|'
testCases[100] = -1
testCases[-1] = -1

for testCase, expectedValue := range testCases {
actual := testCase.getSeparator()
if actual != expectedValue {
t.Errorf("For option %v, expected separator %c but got %c", ArraySerializationOptionStrings[testCase], testCase, actual)
}
}
}

func TestJoinKey(t *testing.T) {
testCases := []struct {
option ArraySerializationOption
keyPrefix string
index any
expected string
}{
{UnIndexed, "prefix", nil, "prefix[]"},
{UnIndexed, "prefix", "index", "prefix[index]"},
{Indexed, "prefix", nil, "prefix"},
{Indexed, "prefix", "index", "prefix[index]"},
{Plain, "prefix", nil, "prefix"},
{Plain, "prefix", "index", "prefix[index]"},
{Tsv, "prefix", nil, "prefix"},
{Tsv, "prefix", "index", "prefix[index]"},
{Csv, "prefix", nil, "prefix"},
{Csv, "prefix", "index", "prefix[index]"},
{Psv, "prefix", nil, "prefix"},
{Psv, "prefix", "index", "prefix[index]"},
}

for _, tc := range testCases {
actual := tc.option.joinKey(tc.keyPrefix, tc.index)
if actual != tc.expected {
t.Errorf("For option %s, keyPrefix %s, and index %v, expected %q but got %q",
ArraySerializationOptionStrings[tc.option], tc.keyPrefix, tc.index, tc.expected, actual)
}
}
}

func TestAppendMap(t *testing.T) {
testCases := []struct {
option ArraySerializationOption
result map[string][]string
param map[string][]string
expected map[string][]string
}{
// Test case where result map is empty
{
option: Csv,
result: map[string][]string{},
param: map[string][]string{
"key1": {"value1"},
},
expected: map[string][]string{
"key1": {"value1"},
},
},
// Test case where result map is non-empty and separator is set
{
option: Tsv,
result: map[string][]string{
"key1": {"value1"},
},
param: map[string][]string{
"key1": {"value2"},
},
expected: map[string][]string{
"key1": {"value1\tvalue2"},
},
},
// Test case where result map is non-empty and separator is not set
{
option: Indexed,
result: map[string][]string{
"key1": {"value1"},
},
param: map[string][]string{
"key1": {"value2"},
},
expected: map[string][]string{
"key1": {"value1", "value2"},
},
},
}

for _, tc := range testCases {
tc.option.appendMap(tc.result, tc.param)

// Check if the result map matches the expected result
if !reflect.DeepEqual(tc.result, tc.expected) {
t.Errorf("For option %d, expected %v but got %v", tc.option, tc.expected, tc.result)
}
}
}

func TestAppend(t *testing.T) {
testCases := []struct {
option ArraySerializationOption
result map[string][]string
key string
value string
expected map[string][]string
}{
// Test case where result map is empty
{option: Csv, result: map[string][]string{"key1": {"value1"}}, key: "key1", value: "value2", expected: map[string][]string{"key1": {"value1,value2"}}},
// Test case where result map is non-empty and separator is set
{Tsv, map[string][]string{"key1": {"value1"}}, "key1", "value2", map[string][]string{"key1": {"value1\tvalue2"}}},
{Psv, map[string][]string{"key1": {"value1"}}, "key1", "value2", map[string][]string{"key1": {"value1|value2"}}},
// Test case where result map is non-empty and separator is not set
{Indexed, map[string][]string{"key1": {"value1"}}, "key1", "value2", map[string][]string{"key1": {"value1", "value2"}}},
}

for _, tc := range testCases {
tc.option.append(tc.result, tc.key, tc.value)

// Check if the result map matches the expected result
for k, v := range tc.expected {
if !equalSlices(tc.result[k], v) {
t.Errorf("For key %q, expected %v but got %v", k, v, tc.result[k])
}
}
}
}

func equalSlices(slice1, slice2 []string) bool {
if len(slice1) != len(slice2) {
return false
}
for i, v := range slice1 {
if v != slice2[i] {
return false
}
}
return true
}
47 changes: 32 additions & 15 deletions https/callBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type CallBuilder interface {
CallAsStream() ([]byte, *http.Response, error)
Authenticate(authGroup AuthGroup)
RequestRetryOption(option RequestRetryOption)
ArraySerializationOption(option ArraySerializationOption)
}

// defaultCallBuilder is a struct that implements the CallBuilder interface for making API calls.
Expand Down Expand Up @@ -94,6 +95,7 @@ type defaultCallBuilder struct {
formParams FormParams
queryParams FormParams
errors map[string]ErrorBuilder[error]
arraySerializationOption ArraySerializationOption
}

// newDefaultCallBuilder creates a new instance of defaultCallBuilder, which implements the CallBuilder interface.
Expand All @@ -105,17 +107,19 @@ func newDefaultCallBuilder(
baseUrlProvider baseUrlProvider,
authProvider map[string]AuthInterface,
retryConfig RetryConfiguration,
option ArraySerializationOption,
) *defaultCallBuilder {
cb := defaultCallBuilder{
httpClient: httpClient,
path: path,
httpMethod: httpMethod,
authProvider: authProvider,
baseUrlProvider: baseUrlProvider,
retryOption: RequestRetryOption(Default),
clientError: nil,
retryConfig: retryConfig,
ctx: ctx,
httpClient: httpClient,
path: path,
httpMethod: httpMethod,
authProvider: authProvider,
baseUrlProvider: baseUrlProvider,
retryOption: RequestRetryOption(Default),
clientError: nil,
retryConfig: retryConfig,
ctx: ctx,
arraySerializationOption: option,
}
cb.addRetryInterceptor()
return &cb
Expand Down Expand Up @@ -148,6 +152,10 @@ func (cb *defaultCallBuilder) RequestRetryOption(option RequestRetryOption) {
cb.retryOption = option
}

func (cb *defaultCallBuilder) ArraySerializationOption(option ArraySerializationOption) {
cb.arraySerializationOption = option
}

// AppendPath appends the provided path to the existing path in the CallBuilder.
func (cb *defaultCallBuilder) AppendPath(path string) {
if cb.path != "" {
Expand Down Expand Up @@ -268,7 +276,7 @@ func (cb *defaultCallBuilder) validateQueryParams() error {
if cb.query == nil {
cb.query = url.Values{}
}
err := cb.queryParams.prepareFormFields(cb.query)
err := cb.queryParams.prepareFormFields(cb.query, cb.arraySerializationOption)
if err != nil {
return internalError{Body: err.Error(), FileInfo: "CallBuilder.go/validateQueryParams"}
}
Expand Down Expand Up @@ -298,7 +306,7 @@ func (cb *defaultCallBuilder) validateFormParams() error {
if cb.form == nil {
cb.form = url.Values{}
}
err := cb.formParams.prepareFormFields(cb.form)
err := cb.formParams.prepareFormFields(cb.form, cb.arraySerializationOption)
if err != nil {
return internalError{Body: err.Error(), FileInfo: "CallBuilder.go/validateFormParams"}
}
Expand All @@ -321,7 +329,7 @@ func (cb *defaultCallBuilder) validateFormData() error {
var headerVal string
var err error = nil
if len(cb.formFields) != 0 {
cb.formData, headerVal, err = cb.formFields.prepareMultipartFields()
cb.formData, headerVal, err = cb.formFields.prepareMultipartFields(cb.arraySerializationOption)
if err != nil {
return internalError{Body: err.Error(), FileInfo: "CallBuilder.go/validateFormData"}
}
Expand Down Expand Up @@ -362,7 +370,13 @@ func (cb *defaultCallBuilder) validateJson() error {
return internalError{Body: fmt.Sprintf("Unable to marshal the given data: %v", err.Error()), FileInfo: "CallBuilder.go/validateJson"}
}
cb.body = string(bytes)
cb.setContentTypeIfNotSet(JSON_CONTENT_TYPE)
contentType := JSON_CONTENT_TYPE
var testMap map[string]any
errTest := json.Unmarshal(bytes, &testMap)
if errTest != nil {
contentType = TEXT_CONTENT_TYPE
}
cb.setContentTypeIfNotSet(contentType)
}
return nil
}
Expand Down Expand Up @@ -594,7 +608,7 @@ func (cb *defaultCallBuilder) CallAsText() (string, *http.Response, error) {

body, err := io.ReadAll(result.Response.Body)
if err != nil {
return "", result.Response, fmt.Errorf("Error reading Response body: %v", err.Error())
return "", result.Response, fmt.Errorf("error reading Response body: %v", err.Error())
}

return string(body), result.Response, err
Expand All @@ -617,7 +631,7 @@ func (cb *defaultCallBuilder) CallAsStream() ([]byte, *http.Response, error) {

bytes, err := io.ReadAll(result.Response.Body)
if err != nil {
return nil, result.Response, fmt.Errorf("Error reading Response body: %v", err.Error())
return nil, result.Response, fmt.Errorf("error reading Response body: %v", err.Error())
}

return bytes, result.Response, err
Expand Down Expand Up @@ -704,11 +718,13 @@ func CreateCallBuilderFactory(
auth map[string]AuthInterface,
httpClient HttpClient,
retryConfig RetryConfiguration,
option ArraySerializationOption,
) CallBuilderFactory {
return func(
ctx context.Context,
httpMethod,
path string,

) CallBuilder {
return newDefaultCallBuilder(
ctx,
Expand All @@ -718,6 +734,7 @@ func CreateCallBuilderFactory(
baseUrlProvider,
auth,
retryConfig,
option,
)
}
}
1 change: 1 addition & 0 deletions https/callBuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func GetCallBuilder(ctx context.Context, method, path string, auth map[string]Au
auth,
client,
NewRetryConfiguration(),
Indexed,
)

return callBuilder(ctx, method, path)
Expand Down
2 changes: 1 addition & 1 deletion https/fileWrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func GetFile(fileUrl string) (FileWrapper, error) {
func ReadBytes(input io.Reader) ([]byte, error) {
bytes, err := io.ReadAll(input)
if err != nil {
err = fmt.Errorf("Error reading file: %v", err)
err = fmt.Errorf("error reading file: %v", err)
}
return bytes, err
}
Loading
Loading