mirror of
https://github.com/retailcrm/api-client-go.git
synced 2024-11-21 20:36:03 +03:00
Neur0toxine
97b1abf470
* refactor library and upgrade version * remove useless environment variable * remove unsupported versions from go.mod * fixes for error handling & data types * different improvements for errors * fixes for types and tests * better coverage, fix error with the unmarshalers
193 lines
5.2 KiB
Go
193 lines
5.2 KiB
Go
package retailcrm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var missingParameterMatcher = regexp.MustCompile(`^Parameter \'([\w\]\[\_\-]+)\' is missing$`)
|
|
var (
|
|
// ErrMissingCredentials will be returned if no API key was provided to the API.
|
|
ErrMissingCredentials = NewAPIError(`apiKey is missing`)
|
|
// ErrInvalidCredentials will be returned if provided API key is invalid.
|
|
ErrInvalidCredentials = NewAPIError(`wrong "apiKey" value`)
|
|
// ErrAccessDenied will be returned in case of "Access denied" error.
|
|
ErrAccessDenied = NewAPIError("access denied")
|
|
// ErrAccountDoesNotExist will be returned if target system does not exist.
|
|
ErrAccountDoesNotExist = NewAPIError("account does not exist")
|
|
// ErrValidation will be returned in case of validation errors.
|
|
ErrValidation = NewAPIError("validation error")
|
|
// ErrMissingParameter will be returned if parameter is missing.
|
|
// Underlying error messages list will contain parameter name in the "Name" key.
|
|
ErrMissingParameter = NewAPIError("missing parameter")
|
|
// ErrGeneric will be returned if error cannot be classified as one of the errors above.
|
|
ErrGeneric = NewAPIError("API error")
|
|
)
|
|
|
|
// APIErrorsList struct.
|
|
type APIErrorsList map[string]string
|
|
|
|
// APIError returns when an API error was occurred.
|
|
type APIError interface {
|
|
error
|
|
fmt.Stringer
|
|
withWrapped(error) APIError
|
|
withErrors(APIErrorsList) APIError
|
|
Unwrap() error
|
|
Errors() APIErrorsList
|
|
}
|
|
|
|
type apiError struct {
|
|
ErrorMsg string `json:"errorMsg,omitempty"`
|
|
ErrorsList APIErrorsList `json:"errors,omitempty"`
|
|
wrapped error
|
|
}
|
|
|
|
// CreateAPIError from the provided response data. Different error types will be returned depending on the response,
|
|
// all of them can be matched using errors.Is. APi errors will always implement APIError interface.
|
|
func CreateAPIError(dataResponse []byte) error {
|
|
a := &apiError{}
|
|
|
|
if len(dataResponse) > 0 && dataResponse[0] == '<' {
|
|
return ErrAccountDoesNotExist
|
|
}
|
|
|
|
if err := json.Unmarshal(dataResponse, &a); err != nil {
|
|
return err
|
|
}
|
|
|
|
var found APIError
|
|
switch a.ErrorMsg {
|
|
case `"apiKey" is missing.`:
|
|
found = ErrMissingCredentials
|
|
case `Wrong "apiKey" value.`:
|
|
found = ErrInvalidCredentials
|
|
case "Access denied.":
|
|
found = ErrAccessDenied
|
|
case "Account does not exist.":
|
|
found = ErrAccountDoesNotExist
|
|
case "Errors in the entity format":
|
|
fallthrough
|
|
case "Validation error":
|
|
found = ErrValidation
|
|
default:
|
|
if param, ok := asMissingParameterErr(a.ErrorMsg); ok {
|
|
return a.withWrapped(ErrMissingParameter).withErrors(APIErrorsList{"Name": param})
|
|
}
|
|
found = ErrGeneric
|
|
}
|
|
|
|
result := NewAPIError(a.ErrorMsg).withWrapped(found)
|
|
if len(a.ErrorsList) > 0 {
|
|
return result.withErrors(a.ErrorsList)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// CreateGenericAPIError for the situations when API response cannot be processed, but response was actually received.
|
|
func CreateGenericAPIError(message string) APIError {
|
|
return NewAPIError(message).withWrapped(ErrGeneric)
|
|
}
|
|
|
|
// NewAPIError returns API error with the provided message.
|
|
func NewAPIError(message string) APIError {
|
|
return &apiError{ErrorMsg: message}
|
|
}
|
|
|
|
// AsAPIError returns APIError and true if provided error is an APIError or contains wrapped APIError.
|
|
// Returns (nil, false) otherwise.
|
|
func AsAPIError(err error) (APIError, bool) {
|
|
apiErr := unwrapAPIError(err)
|
|
return apiErr, apiErr != nil
|
|
}
|
|
|
|
func unwrapAPIError(err error) APIError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if apiErr, ok := err.(APIError); ok { // nolint:errorlint
|
|
return apiErr
|
|
}
|
|
|
|
wrapper, ok := err.(interface { // nolint:errorlint
|
|
Unwrap() error
|
|
})
|
|
if ok {
|
|
return unwrapAPIError(wrapper.Unwrap())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// asMissingParameterErr returns true if "Parameter 'name' is missing" error message is provided.
|
|
func asMissingParameterErr(message string) (string, bool) {
|
|
matches := missingParameterMatcher.FindAllStringSubmatch(message, -1)
|
|
if len(matches) == 1 && len(matches[0]) == 2 {
|
|
return matches[0][1], true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// Error returns errorMsg field from the response.
|
|
func (e *apiError) Error() string {
|
|
return e.ErrorMsg
|
|
}
|
|
|
|
// Unwrap returns wrapped error. It is usually one of the predefined types like ErrGeneric or ErrValidation.
|
|
// It can be used directly, but it's main purpose is to make errors matchable via errors.Is call.
|
|
func (e *apiError) Unwrap() error {
|
|
return e.wrapped
|
|
}
|
|
|
|
// Errors returns errors field from the response.
|
|
func (e *apiError) Errors() APIErrorsList {
|
|
return e.ErrorsList
|
|
}
|
|
|
|
// String returns string representation of an APIError.
|
|
func (e *apiError) String() string {
|
|
var sb strings.Builder
|
|
sb.Grow(256) // nolint:gomnd
|
|
sb.WriteString(fmt.Sprintf(`errorMsg: "%s"`, e.Error()))
|
|
|
|
if len(e.Errors()) > 0 {
|
|
i := 0
|
|
useIndex := true
|
|
errorList := make([]string, len(e.Errors()))
|
|
|
|
for index, errText := range e.Errors() {
|
|
if i == 0 && index == "0" {
|
|
useIndex = false
|
|
}
|
|
|
|
if useIndex {
|
|
errorList[i] = fmt.Sprintf(`%s: "%s"`, index, errText)
|
|
} else {
|
|
errorList[i] = errText
|
|
}
|
|
|
|
i++
|
|
}
|
|
|
|
sb.WriteString(", errors: [" + strings.Join(errorList, ", ") + "]")
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// withWrapped is a wrapped setter.
|
|
func (e *apiError) withWrapped(err error) APIError {
|
|
e.wrapped = err
|
|
return e
|
|
}
|
|
|
|
// withErrors is an ErrorsList setter.
|
|
func (e *apiError) withErrors(m APIErrorsList) APIError {
|
|
e.ErrorsList = m
|
|
return e
|
|
}
|