diff --git a/README.md b/README.md index 3b03cf3..2892374 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ next error (if one exists). A JSON rendering of a `berr.Error` model will only e attachments that are not sensitive (e.g. `detail` attachments). 3. Additional error information should be readily available. If you would like to access metadata, it is available on -the `berr.Error` interface with the `.Metadata()` method, and will also be output with the `.FullMap()` method. +the `berr.Error` struct with the `.Metadata()` method, and will also be output with the `.FullMap()` method. ### Creating `berr.Error` Models diff --git a/attachment_detail_struct.go b/attachment_detail_struct.go deleted file mode 100644 index 5ceabd8..0000000 --- a/attachment_detail_struct.go +++ /dev/null @@ -1,24 +0,0 @@ -package berr - -const AttachmentDetailType = "detail" - -type detailAttachment struct { - value any - key string -} - -func (d detailAttachment) Key() string { - return d.key -} - -func (d detailAttachment) Value() any { - return d.value -} - -func (d detailAttachment) Type() string { - return AttachmentDetailType -} - -func (d detailAttachment) Sensitive() bool { - return false -} diff --git a/attachment_error_struct.go b/attachment_error_struct.go deleted file mode 100644 index ae275c5..0000000 --- a/attachment_error_struct.go +++ /dev/null @@ -1,24 +0,0 @@ -package berr - -const AttachmentErrorType = "error" - -type errorAttachment struct { - value error - key string -} - -func (d errorAttachment) Key() string { - return d.key -} - -func (d errorAttachment) Value() any { - return d.value -} - -func (d errorAttachment) Type() string { - return AttachmentErrorType -} - -func (d errorAttachment) Sensitive() bool { - return true -} diff --git a/attachment_interface.go b/attachment_interface.go deleted file mode 100644 index 39cc1e1..0000000 --- a/attachment_interface.go +++ /dev/null @@ -1,10 +0,0 @@ -package berr - -type Attachments []Attachment - -type Attachment interface { - Key() string - Value() any - Type() string - Sensitive() bool -} diff --git a/attachment_metadata_struct.go b/attachment_metadata_struct.go deleted file mode 100644 index 6a4030e..0000000 --- a/attachment_metadata_struct.go +++ /dev/null @@ -1,24 +0,0 @@ -package berr - -const AttachmentMetadataType = "metadata" - -type metadataAttachment struct { - value any - key string -} - -func (d metadataAttachment) Key() string { - return d.key -} - -func (d metadataAttachment) Value() any { - return d.value -} - -func (d metadataAttachment) Type() string { - return AttachmentMetadataType -} - -func (d metadataAttachment) Sensitive() bool { - return true -} diff --git a/attachment_struct.go b/attachment_struct.go new file mode 100644 index 0000000..b029c0f --- /dev/null +++ b/attachment_struct.go @@ -0,0 +1,28 @@ +package berr + +const AttachmentDetailType = "detail" +const AttachmentMetadataType = "metadata" +const AttachmentErrorType = "error" + +type Attachment struct { + attachmentType string + key string + value any + sensitive bool +} + +func (a Attachment) Key() string { + return a.key +} + +func (a Attachment) Value() any { + return a.value +} + +func (a Attachment) Type() string { + return a.attachmentType +} + +func (a Attachment) Sensitive() bool { + return a.sensitive +} diff --git a/error_interface.go b/error_interface.go deleted file mode 100644 index a992594..0000000 --- a/error_interface.go +++ /dev/null @@ -1,41 +0,0 @@ -package berr - -import ( - "strings" -) - -type Errors []Error - -func (e Errors) Error() string { - if e == nil { - return "" - } - - builder := strings.Builder{} - builder.WriteString("[") - - for idx, val := range e { - if idx > 0 { - builder.WriteString(", ") - } - - builder.WriteString(val.Error()) - } - - builder.WriteString("]") - - return builder.String() -} - -type Error interface { - error - Type() ErrorType - HTTPCode() int - Message() string - Details() map[string]any - Metadata() map[string]any - String() string - Map() map[string]any - FullMap() map[string]any - Unwrap() error -} diff --git a/error_struct.go b/error_struct.go index 9e6c487..02d0ae1 100644 --- a/error_struct.go +++ b/error_struct.go @@ -2,9 +2,10 @@ package berr import ( "fmt" + "strings" ) -func newBerr(errorType ErrorType, errorMessage string, attachments ...Attachment) *berr { +func newBerr(errorType ErrorType, errorMessage string, attachments ...*Attachment) *Error { errorDetail := make(map[string]any) errorMetadata := make(map[string]any) @@ -30,8 +31,8 @@ func newBerr(errorType ErrorType, errorMessage string, attachments ...Attachment func newBerrWithAttachments( errorType ErrorType, errorMessage string, errorDetail, errorMetadata map[string]any, next error, -) *berr { - return &berr{ +) *Error { + return &Error{ ErrType: errorType, ErrTypeString: errorType.String(), ErrMessage: errorMessage, @@ -41,7 +42,30 @@ func newBerrWithAttachments( } } -type berr struct { +type Errors []*Error + +func (e Errors) Error() string { + if e == nil { + return "" + } + + builder := strings.Builder{} + builder.WriteString("[") + + for idx, val := range e { + if idx > 0 { + builder.WriteString(", ") + } + + builder.WriteString(val.Error()) + } + + builder.WriteString("]") + + return builder.String() +} + +type Error struct { ErrDetails map[string]any `json:"details"` ErrMetadata map[string]any `json:"-"` nextError error @@ -50,11 +74,11 @@ type berr struct { ErrType ErrorType `json:"-"` } -func (e *berr) String() string { +func (e Error) String() string { return fmt.Sprintf("[%s_error] %s", e.ErrTypeString, e.ErrMessage) } -func (e *berr) Error() string { +func (e Error) Error() string { if e.nextError != nil { return fmt.Sprintf("%s: %s", e.String(), e.nextError.Error()) } @@ -62,23 +86,23 @@ func (e *berr) Error() string { return e.String() } -func (e *berr) Unwrap() error { +func (e Error) Unwrap() error { return e.nextError } -func (e *berr) Type() ErrorType { +func (e Error) Type() ErrorType { return e.ErrType } -func (e *berr) HTTPCode() int { +func (e Error) HTTPCode() int { return e.ErrType.HTTPCode() } -func (e *berr) Message() string { +func (e Error) Message() string { return e.ErrMessage } -func (e *berr) Details() map[string]any { +func (e Error) Details() map[string]any { if e.ErrDetails != nil && len(e.ErrDetails) > 0 { detailCopy := make(map[string]any) for k, v := range e.ErrDetails { @@ -91,7 +115,7 @@ func (e *berr) Details() map[string]any { return nil } -func (e *berr) Metadata() map[string]any { +func (e Error) Metadata() map[string]any { if e.ErrMetadata != nil && len(e.ErrMetadata) > 0 { metadataCopy := make(map[string]any) for k, v := range e.ErrMetadata { @@ -104,7 +128,7 @@ func (e *berr) Metadata() map[string]any { return nil } -func (e *berr) Map() map[string]any { +func (e Error) Map() map[string]any { return map[string]any{ "error_type": e.Type().String(), "message": e.Message(), @@ -112,7 +136,7 @@ func (e *berr) Map() map[string]any { } } -func (e *berr) FullMap() map[string]any { +func (e Error) FullMap() map[string]any { return map[string]any{ "error_type": e.Type().String(), "message": e.Message(), diff --git a/error_test.go b/error_test.go index 78a218a..6bed3d3 100644 --- a/error_test.go +++ b/error_test.go @@ -26,7 +26,7 @@ func TestErrorStructThroughInterface(t *testing.T) { err := berr.Application(errorMessage, metadataAttachment, detailAttachment, errorAttachment) if err == nil { - t.Fatalf("unexpected nil berr.Error\n") + t.Errorf("unexpected nil error\n") } // -- Test err.Message() value -- @@ -179,6 +179,10 @@ func TestNotFoundError(t *testing.T) { testErrorFunc(t, berr.NotFoundErrorType, berr.NotFound, "resource not found") } +func TestConflictError(t *testing.T) { + testErrorFunc(t, berr.ConflictErrorType, berr.Conflict, "resource conflict detected") +} + func TestAuthorizationError(t *testing.T) { testErrorFunc(t, berr.AuthorizationErrorType, berr.Authorization, "action not authorized") } @@ -227,7 +231,7 @@ func TestErrorsFormatting(t *testing.T) { func testErrorFunc( t *testing.T, errorType berr.ErrorType, - errorFunc func(message string, attachments ...berr.Attachment) berr.Error, + errorFunc func(message string, attachments ...*berr.Attachment) *berr.Error, errorMessage string, ) { t.Helper() @@ -242,10 +246,6 @@ func testErrorFunc( err := errorFunc(errorMessage, detailAttachment) - if err == nil { - t.Fatalf("unexpected nil berr.Error\n") - } - if err.Type() != expectErrorType { t.Errorf("unexpected error type (expected '%s') found: %s\n", expectErrorType, err.Type()) } diff --git a/error_type_enum.go b/error_type_enum.go index c9ec4f0..3ee2860 100644 --- a/error_type_enum.go +++ b/error_type_enum.go @@ -10,6 +10,7 @@ const ( AuthenticationErrorType AuthorizationErrorType NotFoundErrorType + ConflictErrorType ValueMissingErrorType ValueInvalidErrorType UnimplementedErrorType @@ -23,6 +24,7 @@ var errorTypeStringMap = map[ErrorType]string{ AuthenticationErrorType: "authentication", AuthorizationErrorType: "authorization", NotFoundErrorType: "not_found", + ConflictErrorType: "conflict", ValueInvalidErrorType: "value_invalid", ValueMissingErrorType: "value_missing", UnimplementedErrorType: "unimplemented", @@ -36,6 +38,7 @@ var errorTypeHTTPCodeMap = map[ErrorType]int{ AuthenticationErrorType: 401, AuthorizationErrorType: 403, NotFoundErrorType: 404, + ConflictErrorType: 409, ValueInvalidErrorType: 422, ValueMissingErrorType: 422, UnimplementedErrorType: 501, diff --git a/functions.go b/functions.go index 606d5e7..2403ce3 100644 --- a/functions.go +++ b/functions.go @@ -1,67 +1,71 @@ package berr -func FromError(err error) (Error, bool) { - berrError, okay := err.(Error) +func FromError(err error) (*Error, bool) { + berrError, okay := err.(*Error) return berrError, okay } -func New(errorType ErrorType, message string, attachments ...Attachment) Error { +func New(errorType ErrorType, message string, attachments ...*Attachment) *Error { return newBerr(errorType, message, attachments...) } -func Application(message string, attachments ...Attachment) Error { +func Application(message string, attachments ...*Attachment) *Error { return New(ApplicationErrorType, message, attachments...) } -func ValueInvalid(message string, attachments ...Attachment) Error { +func ValueInvalid(message string, attachments ...*Attachment) *Error { return New(ValueInvalidErrorType, message, attachments...) } -func ValueMissing(message string, attachments ...Attachment) Error { +func ValueMissing(message string, attachments ...*Attachment) *Error { return New(ValueMissingErrorType, message, attachments...) } -func Authorization(message string, attachments ...Attachment) Error { +func Authorization(message string, attachments ...*Attachment) *Error { return New(AuthorizationErrorType, message, attachments...) } -func Authentication(message string, attachments ...Attachment) Error { +func Authentication(message string, attachments ...*Attachment) *Error { return New(AuthenticationErrorType, message, attachments...) } -func NotFound(message string, attachments ...Attachment) Error { +func NotFound(message string, attachments ...*Attachment) *Error { return New(NotFoundErrorType, message, attachments...) } -func Unimplemented(message string, attachments ...Attachment) Error { +func Unimplemented(message string, attachments ...*Attachment) *Error { return New(UnimplementedErrorType, message, attachments...) } -func Timeout(message string, attachments ...Attachment) Error { +func Conflict(message string, attachments ...*Attachment) *Error { + return New(ConflictErrorType, message, attachments...) +} + +func Timeout(message string, attachments ...*Attachment) *Error { return New(TimeoutErrorType, message, attachments...) } -func Detail(key string, value any) Attachment { - return detailAttachment{key: key, value: value} +func Detail(key string, value any) *Attachment { + return &Attachment{attachmentType: AttachmentDetailType, key: key, value: value} } -func D(key string, value any) Attachment { +func D(key string, value any) *Attachment { return Detail(key, value) } -func Metadata(key string, value any) Attachment { - return metadataAttachment{key: key, value: value} +func Metadata(key string, value any) *Attachment { + return &Attachment{attachmentType: AttachmentMetadataType, key: key, value: value, sensitive: true} } -func M(key string, value any) Attachment { +func M(key string, value any) *Attachment { return Metadata(key, value) } -func Err(value error) Attachment { - return errorAttachment{key: "error", value: value} +func Err(value error) *Attachment { + return &Attachment{attachmentType: AttachmentErrorType, key: "error", value: value, sensitive: true} } -func E(value error) Attachment { +func E(value error) *Attachment { return Err(value) }