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
1 change: 1 addition & 0 deletions changes/20251201172918.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:camel: Upgrade dependencies
127 changes: 9 additions & 118 deletions utils/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,146 +8,37 @@ package api

import (
"context"
"fmt"
"net/http"
"reflect"
"strings"

"github.com/perimeterx/marshmallow"

"github.com/ARM-software/embedded-development-services-client-utils/utils/errors"
"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/parallelisation"
"github.com/ARM-software/golang-utils/utils/reflection"
"github.com/ARM-software/golang-utils/utils/safeio"
"github.com/ARM-software/golang-utils/utils/http/api"
)

const requiredFieldError = "no value given for required property"

// Deprecated: Use github.com/ARM-software/golang-utils/utils/http/api instead
// IsCallSuccessful determines whether an API response is successful or not
func IsCallSuccessful(r *http.Response) bool {
if r == nil {
return false
}
return r.StatusCode >= http.StatusOK && r.StatusCode < http.StatusMultipleChoices
return api.IsCallSuccessful(r)
}

// CheckAPICallSuccess verifies whether an API response is successful or not and if not, populates an error with all the information needed.
// errorContext corresponds to the description of what led to the error if error there is e.g. `Failed adding a user`.
// resp corresponds to the HTTP response from a certain endpoint. The body of such response is not closed by this function.
// apiErr corresponds to the error which may be returned by the HTTP client when calling the endpoint.
func CheckAPICallSuccess(ctx context.Context, errorContext string, resp *http.Response, apiErr error) (err error) {
err = parallelisation.DetermineContextError(ctx)
if err != nil {
return
}
if !IsCallSuccessful(resp) {
statusCode := 0
errorMessage := strings.Builder{}
respErr := commonerrors.ErrUnexpected
if resp != nil {
statusCode = resp.StatusCode
respErr = errors.MapErrorToHTTPResponseCode(statusCode)
if respErr == nil {
respErr = commonerrors.ErrUnexpected
}
errorDetails, subErr := errors.FetchAPIErrorDescriptionWithContext(ctx, resp)
if commonerrors.Ignore(subErr, commonerrors.ErrMarshalling) != nil {
err = commonerrors.Join(commonerrors.New(respErr, errorContext), subErr)
return
}
if !reflection.IsEmpty(errorDetails) {
errorMessage.WriteString(errorDetails)
}
_ = resp.Body.Close()
}
extra := ""
if apiErr != nil {
extra = fmt.Sprintf("; %v", apiErr.Error())
}
err = commonerrors.Newf(respErr, "%v (%d): %v%v", errorContext, statusCode, errorMessage.String(), extra)
}
return
func CheckAPICallSuccess(ctx context.Context, errorContext string, resp *http.Response, apiErr error) error {
return api.CheckAPICallSuccess(ctx, errorContext, errors.FetchAPIErrorDescriptionWithContext, resp, apiErr)
}

// CallAndCheckSuccess is a wrapper for making an API call and then checking success with `CheckAPICallSuccess`
// 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 CallAndCheckSuccess[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 {
defer func() {
if resp != nil && resp.Body != nil {
_ = resp.Body.Close()
}
}()
}

err = checkResponse(ctx, apiErr, resp, result, errorContext)
return
}

func checkResponse(ctx context.Context, apiErr error, resp *http.Response, result any, errorContext string) (err error) {
err = CheckAPICallSuccess(ctx, errorContext, resp, apiErr)
if err != nil {
return
}

if apiErr != nil {
err = commonerrors.WrapError(commonerrors.ErrMarshalling, apiErr, "API call was successful but an error occurred during response marshalling")
if commonerrors.CorrespondTo(apiErr, requiredFieldError) {
return
}
if resp == nil || resp.Body == nil {
return
}
// At this point, the marshalling problem may be due to the present of unknown fields in the response due to an API extension.
// See https://github.com/OpenAPITools/openapi-generator/issues/21446
var respB []byte
respB, err = safeio.ReadAll(ctx, resp.Body)
if err != nil {
return
}
_, err = marshmallow.Unmarshal(respB, result, marshmallow.WithSkipPopulateStruct(false), marshmallow.WithExcludeKnownFieldsFromMap(true))
if err != nil {
err = commonerrors.WrapError(commonerrors.ErrMarshalling, err, "API call was successful but an error occurred during response marshalling")
return
}
}
if reflection.IsEmpty(result) {
err = commonerrors.New(commonerrors.ErrMarshalling, "unmarshalled response is empty")
return
}
return
func CallAndCheckSuccess[T any](ctx context.Context, errorContext string, apiCallFunc func(ctx context.Context) (*T, *http.Response, error)) (*T, error) {
return api.CallAndCheckSuccess[T](ctx, errorContext, errors.FetchAPIErrorDescriptionWithContext, apiCallFunc)
}

// 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()
}

err = checkResponse(ctx, apiErr, resp, result, errorContext)
if 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
}

return
func GenericCallAndCheckSuccess[T any](ctx context.Context, errorContext string, apiCallFunc func(ctx context.Context) (T, *http.Response, error)) (T, error) {
return api.GenericCallAndCheckSuccess[T](ctx, errorContext, errors.FetchAPIErrorDescriptionWithContext, apiCallFunc)
}
20 changes: 0 additions & 20 deletions utils/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,6 @@ import (
"github.com/ARM-software/golang-utils/utils/field"
)

func TestIsAPICallSuccessful(t *testing.T) {
t.Run("api call successful", func(t *testing.T) {
resp := _http.Response{StatusCode: 200}
isSuccessful := IsCallSuccessful(&resp)
assert.True(t, isSuccessful)
})

t.Run("api call unsuccessful", func(t *testing.T) {
resp := _http.Response{StatusCode: 400}
isSuccessful := IsCallSuccessful(&resp)
assert.False(t, isSuccessful)
})

t.Run("api call returns nothing", func(t *testing.T) {
resp := _http.Response{}
isSuccessful := IsCallSuccessful(&resp)
assert.False(t, isSuccessful)
})
}

func TestCheckAPICallSuccess(t *testing.T) {
t.Run("context cancelled", func(t *testing.T) {
errMessage := "context cancelled"
Expand Down
96 changes: 4 additions & 92 deletions utils/errors/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,99 +5,11 @@
package errors

import (
"net/http"

"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/http/errors"
)

// MapErrorToHTTPResponseCode maps a response status code to a common error.
// Deprecated: MapErrorToHTTPResponseCode maps a response status code to a common error.
// Use github.com/ARM-software/golang-utils/utils/http/errors instead
func MapErrorToHTTPResponseCode(statusCode int) error {
if statusCode < http.StatusBadRequest {
return nil
}
switch statusCode {
case http.StatusBadRequest:
return commonerrors.ErrInvalid
case http.StatusUnauthorized:
return commonerrors.ErrUnauthorised
case http.StatusPaymentRequired:
return commonerrors.ErrUnknown
case http.StatusForbidden:
return commonerrors.ErrForbidden
case http.StatusNotFound:
return commonerrors.ErrNotFound
case http.StatusMethodNotAllowed:
return commonerrors.ErrNotFound
case http.StatusNotAcceptable:
return commonerrors.ErrUnsupported
case http.StatusProxyAuthRequired:
return commonerrors.ErrUnauthorised
case http.StatusRequestTimeout:
return commonerrors.ErrTimeout
case http.StatusConflict:
return commonerrors.ErrConflict
case http.StatusGone:
return commonerrors.ErrNotFound
case http.StatusLengthRequired:
return commonerrors.ErrInvalid
case http.StatusPreconditionFailed:
return commonerrors.ErrCondition
case http.StatusRequestEntityTooLarge:
return commonerrors.ErrTooLarge
case http.StatusRequestURITooLong:
return commonerrors.ErrTooLarge
case http.StatusUnsupportedMediaType:
return commonerrors.ErrUnsupported
case http.StatusRequestedRangeNotSatisfiable:
return commonerrors.ErrOutOfRange
case http.StatusExpectationFailed:
return commonerrors.ErrUnsupported
case http.StatusTeapot:
return commonerrors.ErrUnknown
case http.StatusMisdirectedRequest:
return commonerrors.ErrUnsupported
case http.StatusUnprocessableEntity:
return commonerrors.ErrMarshalling
case http.StatusLocked:
return commonerrors.ErrLocked
case http.StatusFailedDependency:
return commonerrors.ErrFailed
case http.StatusTooEarly:
return commonerrors.ErrUnexpected
case http.StatusUpgradeRequired:
return commonerrors.ErrUnsupported
case http.StatusPreconditionRequired:
return commonerrors.ErrCondition
case http.StatusTooManyRequests:
return commonerrors.ErrUnavailable
case http.StatusRequestHeaderFieldsTooLarge:
return commonerrors.ErrTooLarge
case http.StatusUnavailableForLegalReasons:
return commonerrors.ErrUnavailable

case http.StatusInternalServerError:
return commonerrors.ErrUnexpected
case http.StatusNotImplemented:
return commonerrors.ErrNotImplemented
case http.StatusBadGateway:
return commonerrors.ErrUnavailable
case http.StatusServiceUnavailable:
return commonerrors.ErrUnavailable
case http.StatusGatewayTimeout:
return commonerrors.ErrTimeout
case http.StatusHTTPVersionNotSupported:
return commonerrors.ErrUnsupported
case http.StatusVariantAlsoNegotiates:
return commonerrors.ErrUnexpected
case http.StatusInsufficientStorage:
return commonerrors.ErrUnexpected
case http.StatusLoopDetected:
return commonerrors.ErrUnexpected
case http.StatusNotExtended:
return commonerrors.ErrUnexpected
case http.StatusNetworkAuthenticationRequired:
return commonerrors.ErrUnauthorised
default:
return commonerrors.ErrUnexpected
}
return errors.MapErrorToHTTPResponseCode(statusCode)
}
10 changes: 5 additions & 5 deletions utils/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ module github.com/ARM-software/embedded-development-services-client-utils/utils
go 1.25

require (
github.com/ARM-software/embedded-development-services-client/client v1.82.0
github.com/ARM-software/golang-utils/utils v1.130.0
github.com/ARM-software/embedded-development-services-client/client v1.99.0
github.com/ARM-software/golang-utils/utils v1.136.0
github.com/go-faker/faker/v4 v4.7.0
github.com/go-logr/logr v1.4.3
github.com/perimeterx/marshmallow v1.1.5
github.com/stretchr/testify v1.11.1
go.uber.org/atomic v1.11.0
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.6.0
golang.org/x/sync v0.17.0
golang.org/x/sync v0.18.0
)

require (
Expand Down Expand Up @@ -57,6 +56,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
Expand All @@ -79,7 +79,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zalando/go-keyring v0.2.6 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
Expand Down
20 changes: 10 additions & 10 deletions utils/go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY=
github.com/ARM-software/embedded-development-services-client/client v1.82.0 h1:in5rwsgAlk7LYQROvU7BmSpoXCn4GA3V85W9/ODE0UM=
github.com/ARM-software/embedded-development-services-client/client v1.82.0/go.mod h1:jGywz6vB+i3RkF6J7LXSs0l4g6xHuGfbhW+iWMEODkY=
github.com/ARM-software/golang-utils/utils v1.130.0 h1:1LBLweH1qgJOxynOrcjbPeeoaiaPll8fYCa3ugLyyy4=
github.com/ARM-software/golang-utils/utils v1.130.0/go.mod h1:l1W+4uRhZCVEoDzozfomDG6erB6kZz63Q+DCn6xNGM8=
github.com/ARM-software/embedded-development-services-client/client v1.99.0 h1:to7sVrB6mkXmu+hIs3sa06FEB6bqhkv8zlKNL31fzNU=
github.com/ARM-software/embedded-development-services-client/client v1.99.0/go.mod h1:TPvvUCQkSffqCuP/eCN5ANpsyRKphbSwxaDqP7c5RBk=
github.com/ARM-software/golang-utils/utils v1.136.0 h1:/zYpzcXCG8Q/ycLVRwNMauzr0PFHqT/AK9K+DQOCFv0=
github.com/ARM-software/golang-utils/utils v1.136.0/go.mod h1:253UeNJdOTDnvpEDfJwki4MfgqAR8CaB1FzXFDzjseo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo=
github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
Expand Down Expand Up @@ -74,8 +74,8 @@ github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZ
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down Expand Up @@ -224,8 +224,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand All @@ -244,8 +244,8 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
Loading