Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,5 @@
}
],
"results": {},
"generated_at": "2025-03-28T15:16:31Z"
"generated_at": "2025-04-28T23:41:43Z"
}
1 change: 1 addition & 0 deletions changes/20250429002423.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:camel: Upgrade dependencies
1 change: 1 addition & 0 deletions changes/20250429004302.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: [api] Add a robust call method to deal with interfaces
1 change: 1 addition & 0 deletions changes/20250429004339.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:recycle: Make all the calls more robust to marshaling errors
32 changes: 32 additions & 0 deletions utils/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"fmt"
_http "net/http"
"reflect"
"strings"

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

return
}

// GenericCallAndCheckSuccess is similar to CallAndCheckSuccess but for function returning interfaces rather than concrete types.
// T must be an interface.
// errorContext corresponds to the description of what led to the error if error there is e.g. `Failed adding a user`.
// apiCallFunc corresponds to a generic function that will be called to make the API call
func GenericCallAndCheckSuccess[T any](ctx context.Context, errorContext string, apiCallFunc func(ctx context.Context) (T, *_http.Response, error)) (result T, err error) {
if err = parallelisation.DetermineContextError(ctx); err != nil {
return
}

result, resp, apiErr := apiCallFunc(ctx)
if resp != nil && resp.Body != nil {
_ = resp.Body.Close()
}

if err = CheckAPICallSuccess(ctx, errorContext, resp, apiErr); err != nil {
return
}

if reflect.ValueOf(result).Kind() != reflect.Ptr {
err = commonerrors.Newf(commonerrors.ErrConflict, "result of the call is of type [%T] and so, not a pointer as expected", result)
return
}

if reflection.IsEmpty(result) {
err = commonerrors.New(commonerrors.ErrMarshalling, "unmarshalled response is empty")
return
}

return
}
62 changes: 62 additions & 0 deletions utils/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
_http "net/http"
"testing"

"github.com/go-faker/faker/v4"
"github.com/stretchr/testify/assert"

"github.com/ARM-software/golang-utils/utils/commonerrors"
Expand Down Expand Up @@ -123,3 +124,64 @@ func TestCallAndCheckSuccess(t *testing.T) {
errortest.AssertError(t, err, commonerrors.ErrMarshalling)
})
}

func TestGenericCallAndCheckSuccess(t *testing.T) {
t.Run("context cancelled", func(t *testing.T) {
errMessage := "context cancelled"
parentCtx := context.Background()
ctx, cancelCtx := context.WithCancel(parentCtx)
cancelCtx()
_, actualErr := GenericCallAndCheckSuccess(ctx, errMessage,
func(ctx context.Context) (*struct{}, *_http.Response, error) {
return nil, &_http.Response{Body: io.NopCloser(bytes.NewReader(nil))}, errors.New(errMessage)
})
errortest.AssertError(t, actualErr, commonerrors.ErrCancelled)
})

t.Run("api call not successful", func(t *testing.T) {
errMessage := "client error"
parentCtx := context.Background()
_, actualErr := GenericCallAndCheckSuccess(parentCtx, errMessage,
func(ctx context.Context) (*struct{}, *_http.Response, error) {
resp := _http.Response{StatusCode: 400, Body: io.NopCloser(bytes.NewReader([]byte("{\"message\": \"client error\",\"requestId\": \"761761721\"}")))}
return nil, &resp, errors.New(errMessage)
})
expectedErr := "client error (400): API call error [request-id: 761761721] client error; client error"
assert.Equal(t, actualErr.Error(), expectedErr)
})

t.Run("no context error, api call successful", func(t *testing.T) {
errMessage := "no error"
parentCtx := context.Background()
_, err := GenericCallAndCheckSuccess(parentCtx, errMessage,
func(ctx context.Context) (any, *_http.Response, error) {
tmp := struct {
test string
}{
test: faker.Word(),
}
return &tmp, &_http.Response{StatusCode: 200}, errors.New(errMessage)
})
assert.NoError(t, err)
})

t.Run("api call successful, empty response", func(t *testing.T) {
errMessage := "response error"
parentCtx := context.Background()
_, err := GenericCallAndCheckSuccess(parentCtx, errMessage,
func(ctx context.Context) (*struct{}, *_http.Response, error) {
return &struct{}{}, &_http.Response{StatusCode: 200}, errors.New(errMessage)
})
errortest.AssertError(t, err, commonerrors.ErrMarshalling)
})

t.Run("api call successful, incorrect response", func(t *testing.T) {
errMessage := "response error"
parentCtx := context.Background()
_, err := GenericCallAndCheckSuccess(parentCtx, errMessage,
func(ctx context.Context) (struct{}, *_http.Response, error) {
return struct{}{}, &_http.Response{StatusCode: 200}, errors.New(errMessage)
})
errortest.AssertError(t, err, commonerrors.ErrConflict)
})
}
Loading
Loading