packer-cn/vendor/github.com/scaleway/scaleway-sdk-go/scw/errors.go

485 lines
13 KiB
Go

package scw
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/validation"
)
// SdkError is a base interface for all Scaleway SDK errors.
type SdkError interface {
Error() string
IsScwSdkError()
}
// ResponseError is an error type for the Scaleway API
type ResponseError struct {
// Message is a human-friendly error message
Message string `json:"message"`
// Type is a string code that defines the kind of error. This field is only used by instance API
Type string `json:"type,omitempty"`
// Resource is a string code that defines the resource concerned by the error. This field is only used by instance API
Resource string `json:"resource,omitempty"`
// Fields contains detail about validation error. This field is only used by instance API
Fields map[string][]string `json:"fields,omitempty"`
// StatusCode is the HTTP status code received
StatusCode int `json:"-"`
// Status is the HTTP status received
Status string `json:"-"`
RawBody json.RawMessage `json:"-"`
}
func (e *ResponseError) UnmarshalJSON(b []byte) error {
type tmpResponseError ResponseError
tmp := tmpResponseError(*e)
err := json.Unmarshal(b, &tmp)
if err != nil {
return err
}
tmp.Message = strings.ToLower(tmp.Message)
*e = ResponseError(tmp)
return nil
}
// IsScwSdkError implement SdkError interface
func (e *ResponseError) IsScwSdkError() {}
func (e *ResponseError) Error() string {
s := fmt.Sprintf("scaleway-sdk-go: http error %s", e.Status)
if e.Resource != "" {
s = fmt.Sprintf("%s: resource %s", s, e.Resource)
}
if e.Message != "" {
s = fmt.Sprintf("%s: %s", s, e.Message)
}
if len(e.Fields) > 0 {
s = fmt.Sprintf("%s: %v", s, e.Fields)
}
return s
}
func (e *ResponseError) GetRawBody() json.RawMessage {
return e.RawBody
}
// hasResponseError returns an SdkError when the HTTP status is not OK.
func hasResponseError(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
newErr := &ResponseError{
StatusCode: res.StatusCode,
Status: res.Status,
}
if res.Body == nil {
return newErr
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "cannot read error response body")
}
newErr.RawBody = body
// The error content is not encoded in JSON, only returns HTTP data.
if res.Header.Get("Content-Type") != "application/json" {
newErr.Message = res.Status
return newErr
}
err = json.Unmarshal(body, newErr)
if err != nil {
return errors.Wrap(err, "could not parse error response body")
}
err = unmarshalStandardError(newErr.Type, body)
if err != nil {
return err
}
err = unmarshalNonStandardError(newErr.Type, body)
if err != nil {
return err
}
return newErr
}
func unmarshalStandardError(errorType string, body []byte) error {
var stdErr SdkError
switch errorType {
case "invalid_arguments":
stdErr = &InvalidArgumentsError{RawBody: body}
case "quotas_exceeded":
stdErr = &QuotasExceededError{RawBody: body}
case "transient_state":
stdErr = &TransientStateError{RawBody: body}
case "not_found":
stdErr = &ResourceNotFoundError{RawBody: body}
case "locked":
stdErr = &ResourceLockedError{RawBody: body}
case "permissions_denied":
stdErr = &PermissionsDeniedError{RawBody: body}
case "out_of_stock":
stdErr = &OutOfStockError{RawBody: body}
case "resource_expired":
stdErr = &ResourceExpiredError{RawBody: body}
default:
return nil
}
err := json.Unmarshal(body, stdErr)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}
return stdErr
}
func unmarshalNonStandardError(errorType string, body []byte) error {
switch errorType {
// Only in instance API.
case "unknown_resource":
unknownResourceError := &UnknownResource{RawBody: body}
err := json.Unmarshal(body, unknownResourceError)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}
return unknownResourceError.ToResourceNotFoundError()
case "invalid_request_error":
invalidRequestError := &InvalidRequestError{RawBody: body}
err := json.Unmarshal(body, invalidRequestError)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}
invalidArgumentsError := invalidRequestError.ToInvalidArgumentsError()
if invalidArgumentsError != nil {
return invalidArgumentsError
}
quotasExceededError := invalidRequestError.ToQuotasExceededError()
if quotasExceededError != nil {
return quotasExceededError
}
// At this point, the invalid_request_error is not an InvalidArgumentsError and
// the default marshalling will be used.
return nil
default:
return nil
}
}
type InvalidArgumentsErrorDetail struct {
ArgumentName string `json:"argument_name"`
Reason string `json:"reason"`
HelpMessage string `json:"help_message"`
}
type InvalidArgumentsError struct {
Details []InvalidArgumentsErrorDetail `json:"details"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *InvalidArgumentsError) IsScwSdkError() {}
func (e *InvalidArgumentsError) Error() string {
invalidArgs := make([]string, len(e.Details))
for i, d := range e.Details {
invalidArgs[i] = d.ArgumentName
switch d.Reason {
case "unknown":
invalidArgs[i] += " is invalid for unexpected reason"
case "required":
invalidArgs[i] += " is required"
case "format":
invalidArgs[i] += " is wrongly formatted"
case "constraint":
invalidArgs[i] += " does not respect constraint"
}
if d.HelpMessage != "" {
invalidArgs[i] += ", " + d.HelpMessage
}
}
return "scaleway-sdk-go: invalid argument(s): " + strings.Join(invalidArgs, "; ")
}
func (e *InvalidArgumentsError) GetRawBody() json.RawMessage {
return e.RawBody
}
// UnknownResource is only returned by the instance API.
// Warning: this is not a standard error.
type UnknownResource struct {
Message string `json:"message"`
RawBody json.RawMessage `json:"-"`
}
// ToSdkError returns a standard error InvalidArgumentsError or nil Fields is nil.
func (e *UnknownResource) ToResourceNotFoundError() SdkError {
resourceNotFound := &ResourceNotFoundError{
RawBody: e.RawBody,
}
messageParts := strings.Split(e.Message, `"`)
// Some errors uses ' and not "
if len(messageParts) == 1 {
messageParts = strings.Split(e.Message, "'")
}
switch len(messageParts) {
case 2: // message like: `"111..." not found`
resourceNotFound.ResourceID = messageParts[0]
case 3: // message like: `Security Group "111..." not found`
resourceNotFound.ResourceID = messageParts[1]
// transform `Security group ` to `security_group`
resourceNotFound.Resource = strings.ReplaceAll(strings.ToLower(strings.TrimSpace(messageParts[0])), " ", "_")
default:
return nil
}
if !validation.IsUUID(resourceNotFound.ResourceID) {
return nil
}
return resourceNotFound
}
// InvalidRequestError is only returned by the instance API.
// Warning: this is not a standard error.
type InvalidRequestError struct {
Message string `json:"message"`
Fields map[string][]string `json:"fields"`
Resource string `json:"resource"`
RawBody json.RawMessage `json:"-"`
}
// ToSdkError returns a standard error InvalidArgumentsError or nil Fields is nil.
func (e *InvalidRequestError) ToInvalidArgumentsError() SdkError {
// If error has no fields, it is not an InvalidArgumentsError.
if e.Fields == nil || len(e.Fields) == 0 {
return nil
}
invalidArguments := &InvalidArgumentsError{
RawBody: e.RawBody,
}
fieldNames := []string(nil)
for fieldName := range e.Fields {
fieldNames = append(fieldNames, fieldName)
}
sort.Strings(fieldNames)
for _, fieldName := range fieldNames {
for _, message := range e.Fields[fieldName] {
invalidArguments.Details = append(invalidArguments.Details, InvalidArgumentsErrorDetail{
ArgumentName: fieldName,
Reason: "constraint",
HelpMessage: message,
})
}
}
return invalidArguments
}
func (e *InvalidRequestError) ToQuotasExceededError() SdkError {
if !strings.Contains(strings.ToLower(e.Message), "quota exceeded for this resource") {
return nil
}
return &QuotasExceededError{
Details: []QuotasExceededErrorDetail{
{
Resource: e.Resource,
Quota: 0,
Current: 0,
},
},
RawBody: e.RawBody,
}
}
type QuotasExceededErrorDetail struct {
Resource string `json:"resource"`
Quota uint32 `json:"quota"`
Current uint32 `json:"current"`
}
type QuotasExceededError struct {
Details []QuotasExceededErrorDetail `json:"details"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *QuotasExceededError) IsScwSdkError() {}
func (e *QuotasExceededError) Error() string {
invalidArgs := make([]string, len(e.Details))
for i, d := range e.Details {
invalidArgs[i] = fmt.Sprintf("%s has reached its quota (%d/%d)", d.Resource, d.Current, d.Current)
}
return "scaleway-sdk-go: quota exceeded(s): " + strings.Join(invalidArgs, "; ")
}
func (e *QuotasExceededError) GetRawBody() json.RawMessage {
return e.RawBody
}
type PermissionsDeniedError struct {
Details []struct {
Resource string `json:"resource"`
Action string `json:"action"`
} `json:"details"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *PermissionsDeniedError) IsScwSdkError() {}
func (e *PermissionsDeniedError) Error() string {
invalidArgs := make([]string, len(e.Details))
for i, d := range e.Details {
invalidArgs[i] = fmt.Sprintf("%s %s", d.Action, d.Resource)
}
return "scaleway-sdk-go: insufficient permissions: " + strings.Join(invalidArgs, "; ")
}
func (e *PermissionsDeniedError) GetRawBody() json.RawMessage {
return e.RawBody
}
type TransientStateError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
CurrentState string `json:"current_state"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *TransientStateError) IsScwSdkError() {}
func (e *TransientStateError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s is in a transient state: %s", e.Resource, e.ResourceID, e.CurrentState)
}
func (e *TransientStateError) GetRawBody() json.RawMessage {
return e.RawBody
}
type ResourceNotFoundError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *ResourceNotFoundError) IsScwSdkError() {}
func (e *ResourceNotFoundError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s is not found", e.Resource, e.ResourceID)
}
func (e *ResourceNotFoundError) GetRawBody() json.RawMessage {
return e.RawBody
}
type ResourceLockedError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *ResourceLockedError) IsScwSdkError() {}
func (e *ResourceLockedError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s is locked", e.Resource, e.ResourceID)
}
func (e *ResourceLockedError) GetRawBody() json.RawMessage {
return e.RawBody
}
type OutOfStockError struct {
Resource string `json:"resource"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *OutOfStockError) IsScwSdkError() {}
func (e *OutOfStockError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s is out of stock", e.Resource)
}
func (e *OutOfStockError) GetRawBody() json.RawMessage {
return e.RawBody
}
// InvalidClientOptionError indicates that at least one of client data has been badly provided for the client creation.
type InvalidClientOptionError struct {
errorType string
}
func NewInvalidClientOptionError(format string, a ...interface{}) *InvalidClientOptionError {
return &InvalidClientOptionError{errorType: fmt.Sprintf(format, a...)}
}
// IsScwSdkError implements the SdkError interface
func (e InvalidClientOptionError) IsScwSdkError() {}
func (e InvalidClientOptionError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: %s", e.errorType)
}
// ConfigFileNotFound indicates that the config file could not be found
type ConfigFileNotFoundError struct {
path string
}
func configFileNotFound(path string) *ConfigFileNotFoundError {
return &ConfigFileNotFoundError{path: path}
}
// ConfigFileNotFoundError implements the SdkError interface
func (e ConfigFileNotFoundError) IsScwSdkError() {}
func (e ConfigFileNotFoundError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: cannot read config file %s: no such file or directory", e.path)
}
// ResourceExpiredError implements the SdkError interface
type ResourceExpiredError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
ExpiredSince time.Time `json:"expired_since"`
RawBody json.RawMessage `json:"-"`
}
func (r ResourceExpiredError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s expired since %s", r.Resource, r.ResourceID, r.ExpiredSince.String())
}
func (r ResourceExpiredError) IsScwSdkError() {}