Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor error message format #1675

Merged
merged 5 commits into from
Aug 9, 2024
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
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
137 changes: 82 additions & 55 deletions errors/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,16 @@

// 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 @@ -150,9 +151,9 @@
return newError(ec, ec.Message()).WithComponentType(componentType)
}

// 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)
// WithRemediation returns a new Error object with remediation.
func (ec ErrorCode) WithRemediation(link string) Error {
return newError(ec, ec.Message()).WithRemediation(link)
}

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

// 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)
binbin-li marked this conversation as resolved.
Show resolved Hide resolved
}
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

Check warning on line 267 in errors/types.go

View check run for this annotation

Codecov / codecov/patch

errors/types.go#L267

Added line #L267 was not covered by tests
}
}
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
Loading