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:
1 parent
b8a5b9adf1
commit
d30dd0ff64
8 files changed
+183
-102
No files matched your search
+34
-19
@@ -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)
|
||||||
|
|||||||
@@ -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,6 +40,7 @@ func Test_NewError(t *testing.T) {
|
|||||||
name: "no-options",
|
name: "no-options",
|
||||||
opt: nil,
|
opt: nil,
|
||||||
want: &errors.Err{
|
want: &errors.Err{
|
||||||
|
ErrorId: testId,
|
||||||
Code: errors.Unknown,
|
Code: errors.Unknown,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -46,7 +48,71 @@ func Test_NewError(t *testing.T) {
|
|||||||
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())
|
||||||
|
|||||||
@@ -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"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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"))
|
||||||
)
|
)
|
||||||
Reference in new issue
Block a user