Skip to content

Commit

Permalink
feat: refactor error message format
Browse files Browse the repository at this point in the history
  • Loading branch information
binbin-li committed Aug 5, 2024
1 parent 8b17053 commit af1a0d8
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 93 deletions.
4 changes: 2 additions & 2 deletions cmd/ratify/cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ func TestDiscover(t *testing.T) {

// TODO: make ratify cli more unit testable
// unit test should not need to resolve real image
if !strings.Contains(err.Error(), "referrer store failure") {
t.Errorf("error expected")
if !strings.Contains(err.Error(), "REFERRER_STORE_FAILURE") {
t.Errorf("expected containing: %s, but got: %s", "REFERRER_STORE_FAILURE", err.Error())
}
}

Expand Down
133 changes: 80 additions & 53 deletions errors/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,16 @@ type ErrorCode int

// Error provides a wrapper around ErrorCode with extra Details provided.
type Error struct {
OriginalError error `json:"originalError,omitempty"`
Code ErrorCode `json:"code"`
Message string `json:"message"`
Detail interface{} `json:"detail,omitempty"`
ComponentType ComponentType `json:"componentType,omitempty"`
PluginName string `json:"pluginName,omitempty"`
LinkToDoc string `json:"linkToDoc,omitempty"`
Stack string `json:"stack,omitempty"`
Description string `json:"description,omitempty"`
originalError error
code ErrorCode
message string
detail interface{}
componentType ComponentType
remediation string
pluginName string
stack string
description string
isRootError bool // isRootError is true if the originalError is either nil or not an Error type
}

// ErrorDescriptor provides relevant information about a given error code.
Expand Down Expand Up @@ -152,7 +153,7 @@ func (ec ErrorCode) WithComponentType(componentType ComponentType) Error {

// WithLinkToDoc returns a new Error object with attached link to the documentation.
func (ec ErrorCode) WithLinkToDoc(link string) Error {
return newError(ec, ec.Message()).WithLinkToDoc(link)
return newError(ec, ec.Message()).WithRemediation(link)
}

func (ec ErrorCode) WithDescription() Error {
Expand All @@ -165,118 +166,144 @@ func (ec ErrorCode) WithPluginName(pluginName string) Error {
}

// NewError returns a new Error object.
func (ec ErrorCode) NewError(componentType ComponentType, pluginName, link string, err error, detail interface{}, printStackTrace bool) Error {
func (ec ErrorCode) NewError(componentType ComponentType, pluginName, remediation string, err error, detail interface{}, printStackTrace bool) Error {
stack := ""
if printStackTrace {
stack = getStackTrace()
}
return Error{
Code: ec,
Message: ec.Message(),
OriginalError: err,
Detail: detail,
ComponentType: componentType,
PluginName: pluginName,
LinkToDoc: link,
Stack: stack,
code: ec,
message: ec.Message(),
originalError: err,
detail: detail,
componentType: componentType,
pluginName: pluginName,
remediation: remediation,
stack: stack,
isRootError: err == nil || !errors.As(err, &Error{}),
}
}

func newError(code ErrorCode, message string) Error {
return Error{
Code: code,
Message: message,
code: code,
message: message,
isRootError: true,
}
}

// Is returns true if the error is the same type of the target error.
func (e Error) Is(target error) bool {
t := &Error{}
if errors.As(target, t) {
return e.Code.ErrorCode() == t.Code.ErrorCode()
return e.code.ErrorCode() == t.code.ErrorCode()
}
return false
}

// ErrorCode returns the ID/Value of this Error
func (e Error) ErrorCode() ErrorCode {
return e.Code
return e.code
}

// Unwrap returns the original error
func (e Error) Unwrap() error {
return e.OriginalError
return e.originalError
}

// Error returns a human readable representation of the error.
// An Error message includes the error code, detail from nested errors, root cause and remediation, all separated by ": ".
func (e Error) Error() string {
var errStr string
if e.OriginalError != nil {
errStr += fmt.Sprintf("Original Error: (%s), ", e.OriginalError.Error())
err, details := e.getRootError()
if err.detail != nil {
details = append(details, fmt.Sprintf("%s", err.detail))
}

errStr += fmt.Sprintf("Error: %s, Code: %s", e.Message, e.Code.String())

if e.PluginName != "" {
errStr += fmt.Sprintf(", Plugin Name: %s", e.PluginName)
if err.originalError != nil {
details = append(details, err.originalError.Error())
}

if e.ComponentType != "" {
errStr += fmt.Sprintf(", Component Type: %s", e.ComponentType)
if err.remediation != "" {
details = append(details, err.remediation)
}
return fmt.Sprintf("%s: %s", err.ErrorCode().Descriptor().Value, strings.Join(details, ": "))
}

if e.LinkToDoc != "" {
errStr += fmt.Sprintf(", Documentation: %s", e.LinkToDoc)
// GetDetail returns details from all nested errors.
func (e Error) GetDetail() string {
err, details := e.getRootError()
if err.originalError != nil && err.detail != nil {
details = append(details, fmt.Sprintf("%s", err.detail))
}

if e.Detail != nil {
errStr += fmt.Sprintf(", Detail: %v", e.Detail)
}
return strings.Join(details, ": ")
}

if e.Description != "" {
errStr += fmt.Sprintf(", Description: %v", e.Description)
// GetErrorReason returns the root cause of the error.
func (e Error) GetErrorReason() string {
err, _ := e.getRootError()
if err.originalError != nil {
return err.originalError.Error()
}
return fmt.Sprintf("%s", err.detail)
}

if e.Stack != "" {
errStr += fmt.Sprintf(", Stack trace: %s", e.Stack)
}
// GetRemiation returns the remediation of the root error.
func (e Error) GetRemediation() string {
err, _ := e.getRootError()
return err.remediation
}

return errStr
func (e Error) getRootError() (err Error, details []string) {
err = e
for !err.isRootError {
if err.detail != nil {
details = append(details, fmt.Sprintf("%s", err.detail))
}
var ratifyError Error
if errors.As(err.originalError, &ratifyError) {
err = ratifyError
} else {
// break is unnecessary, but added for safety
break
}
}
return err, details
}

// WithDetail will return a new Error, based on the current one, but with
// some Detail info added
func (e Error) WithDetail(detail interface{}) Error {
e.Detail = detail
e.detail = detail
return e
}

// WithPluginName returns a new Error object with pluginName set.
func (e Error) WithPluginName(pluginName string) Error {
e.PluginName = pluginName
e.pluginName = pluginName
return e
}

// WithComponentType returns a new Error object with ComponentType set.
func (e Error) WithComponentType(componentType ComponentType) Error {
e.ComponentType = componentType
e.componentType = componentType
return e
}

// WithError returns a new Error object with original error.
func (e Error) WithError(err error) Error {
e.OriginalError = err
e.originalError = err
e.isRootError = err == nil || !errors.As(err, &Error{})
return e
}

// WithLinkToDoc returns a new Error object attached with link to documentation.
func (e Error) WithLinkToDoc(link string) Error {
e.LinkToDoc = link
// WithRemediation returns a new Error object attached with remediation.
func (e Error) WithRemediation(remediation string) Error {
e.remediation = remediation
return e
}

func (e Error) WithDescription() Error {
e.Description = e.Code.Description()
e.description = e.code.Description()
return e
}

Expand Down
Loading

0 comments on commit af1a0d8

Please sign in to comment.