Refactor domain error to use errorId (#790)

* Refactor domain error to use errorId

* Wrap accepts options and uses New internally
This commit is contained in:
Louis Ruch authored and GitHub committed 2020-11-17 18:54:23 -08:00
1 parent b8a5b9adf1
commit d30dd0ff64
8 files changed
+184 -103

No files matched your search

+34 -19
View File
@@ -8,12 +8,12 @@ import (
"github.com/lib/pq" "github.com/lib/pq"
) )
// Op represents an operation (package.function). // ErrorId is an id that is unique to the error (not the instance of the error).
// For example iam.CreateRole // The id should be generated using `make rand`, and used as the `errorid` param when calling `errors.New`
type Op string type ErrorId string
// Err provides the ability to specify a Msg, Op, Code and Wrapped error. // Err provides the ability to specify a Msg, ErrorId, Code and Wrapped error.
// Errs must have a Code and all other fields are optional. We've chosen Err // Errs must have a Code and ErrorId all other fields are optional. We've chosen Err
// over Error for the identifier to support the easy embedding of Errs. Errs // over Error for the identifier to support the easy embedding of Errs. Errs
// can be embedded without a conflict between the embedded Err and Err.Error(). // can be embedded without a conflict between the embedded Err and Err.Error().
type Err struct { type Err struct {
@@ -21,63 +21,78 @@ type Err struct {
// errorCodeInfo, which contains the error's Kind and Message // errorCodeInfo, which contains the error's Kind and Message
Code Code Code Code
// Unique ID used to identify the error being returned
ErrorId ErrorId
// Msg for the error // Msg for the error
Msg string Msg string
// Op represents the operation raising/propagating an error and is optional
Op Op
// Wrapped is the error which this Err wraps and will be nil if there's no // Wrapped is the error which this Err wraps and will be nil if there's no
// error to wrap. // error to wrap.
Wrapped error Wrapped error
} }
// New creates a new Err and supports the options of: // New creates a new Err and supports the options of:
// WithOp - allows you to specify an optional Op (operation)
// WithMsg() - allows you to specify an optional error msg, if the default // WithMsg() - allows you to specify an optional error msg, if the default
// msg for the error Code is not sufficient. // msg for the error Code is not sufficient.
// WithWrap() - allows you to specify // WithWrap() - allows you to specify
// an error to wrap // an error to wrap
func New(c Code, opt ...Option) error { func New(c Code, errorid ErrorId, opt ...Option) error {
opts := GetOpts(opt...) opts := GetOpts(opt...)
return &Err{ return &Err{
Code: c, Code: c,
Op: opts.withOp, ErrorId: errorid,
Wrapped: opts.withErrWrapped, Wrapped: opts.withErrWrapped,
Msg: opts.withErrMsg, Msg: opts.withErrMsg,
} }
} }
// Wrap creates a new Err, but preserves the Code of the original error being wrapped
func Wrap(e error, errorid ErrorId, opt ...Option) error {
var code Code
if err, ok := e.(*Err); ok {
// get code from wrapped error
code = err.Code
}
opt = append(opt, WithWrap(e))
return New(code, errorid, opt...)
}
// Convert will convert the error to a Boundary *Err (returning it as an error) // Convert will convert the error to a Boundary *Err (returning it as an error)
// and attempt to add a helpful error msg as well. If that's not possible, it // and attempt to add a helpful error msg as well. If that's not possible, it
// will return nil // will return nil
func Convert(e error) *Err { func Convert(e error, errorid ErrorId) *Err {
if e == nil { if e == nil {
return nil return nil
} }
if err, ok := e.(*Err); ok { if err, ok := e.(*Err); ok {
return err return err
} }
var pqError *pq.Error var pqError *pq.Error
if As(e, &pqError) { if As(e, &pqError) {
if pqError.Code.Class() == "23" { // class of integrity constraint violations if pqError.Code.Class() == "23" { // class of integrity constraint violations
switch pqError.Code { switch pqError.Code {
case "23505": // unique_violation case "23505": // unique_violation
return New(NotUnique, WithMsg(pqError.Detail), WithWrap(ErrNotUnique)).(*Err) return New(NotUnique, errorid, WithMsg(pqError.Detail), WithWrap(ErrNotUnique)).(*Err)
case "23502": // not_null_violation case "23502": // not_null_violation
msg := fmt.Sprintf("%s must not be empty", pqError.Column) msg := fmt.Sprintf("%s must not be empty", pqError.Column)
return New(NotNull, WithMsg(msg), WithWrap(ErrNotNull)).(*Err) return New(NotNull, errorid, WithMsg(msg), WithWrap(ErrNotNull)).(*Err)
case "23514": // check_violation case "23514": // check_violation
msg := fmt.Sprintf("%s constraint failed", pqError.Constraint) msg := fmt.Sprintf("%s constraint failed", pqError.Constraint)
return New(CheckConstraint, WithMsg(msg), WithWrap(ErrCheckConstraint)).(*Err) return New(CheckConstraint, errorid, WithMsg(msg), WithWrap(ErrCheckConstraint)).(*Err)
default: default:
return New(NotSpecificIntegrity, WithMsg(pqError.Message)).(*Err) return New(NotSpecificIntegrity, errorid, WithMsg(pqError.Message)).(*Err)
} }
} }
if pqError.Code == "42P01" { if pqError.Code == "42P01" {
return New(MissingTable, WithMsg(pqError.Message)).(*Err) return New(MissingTable, errorid, WithMsg(pqError.Message)).(*Err)
} }
} }
if errors.Is(e, ErrRecordNotFound) {
return New(RecordNotFound, errorid, WithMsg(e.Error())).(*Err)
}
// unfortunately, we can't help. // unfortunately, we can't help.
return nil return nil
} }
@@ -97,8 +112,8 @@ func (e *Err) Error() string {
return "" return ""
} }
var s strings.Builder var s strings.Builder
if e.Op != "" { if e.ErrorId != "" {
join(&s, ": ", string(e.Op)) join(&s, ": ", string(e.ErrorId))
} }
if e.Msg != "" { if e.Msg != "" {
join(&s, ": ", e.Msg) join(&s, ": ", e.Msg)
+100 -21
View File
@@ -3,6 +3,7 @@ package errors_test
import ( import (
"context" "context"
stderrors "errors" stderrors "errors"
"fmt"
"testing" "testing"
"github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db"
@@ -14,6 +15,7 @@ import (
func Test_NewError(t *testing.T) { func Test_NewError(t *testing.T) {
t.Parallel() t.Parallel()
testId := errors.ErrorId("testid")
tests := []struct { tests := []struct {
name string name string
code errors.Code code errors.Code
@@ -24,13 +26,12 @@ func Test_NewError(t *testing.T) {
name: "all-options", name: "all-options",
code: errors.InvalidParameter, code: errors.InvalidParameter,
opt: []errors.Option{ opt: []errors.Option{
errors.WithOp("alice.Bob"),
errors.WithWrap(errors.ErrRecordNotFound), errors.WithWrap(errors.ErrRecordNotFound),
errors.WithMsg("test msg"), errors.WithMsg("test msg"),
}, },
want: &errors.Err{ want: &errors.Err{
Op: "alice.Bob",
Wrapped: errors.ErrRecordNotFound, Wrapped: errors.ErrRecordNotFound,
ErrorId: testId,
Msg: "test msg", Msg: "test msg",
Code: errors.InvalidParameter, Code: errors.InvalidParameter,
}, },
@@ -39,14 +40,79 @@ func Test_NewError(t *testing.T) {
name: "no-options", name: "no-options",
opt: nil, opt: nil,
want: &errors.Err{ want: &errors.Err{
Code: errors.Unknown, ErrorId: testId,
Code: errors.Unknown,
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
err := errors.New(tt.code, tt.opt...) err := errors.New(tt.code, testId, tt.opt...)
require.Error(err)
assert.Equal(tt.want, err)
})
}
}
func Test_WrapError(t *testing.T) {
t.Parallel()
testId := errors.ErrorId("testid")
testErr := errors.New(errors.InvalidParameter, "uniqueId")
tests := []struct {
name string
opt []errors.Option
err error
want error
}{
{
name: "boundary-error",
err: testErr,
opt: []errors.Option{
errors.WithMsg("test msg"),
},
want: &errors.Err{
Wrapped: testErr,
ErrorId: testId,
Msg: "test msg",
Code: errors.InvalidParameter,
},
},
{
name: "boundary-error-no-msg",
err: testErr,
want: &errors.Err{
Wrapped: testErr,
ErrorId: testId,
Code: errors.InvalidParameter,
},
},
{
name: "std-error",
err: fmt.Errorf("std error"),
want: &errors.Err{
Wrapped: fmt.Errorf("std error"),
ErrorId: testId,
Code: errors.Unknown,
},
},
{
name: "conflicting-with-wrap",
err: testErr,
opt: []errors.Option{
errors.WithWrap(fmt.Errorf("dont wrap this error")),
},
want: &errors.Err{
Wrapped: testErr,
ErrorId: testId,
Code: errors.InvalidParameter,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := errors.Wrap(tt.err, testId, tt.opt...)
require.Error(err) require.Error(err)
assert.Equal(tt.want, err) assert.Equal(tt.want, err)
}) })
@@ -67,12 +133,12 @@ func TestError_Info(t *testing.T) {
}, },
{ {
name: "Unknown", name: "Unknown",
err: errors.New(errors.Unknown).(*errors.Err), err: errors.New(errors.Unknown, "").(*errors.Err),
want: errors.Unknown, want: errors.Unknown,
}, },
{ {
name: "InvalidParameter", name: "InvalidParameter",
err: errors.New(errors.InvalidParameter).(*errors.Err), err: errors.New(errors.InvalidParameter, "").(*errors.Err),
want: errors.InvalidParameter, want: errors.InvalidParameter,
}, },
} }
@@ -93,22 +159,27 @@ func TestError_Error(t *testing.T) {
}{ }{
{ {
name: "msg", name: "msg",
err: errors.New(errors.Unknown, errors.WithMsg("test msg")), err: errors.New(errors.Unknown, "", errors.WithMsg("test msg")),
want: "test msg: unknown: error #0", want: "test msg: unknown: error #0",
}, },
{ {
name: "code", name: "code",
err: errors.New(errors.CheckConstraint), err: errors.New(errors.CheckConstraint, ""),
want: "constraint check failed, integrity violation: error #1000", want: "constraint check failed, integrity violation: error #1000",
}, },
{ {
name: "op-msg-and-code", name: "id",
err: errors.New(errors.CheckConstraint, errors.WithOp("alice.bob"), errors.WithMsg("test msg")), err: errors.New(errors.Unknown, "uniqueId"),
want: "alice.bob: test msg: integrity violation: error #1000", want: "uniqueId: unknown, unknown: error #0",
},
{
name: "id-msg-and-code",
err: errors.New(errors.CheckConstraint, "uniqueId", errors.WithMsg("test msg")),
want: "uniqueId: test msg: integrity violation: error #1000",
}, },
{ {
name: "unknown", name: "unknown",
err: errors.New(errors.Unknown), err: errors.New(errors.Unknown, ""),
want: "unknown, unknown: error #0", want: "unknown, unknown: error #0",
}, },
} }
@@ -129,7 +200,8 @@ func TestError_Error(t *testing.T) {
func TestError_Unwrap(t *testing.T) { func TestError_Unwrap(t *testing.T) {
t.Parallel() t.Parallel()
testErr := errors.New(errors.Unknown, errors.WithMsg("test error")) testId := errors.ErrorId("testid")
testErr := errors.New(errors.Unknown, testId, errors.WithMsg("test error"))
tests := []struct { tests := []struct {
name string name string
@@ -138,8 +210,14 @@ func TestError_Unwrap(t *testing.T) {
wantIsErr error wantIsErr error
}{ }{
{ {
name: "ErrInvalidParameter", name: "ErrInvalidParameterWithWrap",
err: errors.New(errors.InvalidParameter, errors.WithWrap(errors.ErrInvalidParameter)), err: errors.New(errors.InvalidParameter, testId, errors.WithWrap(errors.ErrInvalidParameter)),
want: errors.ErrInvalidParameter,
wantIsErr: errors.ErrInvalidParameter,
},
{
name: "ErrInvalidParameterWrap",
err: errors.Wrap(errors.ErrInvalidParameter, testId),
want: errors.ErrInvalidParameter, want: errors.ErrInvalidParameter,
wantIsErr: errors.ErrInvalidParameter, wantIsErr: errors.ErrInvalidParameter,
}, },
@@ -170,6 +248,7 @@ func TestError_Unwrap(t *testing.T) {
func TestConvertError(t *testing.T) { func TestConvertError(t *testing.T) {
t.Parallel() t.Parallel()
testId := errors.ErrorId("testid")
const ( const (
createTable = ` createTable = `
create table if not exists test_table ( create table if not exists test_table (
@@ -210,13 +289,13 @@ func TestConvertError(t *testing.T) {
e: &pq.Error{ e: &pq.Error{
Code: pq.ErrorCode("23001"), Code: pq.ErrorCode("23001"),
}, },
wantErr: errors.New(errors.NotSpecificIntegrity), wantErr: errors.New(errors.NotSpecificIntegrity, testId),
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
err := errors.Convert(tt.e) err := errors.Convert(tt.e, testId)
if tt.wantErr == nil { if tt.wantErr == nil {
assert.Nil(err) assert.Nil(err)
return return
@@ -234,7 +313,7 @@ func TestConvertError(t *testing.T) {
_, err = rw.Exec(ctx, insert, []interface{}{"alice", "dup coworker", nil}) _, err = rw.Exec(ctx, insert, []interface{}{"alice", "dup coworker", nil})
require.Error(err) require.Error(err)
e := errors.Convert(err) e := errors.Convert(err, "")
require.NotNil(e) require.NotNil(e)
assert.True(errors.Is(e, errors.ErrNotUnique)) assert.True(errors.Is(e, errors.ErrNotUnique))
assert.Equal("Key (name)=(alice) already exists.: integrity violation: error #1002: \nunique constraint violation: integrity violation: error #1002", e.Error()) assert.Equal("Key (name)=(alice) already exists.: integrity violation: error #1002: \nunique constraint violation: integrity violation: error #1002", e.Error())
@@ -246,7 +325,7 @@ func TestConvertError(t *testing.T) {
_, err = rw.Exec(ctx, insert, []interface{}{"alice", nil, nil}) _, err = rw.Exec(ctx, insert, []interface{}{"alice", nil, nil})
require.Error(err) require.Error(err)
e := errors.Convert(err) e := errors.Convert(err, "")
require.NotNil(e) require.NotNil(e)
assert.True(errors.Is(e, errors.ErrNotNull)) assert.True(errors.Is(e, errors.ErrNotNull))
assert.Equal("description must not be empty: integrity violation: error #1001: \nnot null constraint violated: integrity violation: error #1001", e.Error()) assert.Equal("description must not be empty: integrity violation: error #1001: \nnot null constraint violated: integrity violation: error #1001", e.Error())
@@ -258,7 +337,7 @@ func TestConvertError(t *testing.T) {
_, err = rw.Exec(ctx, insert, []interface{}{"alice", "coworker", "one"}) _, err = rw.Exec(ctx, insert, []interface{}{"alice", "coworker", "one"})
require.Error(err) require.Error(err)
e := errors.Convert(err) e := errors.Convert(err, "")
require.NotNil(e) require.NotNil(e)
assert.True(errors.Is(e, errors.ErrCheckConstraint)) assert.True(errors.Is(e, errors.ErrCheckConstraint))
assert.Equal("test_table_five_check constraint failed: integrity violation: error #1000: \ncheck constraint violated: integrity violation: error #1000", e.Error()) assert.Equal("test_table_five_check constraint failed: integrity violation: error #1000: \ncheck constraint violated: integrity violation: error #1000", e.Error())
@@ -267,7 +346,7 @@ func TestConvertError(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
_, err := rw.Exec(ctx, missingTable, nil) _, err := rw.Exec(ctx, missingTable, nil)
require.Error(err) require.Error(err)
e := errors.Convert(err) e := errors.Convert(err, "")
require.NotNil(e) require.NotNil(e)
assert.True(errors.Match(errors.T(errors.MissingTable), e)) assert.True(errors.Match(errors.T(errors.MissingTable), e))
assert.Equal("relation \"not_a_defined_table\" does not exist: integrity violation: error #1004", e.Error()) assert.Equal("relation \"not_a_defined_table\" does not exist: integrity violation: error #1004", e.Error())
+11 -5
View File
@@ -44,7 +44,9 @@ func TestError_IsUnique(t *testing.T) {
}, },
{ {
name: "wrapped-pq-is-unique", name: "wrapped-pq-is-unique",
in: errors.New(errors.NotUnique, in: errors.New(
errors.NotUnique,
"",
errors.WithWrap(&pq.Error{ errors.WithWrap(&pq.Error{
Code: pq.ErrorCode("23505"), Code: pq.ErrorCode("23505"),
}), }),
@@ -96,12 +98,14 @@ func TestError_IsCheckConstraint(t *testing.T) {
}, },
{ {
name: "ErrCodeCheckConstraint", name: "ErrCodeCheckConstraint",
in: errors.New(errors.CheckConstraint), in: errors.New(errors.CheckConstraint, ""),
want: true, want: true,
}, },
{ {
name: "wrapped-pq-is-check-constraint", name: "wrapped-pq-is-check-constraint",
in: errors.New(errors.CheckConstraint, in: errors.New(
errors.CheckConstraint,
"",
errors.WithWrap(&pq.Error{ errors.WithWrap(&pq.Error{
Code: pq.ErrorCode("23514"), Code: pq.ErrorCode("23514"),
}), }),
@@ -160,12 +164,14 @@ func TestError_IsNotNullError(t *testing.T) {
}, },
{ {
name: "ErrCodeNotNull", name: "ErrCodeNotNull",
in: errors.New(errors.NotNull), in: errors.New(errors.NotNull, ""),
want: true, want: true,
}, },
{ {
name: "wrapped-pq-is-not-null", name: "wrapped-pq-is-not-null",
in: errors.New(errors.NotNull, in: errors.New(
errors.NotNull,
"",
errors.WithWrap(&pq.Error{ errors.WithWrap(&pq.Error{
Code: pq.ErrorCode("23502"), Code: pq.ErrorCode("23502"),
}), }),
+3 -3
View File
@@ -16,10 +16,10 @@ func T(args ...interface{}) *Template {
switch arg := a.(type) { switch arg := a.(type) {
case Code: case Code:
t.Code = arg t.Code = arg
case ErrorId:
t.ErrorId = arg
case string: case string:
t.Msg = arg t.Msg = arg
case Op:
t.Op = arg
case *Err: // order is important, this match must before "case error:" case *Err: // order is important, this match must before "case error:"
c := *arg c := *arg
t.Wrapped = &c t.Wrapped = &c
@@ -78,7 +78,7 @@ func Match(t *Template, err error) bool {
if t.Msg != "" && t.Msg != e.Msg { if t.Msg != "" && t.Msg != e.Msg {
return false return false
} }
if t.Op != "" && t.Op != e.Op { if t.ErrorId != "" && t.ErrorId != e.ErrorId {
return false return false
} }
if t.Kind != Other && t.Info().Kind != e.Info().Kind { if t.Kind != Other && t.Info().Kind != e.Info().Kind {
+21 -18
View File
@@ -10,6 +10,7 @@ import (
func TestT(t *testing.T) { func TestT(t *testing.T) {
t.Parallel() t.Parallel()
stdErr := stderrors.New("test error") stdErr := stderrors.New("test error")
testId := ErrorId("testid")
tests := []struct { tests := []struct {
name string name string
args []interface{} args []interface{}
@@ -19,7 +20,7 @@ func TestT(t *testing.T) {
name: "all fields", name: "all fields",
args: []interface{}{ args: []interface{}{
"test error msg", "test error msg",
Op("alice.Bob"), testId,
InvalidParameter, InvalidParameter,
stdErr, stdErr,
Integrity, Integrity,
@@ -27,8 +28,8 @@ func TestT(t *testing.T) {
want: &Template{ want: &Template{
Err: Err{ Err: Err{
Code: InvalidParameter, Code: InvalidParameter,
ErrorId: testId,
Msg: "test error msg", Msg: "test error msg",
Op: "alice.Bob",
Wrapped: stdErr, Wrapped: stdErr,
}, },
Kind: Integrity, Kind: Integrity,
@@ -109,6 +110,7 @@ func TestTemplate_Info(t *testing.T) {
func TestTemplate_Error(t *testing.T) { func TestTemplate_Error(t *testing.T) {
t.Parallel() t.Parallel()
stdErr := stderrors.New("test error") stdErr := stderrors.New("test error")
testId := ErrorId("testid")
tests := []struct { tests := []struct {
name string name string
template *Template template *Template
@@ -121,7 +123,7 @@ func TestTemplate_Error(t *testing.T) {
name: "all params", name: "all params",
template: T( template: T(
"test error msg", "test error msg",
Op("alice.Bob"), testId,
InvalidParameter, InvalidParameter,
stdErr, stdErr,
Integrity, Integrity,
@@ -140,6 +142,7 @@ func TestTemplate_Error(t *testing.T) {
func TestMatch(t *testing.T) { func TestMatch(t *testing.T) {
t.Parallel() t.Parallel()
stdErr := stderrors.New("test error") stdErr := stderrors.New("test error")
testId := ErrorId("testid")
tests := []struct { tests := []struct {
name string name string
template *Template template *Template
@@ -149,7 +152,7 @@ func TestMatch(t *testing.T) {
{ {
name: "nil template", name: "nil template",
template: nil, template: nil,
err: New(NotUnique, WithMsg("this thing was must be unique")), err: New(NotUnique, testId, WithMsg("this thing was must be unique")),
want: false, want: false,
}, },
{ {
@@ -163,8 +166,8 @@ func TestMatch(t *testing.T) {
template: T(Integrity), template: T(Integrity),
err: New( err: New(
NotUnique, NotUnique,
testId,
WithMsg("this thing must be unique"), WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: true, want: true,
@@ -174,8 +177,8 @@ func TestMatch(t *testing.T) {
template: T(Integrity), template: T(Integrity),
err: New( err: New(
RecordNotFound, RecordNotFound,
testId,
WithMsg("this thing is missing"), WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: false, want: false,
@@ -185,8 +188,8 @@ func TestMatch(t *testing.T) {
template: T(NotUnique), template: T(NotUnique),
err: New( err: New(
NotUnique, NotUnique,
testId,
WithMsg("this thing must be unique"), WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: true, want: true,
@@ -196,30 +199,30 @@ func TestMatch(t *testing.T) {
template: T(NotUnique), template: T(NotUnique),
err: New( err: New(
RecordNotFound, RecordNotFound,
testId,
WithMsg("this thing is missing"), WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: false, want: false,
}, },
{ {
name: "match on Op only", name: "match on Op only",
template: T(Op("alice.Bob")), template: T(ErrorId("unique")),
err: New( err: New(
NotUnique, NotUnique,
"unique",
WithMsg("this thing must be unique"), WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: true, want: true,
}, },
{ {
name: "no match on Op only", name: "no match on Op only",
template: T(Op("alice.Alice")), template: T(ErrorId("unique")),
err: New( err: New(
RecordNotFound, RecordNotFound,
testId,
WithMsg("this thing is missing"), WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: false, want: false,
@@ -231,12 +234,12 @@ func TestMatch(t *testing.T) {
Integrity, Integrity,
InvalidParameter, InvalidParameter,
ErrInvalidFieldMask, ErrInvalidFieldMask,
Op("alice.Bob"), ErrorId("unique"),
), ),
err: New( err: New(
InvalidParameter, InvalidParameter,
"unique",
WithMsg("this thing must be unique"), WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: true, want: true,
@@ -246,8 +249,8 @@ func TestMatch(t *testing.T) {
template: T(ErrInvalidFieldMask), template: T(ErrInvalidFieldMask),
err: New( err: New(
NotUnique, NotUnique,
testId,
WithMsg("this thing must be unique"), WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: true, want: true,
@@ -257,8 +260,8 @@ func TestMatch(t *testing.T) {
template: T(ErrNotUnique), template: T(ErrNotUnique),
err: New( err: New(
RecordNotFound, RecordNotFound,
testId,
WithMsg("this thing is missing"), WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask), WithWrap(ErrInvalidFieldMask),
), ),
want: false, want: false,
@@ -268,8 +271,8 @@ func TestMatch(t *testing.T) {
template: T(stdErr), template: T(stdErr),
err: New( err: New(
NotUnique, NotUnique,
testId,
WithMsg("this thing must be unique"), WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(stdErr), WithWrap(stdErr),
), ),
want: true, want: true,
@@ -279,8 +282,8 @@ func TestMatch(t *testing.T) {
template: T(stderrors.New("no match")), template: T(stderrors.New("no match")),
err: New( err: New(
RecordNotFound, RecordNotFound,
testId,
WithMsg("this thing is missing"), WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(stdErr), WithWrap(stdErr),
), ),
want: false, want: false,
-9
View File
@@ -16,7 +16,6 @@ type Option func(*Options)
type Options struct { type Options struct {
withErrWrapped error withErrWrapped error
withErrMsg string withErrMsg string
withOp Op
} }
func getDefaultOptions() Options { func getDefaultOptions() Options {
@@ -38,11 +37,3 @@ func WithMsg(msg string) Option {
o.withErrMsg = msg o.withErrMsg = msg
} }
} }
// WithOp provides an option to provide the operation that's raising/propagating
// the error.
func WithOp(op Op) Option {
return func(o *Options) {
o.withOp = op
}
}
+4 -15
View File
@@ -1,6 +1,7 @@
package errors package errors
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -31,21 +32,9 @@ func Test_getOpts(t *testing.T) {
assert.Equal(opts, testOpts) assert.Equal(opts, testOpts)
// try setting it // try setting it
opts = GetOpts(WithWrap(ErrInvalidParameter)) err := fmt.Errorf("test error")
testOpts.withErrWrapped = ErrInvalidParameter opts = GetOpts(WithWrap(err))
assert.Equal(opts, testOpts) testOpts.withErrWrapped = err
})
t.Run("WithOp", func(t *testing.T) {
assert := assert.New(t)
// test default
opts := GetOpts()
testOpts := getDefaultOptions()
testOpts.withOp = ""
assert.Equal(opts, testOpts)
// try setting it
opts = GetOpts(WithOp("alice.bob"))
testOpts.withOp = "alice.bob"
assert.Equal(opts, testOpts) assert.Equal(opts, testOpts)
}) })
} }
+11 -13
View File
@@ -1,44 +1,42 @@
package errors package errors
// Errors returned from this package may be tested against these errors // Errors returned from this package may be tested against these errors with errors.Is.
// with errors.Is. Creating new Sentinel type errors like these should be // TODO: Deprecate sentinel errors in favor of the new Err type that includes unique ErrorId and Code.
// deprecated in favor of the new Err type that includes unique Codes and a
// Matching function.
var ( var (
// ErrInvalidPublicId indicates an invalid PublicId. // ErrInvalidPublicId indicates an invalid PublicId.
ErrInvalidPublicId = New(InvalidParameter, WithMsg("invalid publicId")) ErrInvalidPublicId = New(InvalidParameter, "", WithMsg("invalid publicId"))
// ErrInvalidParameter is returned by create and update methods if // ErrInvalidParameter is returned by create and update methods if
// an attribute on a struct contains illegal or invalid values. // an attribute on a struct contains illegal or invalid values.
ErrInvalidParameter = New(InvalidParameter, WithMsg("invalid parameter")) ErrInvalidParameter = New(InvalidParameter, "", WithMsg("invalid parameter"))
// ErrInvalidFieldMask is returned by update methods if the field mask // ErrInvalidFieldMask is returned by update methods if the field mask
// contains unknown fields or fields that cannot be updated. // contains unknown fields or fields that cannot be updated.
ErrInvalidFieldMask = New(InvalidParameter, WithMsg("invalid field mask")) ErrInvalidFieldMask = New(InvalidParameter, "", WithMsg("invalid field mask"))
// ErrEmptyFieldMask is returned by update methods if the field mask is // ErrEmptyFieldMask is returned by update methods if the field mask is
// empty. // empty.
ErrEmptyFieldMask = New(InvalidParameter, WithMsg("empty field mask")) ErrEmptyFieldMask = New(InvalidParameter, "", WithMsg("empty field mask"))
// ErrNotUnique is returned by create and update methods when a write // ErrNotUnique is returned by create and update methods when a write
// to the repository resulted in a unique constraint violation. // to the repository resulted in a unique constraint violation.
ErrNotUnique = New(NotUnique, WithMsg("unique constraint violation")) ErrNotUnique = New(NotUnique, "", WithMsg("unique constraint violation"))
// ErrNotNull is returned by methods when a write to the repository resulted // ErrNotNull is returned by methods when a write to the repository resulted
// in a check constraint violation // in a check constraint violation
ErrCheckConstraint = New(CheckConstraint, WithMsg("check constraint violated")) ErrCheckConstraint = New(CheckConstraint, "", WithMsg("check constraint violated"))
// ErrNotNull is returned by methods when a write to the repository resulted // ErrNotNull is returned by methods when a write to the repository resulted
// in a not null constraint violation // in a not null constraint violation
ErrNotNull = New(NotNull, WithMsg("not null constraint violated")) ErrNotNull = New(NotNull, "", WithMsg("not null constraint violated"))
// ErrRecordNotFound returns a "record not found" error and it only occurs // ErrRecordNotFound returns a "record not found" error and it only occurs
// when attempting to read from the database into struct. // when attempting to read from the database into struct.
// When reading into a slice it won't return this error. // When reading into a slice it won't return this error.
ErrRecordNotFound = New(RecordNotFound, WithMsg("record not found")) ErrRecordNotFound = New(RecordNotFound, "", WithMsg("record not found"))
// ErrMultipleRecords is returned by update and delete methods when a // ErrMultipleRecords is returned by update and delete methods when a
// write to the repository would result in more than one record being // write to the repository would result in more than one record being
// changed resulting in the transaction being rolled back. // changed resulting in the transaction being rolled back.
ErrMultipleRecords = New(MultipleRecords, WithMsg("multiple records")) ErrMultipleRecords = New(MultipleRecords, "", WithMsg("multiple records"))
) )