Skip to content

Commit 7e9c8f1

Browse files
authored
[api] Add a robust call method to deal with interfaces (#108)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Proprietary --> ### Description Make the code more robust to silent marshaling errors ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update). --------- Co-authored-by: acabarbaye <[email protected]>
1 parent f65d524 commit 7e9c8f1

20 files changed

+273
-157
lines changed

.secrets.baseline

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,5 @@
101101
}
102102
],
103103
"results": {},
104-
"generated_at": "2025-03-28T15:16:31Z"
104+
"generated_at": "2025-04-28T23:41:43Z"
105105
}

changes/20250429002423.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:camel: Upgrade dependencies

changes/20250429004302.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: [api] Add a robust call method to deal with interfaces

changes/20250429004339.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:recycle: Make all the calls more robust to marshaling errors

utils/api/api.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"context"
1111
"fmt"
1212
_http "net/http"
13+
"reflect"
1314
"strings"
1415

1516
"github.com/ARM-software/embedded-development-services-client-utils/utils/errors"
@@ -85,3 +86,34 @@ func CallAndCheckSuccess[T any](ctx context.Context, errorContext string, apiCal
8586

8687
return
8788
}
89+
90+
// GenericCallAndCheckSuccess is similar to CallAndCheckSuccess but for function returning interfaces rather than concrete types.
91+
// T must be an interface.
92+
// errorContext corresponds to the description of what led to the error if error there is e.g. `Failed adding a user`.
93+
// apiCallFunc corresponds to a generic function that will be called to make the API call
94+
func GenericCallAndCheckSuccess[T any](ctx context.Context, errorContext string, apiCallFunc func(ctx context.Context) (T, *_http.Response, error)) (result T, err error) {
95+
if err = parallelisation.DetermineContextError(ctx); err != nil {
96+
return
97+
}
98+
99+
result, resp, apiErr := apiCallFunc(ctx)
100+
if resp != nil && resp.Body != nil {
101+
_ = resp.Body.Close()
102+
}
103+
104+
if err = CheckAPICallSuccess(ctx, errorContext, resp, apiErr); err != nil {
105+
return
106+
}
107+
108+
if reflect.ValueOf(result).Kind() != reflect.Ptr {
109+
err = commonerrors.Newf(commonerrors.ErrConflict, "result of the call is of type [%T] and so, not a pointer as expected", result)
110+
return
111+
}
112+
113+
if reflection.IsEmpty(result) {
114+
err = commonerrors.New(commonerrors.ErrMarshalling, "unmarshalled response is empty")
115+
return
116+
}
117+
118+
return
119+
}

utils/api/api_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
_http "net/http"
1414
"testing"
1515

16+
"github.com/go-faker/faker/v4"
1617
"github.com/stretchr/testify/assert"
1718

1819
"github.com/ARM-software/golang-utils/utils/commonerrors"
@@ -123,3 +124,64 @@ func TestCallAndCheckSuccess(t *testing.T) {
123124
errortest.AssertError(t, err, commonerrors.ErrMarshalling)
124125
})
125126
}
127+
128+
func TestGenericCallAndCheckSuccess(t *testing.T) {
129+
t.Run("context cancelled", func(t *testing.T) {
130+
errMessage := "context cancelled"
131+
parentCtx := context.Background()
132+
ctx, cancelCtx := context.WithCancel(parentCtx)
133+
cancelCtx()
134+
_, actualErr := GenericCallAndCheckSuccess(ctx, errMessage,
135+
func(ctx context.Context) (*struct{}, *_http.Response, error) {
136+
return nil, &_http.Response{Body: io.NopCloser(bytes.NewReader(nil))}, errors.New(errMessage)
137+
})
138+
errortest.AssertError(t, actualErr, commonerrors.ErrCancelled)
139+
})
140+
141+
t.Run("api call not successful", func(t *testing.T) {
142+
errMessage := "client error"
143+
parentCtx := context.Background()
144+
_, actualErr := GenericCallAndCheckSuccess(parentCtx, errMessage,
145+
func(ctx context.Context) (*struct{}, *_http.Response, error) {
146+
resp := _http.Response{StatusCode: 400, Body: io.NopCloser(bytes.NewReader([]byte("{\"message\": \"client error\",\"requestId\": \"761761721\"}")))}
147+
return nil, &resp, errors.New(errMessage)
148+
})
149+
expectedErr := "client error (400): API call error [request-id: 761761721] client error; client error"
150+
assert.Equal(t, actualErr.Error(), expectedErr)
151+
})
152+
153+
t.Run("no context error, api call successful", func(t *testing.T) {
154+
errMessage := "no error"
155+
parentCtx := context.Background()
156+
_, err := GenericCallAndCheckSuccess(parentCtx, errMessage,
157+
func(ctx context.Context) (any, *_http.Response, error) {
158+
tmp := struct {
159+
test string
160+
}{
161+
test: faker.Word(),
162+
}
163+
return &tmp, &_http.Response{StatusCode: 200}, errors.New(errMessage)
164+
})
165+
assert.NoError(t, err)
166+
})
167+
168+
t.Run("api call successful, empty response", func(t *testing.T) {
169+
errMessage := "response error"
170+
parentCtx := context.Background()
171+
_, err := GenericCallAndCheckSuccess(parentCtx, errMessage,
172+
func(ctx context.Context) (*struct{}, *_http.Response, error) {
173+
return &struct{}{}, &_http.Response{StatusCode: 200}, errors.New(errMessage)
174+
})
175+
errortest.AssertError(t, err, commonerrors.ErrMarshalling)
176+
})
177+
178+
t.Run("api call successful, incorrect response", func(t *testing.T) {
179+
errMessage := "response error"
180+
parentCtx := context.Background()
181+
_, err := GenericCallAndCheckSuccess(parentCtx, errMessage,
182+
func(ctx context.Context) (struct{}, *_http.Response, error) {
183+
return struct{}{}, &_http.Response{StatusCode: 200}, errors.New(errMessage)
184+
})
185+
errortest.AssertError(t, err, commonerrors.ErrConflict)
186+
})
187+
}

0 commit comments

Comments
 (0)