Skip to content

Commit 5a5fc51

Browse files
committed
Add new custom error types
1 parent db45e4b commit 5a5fc51

File tree

2 files changed

+379
-0
lines changed

2 files changed

+379
-0
lines changed

internal/pkg/errors/errors.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package errors
22

33
import (
4+
"encoding/json"
5+
sysErrors "errors"
46
"fmt"
57
"strings"
68

79
"github.com/spf13/cobra"
10+
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
811
)
912

1013
const (
@@ -548,3 +551,136 @@ type OneOfFlagsIsMissing struct {
548551
func (e *OneOfFlagsIsMissing) Error() string {
549552
return fmt.Sprintf(ONE_OF_THE_FLAGS_MUST_BE_PROVIDED_WHEN_ANOTHER_FLAG_IS_SET, e.MissingFlags, e.SetFlag)
550553
}
554+
555+
// ___FORMATTING_ERRORS_________________________________________________________
556+
557+
// InvalidFormatError indicates that an unsupported format was provided.
558+
type InvalidFormatError struct {
559+
Format string // The invalid format that was provided
560+
}
561+
562+
func (e *InvalidFormatError) Error() string {
563+
if e.Format != "" {
564+
return fmt.Sprintf("unsupported format provided: %s", e.Format)
565+
}
566+
return "unsupported format provided"
567+
}
568+
569+
// NewInvalidFormatError creates a new InvalidFormatError with the provided format.
570+
func NewInvalidFormatError(format string) *InvalidFormatError {
571+
return &InvalidFormatError{
572+
Format: format,
573+
}
574+
}
575+
576+
// ___BUILD_REQUEST_ERRORS______________________________________________________
577+
// BuildRequestError indicates that a request could not be built.
578+
type BuildRequestError struct {
579+
Reason string // Optional: specific reason why the request failed to build
580+
Err error // Optional: underlying error
581+
}
582+
583+
func (e *BuildRequestError) Error() string {
584+
if e.Reason != "" && e.Err != nil {
585+
return fmt.Sprintf("could not build request (%s): %v", e.Reason, e.Err)
586+
}
587+
if e.Reason != "" {
588+
return fmt.Sprintf("could not build request: %s", e.Reason)
589+
}
590+
if e.Err != nil {
591+
return fmt.Sprintf("could not build request: %v", e.Err)
592+
}
593+
return "could not build request"
594+
}
595+
596+
func (e *BuildRequestError) Unwrap() error {
597+
return e.Err
598+
}
599+
600+
// NewBuildRequestError creates a new BuildRequestError with optional reason and underlying error.
601+
func NewBuildRequestError(reason string, err error) *BuildRequestError {
602+
return &BuildRequestError{
603+
Reason: reason,
604+
Err: err,
605+
}
606+
}
607+
608+
// ___REQUESTS_ERRORS___________________________________________________________
609+
// RequestFailedError indicates that an API request failed.
610+
// If the provided error is an OpenAPI error, the status code and message from the error body will be included in the error message.
611+
type RequestFailedError struct {
612+
Err error // Optional: underlying error
613+
}
614+
615+
func (e *RequestFailedError) Error() string {
616+
var msg = "request failed"
617+
618+
if e.Err != nil {
619+
var oApiErr *oapierror.GenericOpenAPIError
620+
if sysErrors.As(e.Err, &oApiErr) {
621+
// Extract status code from OpenAPI error header if it exists
622+
if oApiErr.StatusCode > 0 {
623+
msg += fmt.Sprintf(" (%d)", oApiErr.StatusCode)
624+
}
625+
626+
// Try to extract message from OpenAPI error body
627+
if bodyMsg := extractOpenApiMessageFromBody(oApiErr.Body); bodyMsg != "" {
628+
msg += fmt.Sprintf(": %s", bodyMsg)
629+
} else if trimmedBody := strings.TrimSpace(string(oApiErr.Body)); trimmedBody != "" {
630+
msg += fmt.Sprintf(": %s", trimmedBody)
631+
} else {
632+
// Otherwise use the Go error
633+
msg += fmt.Sprintf(": %v", e.Err)
634+
}
635+
} else {
636+
// If this can't be cased into a OpenApi error use the Go error
637+
msg += fmt.Sprintf(": %v", e.Err)
638+
}
639+
}
640+
641+
return msg
642+
}
643+
644+
func (e *RequestFailedError) Unwrap() error {
645+
return e.Err
646+
}
647+
648+
// NewRequestFailedError creates a new RequestFailedError with optional details.
649+
func NewRequestFailedError(err error) *RequestFailedError {
650+
return &RequestFailedError{
651+
Err: err,
652+
}
653+
}
654+
655+
// ___HELPERS___________________________________________________________________
656+
// extractOpenApiMessageFromBody attempts to parse a JSON body and extract the "message"
657+
// field. It returns an empty string if parsing fails or if no message is found.
658+
func extractOpenApiMessageFromBody(body []byte) string {
659+
trimmedBody := strings.TrimSpace(string(body))
660+
// Return early if empty.
661+
if trimmedBody == "" {
662+
return ""
663+
}
664+
665+
// Try to unmarshal as a structured error first
666+
var errorBody struct {
667+
Message string `json:"message"`
668+
}
669+
if err := json.Unmarshal(body, &errorBody); err == nil && errorBody.Message != "" {
670+
if msg := strings.TrimSpace(errorBody.Message); msg != "" {
671+
return msg
672+
}
673+
}
674+
675+
// If that fails, try to unmarshal as a plain string
676+
var plainBody string
677+
if err := json.Unmarshal(body, &plainBody); err == nil && plainBody != "" {
678+
if msg := strings.TrimSpace(plainBody); msg != "" {
679+
return msg
680+
}
681+
return ""
682+
}
683+
684+
// All parsing attempts failed or yielded no message
685+
return ""
686+
}

0 commit comments

Comments
 (0)