From 2303a22607321bbac3b01fea49181d1d94e056ec Mon Sep 17 00:00:00 2001 From: Rodrigo Fonseca Date: Fri, 23 May 2025 17:55:55 -0300 Subject: [PATCH] feat: type key-values only Allowing for arbitrary lists of key-values was causing unintentional panicking when the developer mistakes the type of a value. E.g. using an error code with type string instead of errors.Code. By Allowing only typed values to be used we eliminate one potential panic. --- README.md | 31 ++++++++++++++++++++++++++++++- op_test.go | 2 +- with.go | 29 +---------------------------- with_test.go | 17 ++++++----------- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index ad4e2b4..8e3f76d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ # errors -A golang package to handle errors. + +A golang package to create useful errors. + +## Using the `errors` package + +Import the package: + +``` go +import ( + "github.com/arquivei/errors" +) +``` + +Use `errors.With()`: + +``` go +func doStuff() error { + const op = errors.Op("doStuff") + + err := fmt.Errorf("some error") + + return errors.With(err, + errors.SeverityRuntime, op, + errors.Code("RUNTIME_ERROR"), + errors.KV("context1", "value1"), + errors.KV("context2", "value2"), + ) +} +``` + diff --git a/op_test.go b/op_test.go index f1ce47e..aa3c055 100644 --- a/op_test.go +++ b/op_test.go @@ -16,7 +16,7 @@ func TestGetOpStack(t *testing.T) { err = errors.With(err, errors.Op("op 1")) err = errors.With(err, errors.Op("op 2")) const op3 errors.Op = "op 3" - err = errors.With(err, op3.Key(), op3.Value()) + err = errors.With(err, op3) op4 := errors.Op("op 4") err = errors.With(err, op4) diff --git a/with.go b/with.go index 541bb8c..22b0b04 100644 --- a/with.go +++ b/with.go @@ -4,44 +4,17 @@ import ( "reflect" ) -func With(err error, args ...any) error { +func With(err error, keyvalues ...KeyValuer) error { if err == nil { return nil } - keyvalues := argsToKeyValues(args) for _, keyval := range keyvalues { if !reflect.TypeOf(keyval.Key()).Comparable() { panic("key is not comparable") } - // err = Error{err: err, KeyValue: KeyValue{key: keyval.Key(), value: keyval.Value()}} err = Error{err: err, keyval: keyval} } return err } - -func argsToKeyValues(args []any) []KeyValuer { - var ( - keyvalue KeyValuer - keyvalues []KeyValuer - ) - for len(args) > 0 { - keyvalue, args = cutKV(args) - keyvalues = append(keyvalues, keyvalue) - } - - return keyvalues -} - -func cutKV(args []any) (KeyValuer, []any) { - switch head := args[0].(type) { - case KeyValuer: - return head, args[1:] - default: - if len(args) < 2 { - panic("invalid number of args") - } - return KV(head, args[1]), args[2:] - } -} diff --git a/with_test.go b/with_test.go index f986ef3..370262b 100644 --- a/with_test.go +++ b/with_test.go @@ -15,21 +15,16 @@ func TestWithNoKeyValues(t *testing.T) { } } -func TestWith(t *testing.T) { - err := errors.With(nil, "key 1", "value 1") +func TestWithNoError(t *testing.T) { + err := errors.With(nil) if err != nil { t.Error("expected nil, got", err) } +} - rootErr := errors.New("some error") - err = errors.With(rootErr) - - if err != rootErr { - t.Error("expected", rootErr, "got", err) - } - - err = errors.With(rootErr, "key 1", "value 1") - +func TestWith(t *testing.T) { + // receiving a single keyvalue will return a new error + err := errors.With(errors.New("some error"), errors.KV("key", "value")) if _, ok := err.(errors.Error); !ok { t.Error("expected errors.Error, got", err) }