Skip to content

Commit 1ab6558

Browse files
authored
perf(go): refactor code for JSON body and query parameters handling (#64)
1 parent e8e4c2f commit 1ab6558

File tree

7 files changed

+98
-129
lines changed

7 files changed

+98
-129
lines changed

https/callBuilder.go

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"net/http"
1111
"net/url"
12+
"reflect"
1213
"strconv"
1314
"strings"
1415
"time"
@@ -37,14 +38,14 @@ type baseUrlProvider func(server string) string
3738
type CallBuilder interface {
3839
AppendPath(path string)
3940
AppendTemplateParam(param string)
40-
AppendTemplateParams(params interface{})
41+
AppendTemplateParams(params any)
4142
AppendErrors(errors map[string]ErrorBuilder[error])
4243
BaseUrl(arg string)
4344
Method(httpMethodName string)
4445
validateMethod() error
4546
Accept(acceptHeaderValue string)
4647
ContentType(contentTypeHeaderValue string)
47-
Header(name string, value interface{})
48+
Header(name string, value any)
4849
CombineHeaders(headersToMerge map[string]string)
4950
QueryParam(name string, value any)
5051
QueryParamWithArraySerializationOption(name string, value any, option ArraySerializationOption)
@@ -57,7 +58,7 @@ type CallBuilder interface {
5758
validateFormData() error
5859
Text(body string)
5960
FileStream(file FileWrapper)
60-
Json(data interface{})
61+
Json(data any)
6162
validateJson() error
6263
intercept(interceptor HttpInterceptor)
6364
InterceptRequest(interceptor func(httpRequest *http.Request) *http.Request)
@@ -92,7 +93,7 @@ type defaultCallBuilder struct {
9293
retryOption RequestRetryOption
9394
retryConfig RetryConfiguration
9495
clientError error
95-
jsonData interface{}
96+
jsonData any
9697
formFields formParams
9798
formParams formParams
9899
queryParams formParams
@@ -169,7 +170,7 @@ func (cb *defaultCallBuilder) AppendPath(path string) {
169170

170171
// AppendTemplateParam appends the provided parameter to the existing path in the CallBuilder as a URL template parameter.
171172
func (cb *defaultCallBuilder) AppendTemplateParam(param string) {
172-
if strings.Contains(cb.path, "%s") {
173+
if strings.Contains(cb.path, "%v") {
173174
cb.path = fmt.Sprintf(cb.path, "/"+url.QueryEscape(param))
174175
} else {
175176
cb.AppendPath(url.QueryEscape(param))
@@ -178,15 +179,19 @@ func (cb *defaultCallBuilder) AppendTemplateParam(param string) {
178179

179180
// AppendTemplateParams appends the provided parameters to the existing path in the CallBuilder as URL template parameters.
180181
// It accepts a slice of strings or a slice of integers as the params argument.
181-
func (cb *defaultCallBuilder) AppendTemplateParams(params interface{}) {
182-
switch x := params.(type) {
183-
case []string:
184-
for _, param := range x {
185-
cb.AppendTemplateParam(param)
186-
}
187-
case []int:
188-
for _, param := range x {
189-
cb.AppendTemplateParam(strconv.Itoa(int(param)))
182+
func (cb *defaultCallBuilder) AppendTemplateParams(params any) {
183+
reflectValue := reflect.ValueOf(params)
184+
if reflectValue.Type().Kind() == reflect.Slice {
185+
for i := 0; i < reflectValue.Len(); i++ {
186+
innerParam := reflectValue.Index(i).Interface()
187+
switch x := innerParam.(type) {
188+
case string:
189+
cb.AppendTemplateParam(x)
190+
case int:
191+
cb.AppendTemplateParam(strconv.Itoa(int(x)))
192+
default:
193+
cb.AppendTemplateParam(fmt.Sprintf("%v", x))
194+
}
190195
}
191196
}
192197
}
@@ -250,7 +255,7 @@ func (cb *defaultCallBuilder) ContentType(contentTypeHeaderValue string) {
250255
// It takes the name of the header and the value of the header as arguments.
251256
func (cb *defaultCallBuilder) Header(
252257
name string,
253-
value interface{},
258+
value any,
254259
) {
255260
if cb.headers == nil {
256261
cb.headers = make(map[string]string)
@@ -297,8 +302,8 @@ func (cb *defaultCallBuilder) validateQueryParams() error {
297302
}
298303

299304
// QueryParams sets multiple query parameters for the API call.
300-
// It takes a map of string keys and interface{} values representing the query parameters.
301-
func (cb *defaultCallBuilder) QueryParams(parameters map[string]interface{}) {
305+
// It takes a map of string keys and any values representing the query parameters.
306+
func (cb *defaultCallBuilder) QueryParams(parameters map[string]any) {
302307
cb.query = utilities.PrepareQueryParams(cb.query, parameters)
303308
}
304309

@@ -377,7 +382,7 @@ func (cb *defaultCallBuilder) FileStream(file FileWrapper) {
377382
}
378383

379384
// Json sets the request body for the API call as JSON.
380-
func (cb *defaultCallBuilder) Json(data interface{}) {
385+
func (cb *defaultCallBuilder) Json(data any) {
381386
cb.jsonData = data
382387
}
383388

@@ -387,22 +392,31 @@ func (cb *defaultCallBuilder) Json(data interface{}) {
387392
// If there is an error during marshaling, it returns an internalError.
388393
func (cb *defaultCallBuilder) validateJson() error {
389394
if cb.jsonData != nil {
390-
bytes, err := json.Marshal(cb.jsonData)
395+
dataBytes, err := json.Marshal(cb.jsonData)
391396
if err != nil {
392397
return internalError{Body: fmt.Sprintf("Unable to marshal the given data: %v", err.Error()), FileInfo: "CallBuilder.go/validateJson"}
393398
}
394-
cb.body = string(bytes)
395-
contentType := JSON_CONTENT_TYPE
396-
var testMap map[string]any
397-
errTest := json.Unmarshal(bytes, &testMap)
398-
if errTest != nil {
399-
contentType = TEXT_CONTENT_TYPE
399+
if !cb.isOAFJson(dataBytes) {
400+
cb.body = string(dataBytes)
401+
cb.setContentTypeIfNotSet(JSON_CONTENT_TYPE)
400402
}
401-
cb.setContentTypeIfNotSet(contentType)
402403
}
403404
return nil
404405
}
405406

407+
func (cb *defaultCallBuilder) isOAFJson(dataBytes []byte) bool {
408+
switch reflect.TypeOf(cb.jsonData).Kind() {
409+
case reflect.Struct, reflect.Ptr:
410+
var testObj map[string]any
411+
structErr := json.Unmarshal(dataBytes, &testObj)
412+
if structErr != nil {
413+
cb.Text(fmt.Sprintf("%v", cb.jsonData))
414+
return true
415+
}
416+
}
417+
return false
418+
}
419+
406420
// setContentTypeIfNotSet sets the "Content-Type" header if it is not already set in the CallBuilder.
407421
// It takes the contentType as an argument and sets it as the value for the "Content-Type" header.
408422
// If the headers map is nil, it initializes it before setting the header.

https/callBuilder_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func TestAppendPathEmptyPath(t *testing.T) {
9090
}
9191

9292
func TestAppendTemplateParamsStrings(t *testing.T) {
93-
request := GetCallBuilder(ctx, "GET", "/template/%s", nil)
93+
request := GetCallBuilder(ctx, "GET", "/template/%v", nil)
9494
request.AppendTemplateParams([]string{"abc", "def"})
9595
_, response, err := request.CallAsJson()
9696
if err != nil {
@@ -105,7 +105,7 @@ func TestAppendTemplateParamsStrings(t *testing.T) {
105105
}
106106

107107
func TestAppendTemplateParamsIntegers(t *testing.T) {
108-
request := GetCallBuilder(ctx, "GET", "/template/%s", nil)
108+
request := GetCallBuilder(ctx, "GET", "/template/%v", nil)
109109
request.AppendTemplateParams([]int{1, 2, 3, 4, 5})
110110
_, response, err := request.CallAsJson()
111111
if err != nil {

https/formData.go

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ type FormParam struct {
2121
}
2222

2323
type formParam struct {
24-
Key string
25-
Value any
26-
Headers http.Header
27-
ArraySerializationOption ArraySerializationOption
24+
key string
25+
value any
26+
headers http.Header
27+
arraySerializationOption ArraySerializationOption
2828
}
2929

3030
type formParams []formParam
@@ -41,7 +41,7 @@ func (fp *FormParams) Add(formParam FormParam) {
4141

4242
// Add appends a FormParam to the FormParams collection.
4343
func (fp *formParams) add(formParam formParam) {
44-
if formParam.Value != nil {
44+
if formParam.value != nil {
4545
*fp = append(*fp, formParam)
4646
}
4747
}
@@ -53,7 +53,7 @@ func (fp *formParams) prepareFormFields(form url.Values) error {
5353
form = url.Values{}
5454
}
5555
for _, param := range *fp {
56-
paramsMap, err := toMap(param.Key, param.Value, param.ArraySerializationOption)
56+
paramsMap, err := toMap(param.key, param.value, param.arraySerializationOption)
5757
if err != nil {
5858
return err
5959
}
@@ -72,22 +72,22 @@ func (fp *formParams) prepareMultipartFields() (bytes.Buffer, string, error) {
7272
body := &bytes.Buffer{}
7373
writer := multipart.NewWriter(body)
7474
for _, field := range *fp {
75-
switch fieldValue := field.Value.(type) {
75+
switch fieldValue := field.value.(type) {
7676
case FileWrapper:
7777
mediaParam := map[string]string{
78-
"name": field.Key,
78+
"name": field.key,
7979
"filename": fieldValue.FileName,
8080
}
81-
formParamWriter(writer, field.Headers, mediaParam, fieldValue.File)
81+
formParamWriter(writer, field.headers, mediaParam, fieldValue.File)
8282
default:
83-
paramsMap, err := toMap(field.Key, field.Value, field.ArraySerializationOption)
83+
paramsMap, err := toMap(field.key, field.value, field.arraySerializationOption)
8484
if err != nil {
8585
return *body, writer.FormDataContentType(), err
8686
}
8787
for key, values := range paramsMap {
8888
mediaParam := map[string]string{"name": key}
8989
for _, value := range values {
90-
formParamWriter(writer, field.Headers, mediaParam, []byte(value))
90+
formParamWriter(writer, field.headers, mediaParam, []byte(value))
9191
}
9292
}
9393
}
@@ -119,25 +119,16 @@ func formParamWriter(
119119
return nil
120120
}
121121

122-
func toMap(keyPrefix string, paramObj any, option ArraySerializationOption) (map[string][]string, error) {
123-
if paramObj == nil {
122+
func toMap(keyPrefix string, param any, option ArraySerializationOption) (map[string][]string, error) {
123+
if param == nil {
124124
return map[string][]string{}, nil
125125
}
126126

127-
var param any
128-
marshalBytes, err := json.Marshal(toStructPtr(paramObj))
129-
if err == nil && reflect.TypeOf(paramObj).Kind() != reflect.Map {
130-
err = json.Unmarshal(marshalBytes, &param)
131-
if err != nil {
132-
return map[string][]string{}, nil
133-
}
134-
} else {
135-
param = paramObj
136-
}
137-
138127
switch reflect.TypeOf(param).Kind() {
139-
case reflect.Struct, reflect.Ptr:
128+
case reflect.Ptr:
140129
return processStructAndPtr(keyPrefix, param, option)
130+
case reflect.Struct:
131+
return processStructAndPtr(keyPrefix, toStructPtr(param), option)
141132
case reflect.Map:
142133
return processMap(keyPrefix, param, option)
143134
case reflect.Slice:
@@ -148,11 +139,10 @@ func toMap(keyPrefix string, paramObj any, option ArraySerializationOption) (map
148139
}
149140

150141
func processStructAndPtr(keyPrefix string, param any, option ArraySerializationOption) (map[string][]string, error) {
151-
innerMap, err := structToMap(param)
152-
if err != nil {
153-
return nil, err
154-
}
155-
return toMap(keyPrefix, innerMap, option)
142+
innerData, err := structToAny(param)
143+
if err != nil { return nil, err }
144+
145+
return toMap(keyPrefix, innerData, option)
156146
}
157147

158148
func processMap(keyPrefix string, param any, option ArraySerializationOption) (map[string][]string, error) {
@@ -175,7 +165,7 @@ func processSlice(keyPrefix string, param any, option ArraySerializationOption)
175165
result := make(map[string][]string)
176166
for i := 0; i < reflectValue.Len(); i++ {
177167
innerStruct := reflectValue.Index(i).Interface()
178-
var indexStr interface{}
168+
var indexStr any
179169
switch innerStruct.(type) {
180170
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128, string:
181171
indexStr = nil
@@ -194,35 +184,32 @@ func processSlice(keyPrefix string, param any, option ArraySerializationOption)
194184

195185
func processDefault(keyPrefix string, param any) (map[string][]string, error) {
196186
var defaultValue string
197-
switch in := param.(type) {
198-
case string:
199-
defaultValue = in
187+
switch reflect.TypeOf(param).Kind() {
188+
case reflect.String:
189+
defaultValue = fmt.Sprintf("%v", param)
200190
default:
201-
dataBytes, err := json.Marshal(in)
191+
dataBytes, err := json.Marshal(param)
202192
if err == nil {
203193
defaultValue = string(dataBytes)
204194
} else {
205-
defaultValue = fmt.Sprintf("%v", in)
195+
defaultValue = fmt.Sprintf("%v", param)
206196
}
207197
}
208198
return map[string][]string{keyPrefix: {defaultValue}}, nil
209199
}
210200

211-
// structToMap converts a given data structure to a map.
212-
func structToMap(data any) (map[string]any, error) {
213-
if reflect.TypeOf(data).Kind() != reflect.Ptr {
214-
data = toStructPtr(data)
215-
}
201+
// structToAny converts a given data structure into an any type.
202+
func structToAny(data any) (any, error) {
216203
dataBytes, err := json.Marshal(data)
217204
if err != nil {
218205
return nil, err
219206
}
220-
mapData := make(map[string]interface{})
221-
err = json.Unmarshal(dataBytes, &mapData)
222-
return mapData, err
207+
var innerData any
208+
err = json.Unmarshal(dataBytes, &innerData)
209+
return innerData, err
223210
}
224211

225-
// Return a pointer to the supplied struct via interface{}
212+
// Return a pointer to the supplied struct via any
226213
func toStructPtr(obj any) any {
227214
// Create a new instance of the underlying type
228215
vp := reflect.New(reflect.TypeOf(obj))

https/formData_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ func GetStruct() Person {
1919
}
2020

2121
func TestStructToMap(t *testing.T) {
22-
result, _ := structToMap(GetStruct())
22+
result, _ := structToAny(GetStruct())
2323

24-
expected := map[string]interface{}{
24+
expected := map[string]any{
2525
"Name": "Bisma",
2626
"Employed": true,
2727
}
@@ -32,7 +32,7 @@ func TestStructToMap(t *testing.T) {
3232
}
3333

3434
func TestStructToMapMarshallingError(t *testing.T) {
35-
result, err := structToMap(math.Inf(1))
35+
result, err := structToAny(math.Inf(1))
3636

3737
if err == nil && result != nil {
3838
t.Error("Failed:\nExpected error in marshalling infinity number")

0 commit comments

Comments
 (0)