Skip to content
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: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
vendor/

# Go workspace file
go.work
go.work.sum

# env file
.env
# Coverage file
.cover
23 changes: 23 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package errors

import "testing"

func TestErrorError(t *testing.T) {
rootErr := New("root error")

t.Run("Unwrap", func(t *testing.T) {
var err error = Error{err: rootErr}
if unwrapped := err.(Error).Unwrap(); unwrapped != rootErr {
t.Errorf("expected %v, got %v", rootErr, unwrapped)
}
})

t.Run("Error", func(t *testing.T) {
var err error = Error{err: rootErr}
expected := "root error"
if err.Error() != expected {
t.Errorf("expected %q, got %q", expected, err.Error())
}
})

}
2 changes: 1 addition & 1 deletion formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func writeKV(sb *strings.Builder, kvs []KeyValuer) {
}
sb.WriteString(stringify(kv.Key()))
sb.WriteString(": ")
sb.WriteString(kv.String())
sb.WriteString(stringify(kv.Value()))

shouldAddComma = true
}
Expand Down
49 changes: 49 additions & 0 deletions formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ import (
"github.com/arquivei/errors"
)

func TestFormatter(t *testing.T) {
formatter := errors.Formatter(func(err error) string {
return "formatted error: " + err.Error()
})

if formatter == nil {
t.Error("expected non-nil formatter")
}
if formatter.String() != "<ErrorFormatter>" {
t.Errorf("expected '<ErrorFormatter>', got '%s'", formatter.String())
}
if formatter.Key() == nil {
t.Error("expected non-nil key")
}
if formatter.Value() == nil {
t.Error("expected non-nil value")
}
}

func TestGetFormatter(t *testing.T) {
err := errors.New("some error")
if errors.GetFormatter(err) == nil {
Expand Down Expand Up @@ -35,3 +54,33 @@ func TestGetFormatter(t *testing.T) {
t.Error("expected custom formatter, got", err.Error())
}
}

func TestRootErrorFormatter(t *testing.T) {
rootErr := errors.New("root error")
err := errors.With(
rootErr,
errors.RootErrorFormatter,
errors.SeverityInput,
errors.Code("BAD_REQUEST"),
errors.KV("key1", "value1"),
)

if err.Error() != "root error" {
t.Errorf("expected 'root error', got '%s'", err.Error())
}
}
func TestRootErrorKVFormatter(t *testing.T) {
rootErr := errors.New("root error")
err := errors.With(
rootErr,
errors.RootErrorKVFormatter,
errors.SeverityInput,
errors.Code("BAD_REQUEST"),
errors.KV("key1", "value1"),
)

expected := "root error {key1: value1}"
if err.Error() != expected {
t.Errorf("expected '%s', got '%s'", expected, err.Error())
}
}
11 changes: 1 addition & 10 deletions kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
type KeyValuer interface {
Key() any
Value() any
String() string
}

type KeyValue struct {
Expand All @@ -28,10 +27,6 @@ func (kv KeyValue) Value() any {
return kv.value
}

func (kv KeyValue) String() string {
return stringify(kv.value)
}

// KV is a constructor for KeyValuer types.
func KV(key any, value any) KeyValuer {
return KeyValue{
Expand All @@ -46,7 +41,7 @@ func KV(key any, value any) KeyValuer {
// NOTE: Extracted from the context package.
func stringify(v any) string {
switch s := v.(type) {
case stringer:
case fmt.Stringer:
return s.String()
case string:
return s
Expand All @@ -57,7 +52,3 @@ func stringify(v any) string {
}
return fmt.Sprintf("%v", v)
}

type stringer interface {
String() string
}
33 changes: 33 additions & 0 deletions kv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package errors

import "testing"

type stringer struct{}

func (s stringer) String() string {
return "stringer"
}

func Test_stringify(t *testing.T) {
tests := []struct {
name string
input any
expected string
}{
{"nil", nil, "<nil>"},
{"string", "test", "test"},
{"int", 42, "42"},
{"float64", 3.14, "3.14"},
{"bool", true, "true"},
{"stringer", stringer{}, "stringer"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := stringify(tt.input)
if result != tt.expected {
t.Errorf("expected %q, got %q", tt.expected, result)
}
})
}
}
12 changes: 12 additions & 0 deletions std.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,38 @@ import (
"fmt"
)

// Unwrap returns the next error in the chain, if any.
// This calls the standard library's errors.Unwrap function
func Unwrap(err error) error {
return errors.Unwrap(err)
}

// Is checks if the target error is present in the error chain.
// This calls the standard library's errors.Is function.
func Is(err, target error) bool {
return errors.Is(err, target)
}

// As checks if the error can be cast to the target type.
// This calls the standard library's errors.As function.
func As(err error, target any) bool {
return errors.As(err, target)
}

// New creates a new error with the given string message.
// This calls the standard library's errors.New function.
func New(str string) error {
return errors.New(str)
}

// Errorf formats an error message according to a format specifier and returns it as an error.
// This calls the standard library's fmt.Errorf function.
func Errorf(format string, args ...any) error {
return fmt.Errorf(format, args...)
}

// Join combines multiple errors into a single error.
// This calls the standard library's errors.Join function.
func Join(errs ...error) error {
return errors.Join(errs...)
}
75 changes: 75 additions & 0 deletions std_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package errors

import "testing"

func TestUnwrap(t *testing.T) {
rootErr := New("root error")
wrappedErr := With(rootErr)

if _, ok := wrappedErr.(Error); !ok {
t.Fatal("expected wrappedErr to be of type Error")
}

if unwrapped := Unwrap(wrappedErr); unwrapped != rootErr {
t.Errorf("expected %v, got %v", rootErr, unwrapped)
}
}

func TestIs(t *testing.T) {
rootErr := New("root error")
err := With(rootErr, KV("key", "value"))
err = With(err, KV("key2", "value2"))

if _, ok := err.(Error); !ok {
t.Fatal("expected wrappedErr to be of type Error")
}

if !Is(err, rootErr) {
t.Errorf("expected error to match rootErr, got %v", err)
}
}

func TestAs(t *testing.T) {
rootErr := New("root error")
err := With(rootErr, KV("key", "value"))

var e Error
if !As(err, &e) {
t.Fatal("expected error to be of type Error")
}
}

func TestNew(t *testing.T) {
err := New("test error")
if err == nil {
t.Fatal("expected non-nil error, got nil")
}

if err.Error() != "test error" {
t.Errorf("expected 'test error', got %s", err.Error())
}
}

func TestErrorf(t *testing.T) {
err := Errorf("test error: %s", "details")
if err == nil {
t.Fatal("expected non-nil error, got nil")
}

if err.Error() != "test error: details" {
t.Errorf("expected 'test error: details', got %s", err.Error())
}
}

func TestJoin(t *testing.T) {
err1 := New("first error")
err2 := New("second error")

err := Join(err1, err2)
if err == nil {
t.Fatal("expected non-nil error, got nil")
}
if err.Error() != "first error\nsecond error" {
t.Errorf("expected 'first error\nsecond error', got %s", err.Error())
}
}
18 changes: 14 additions & 4 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func Value(err error, key any) any {
for ; err != nil; err = errors.Unwrap(err) {
if e, ok := err.(Error); ok {
if e.keyval.Key() == key {
if e.keyval != nil && e.keyval.Key() == key {
return e.keyval.Value()
}
}
Expand All @@ -33,7 +33,7 @@ func Values(err error, key any) []any {
var e Error
for ; err != nil; err = errors.Unwrap(err) {
if errors.As(err, &e) {
if e.keyval.Key() == key {
if e.keyval != nil && e.keyval.Key() == key {
values = append(values, e.keyval.Value())
}
}
Expand All @@ -50,13 +50,11 @@ func ValuesT[T any](err error, key any) []T {
if len(values) == 0 {
return nil
}

tValues := make([]T, 0, len(values))
for _, v := range values {
if v == nil {
continue
}

if t, ok := v.(T); ok {
tValues = append(tValues, t)
}
Expand All @@ -74,6 +72,9 @@ func ValueAllSlice(err error) []KeyValuer {

for ; err != nil; err = errors.Unwrap(err) {
if e, ok := err.(Error); ok {
if e.keyval == nil {
continue
}
if isBuiltInKeyValuer(e.keyval.Key()) {
continue
}
Expand All @@ -93,6 +94,9 @@ func ValuesMapOf(err error, keyType any) map[any][]any {
m := make(map[any][]any)
for ; err != nil; err = errors.Unwrap(err) {
if e, ok := err.(Error); ok {
if e.keyval == nil {
continue
}
if reflect.TypeOf(e.keyval.Key()) == reflect.TypeOf(keyType) {
m[e.keyval.Key()] = append(m[e.keyval.Key()], e.keyval.Value())
}
Expand All @@ -109,6 +113,9 @@ func ValueMap(err error) map[any]any {
m := make(map[any]any)
for ; err != nil; err = errors.Unwrap(err) {
if e, ok := err.(Error); ok {
if e.keyval == nil {
continue
}
if isBuiltInKeyValuer(e.keyval.Key()) {
continue
}
Expand All @@ -127,6 +134,9 @@ func ValueMapOf(err error, keyType any) map[any]any {
m := make(map[any]any)
for ; err != nil; err = errors.Unwrap(err) {
if e, ok := err.(Error); ok {
if e.keyval == nil {
continue
}
if reflect.TypeOf(e.keyval.Key()) == reflect.TypeOf(keyType) {
if _, ok := m[e.keyval.Key()]; !ok {
m[e.keyval.Key()] = e.keyval.Value()
Expand Down
Loading
Loading