Refactor host_repo -> domain errors (#786)

* Refactor domain errors: New -> E, create helper New and Wrap
* Update host repo to use domain errors
This commit is contained in:
Louis Ruch authored and GitHub committed 2020-12-09 10:11:38 -08:00
1 parent aa4157639c
commit ec0020de90
33 files changed
+996 -374

No files matched your search

+7 -3
View File
@@ -21,13 +21,17 @@ const (
Unknown Code = 0 // Unknown will be equal to a zero value for Codes
// General function errors are reserved Codes 100-999
InvalidParameter Code = 100 // InvalidParameter represents and invalid parameter for an operation.
InvalidParameter Code = 100 // InvalidParameter represents an invalid parameter for an operation.
InvalidAddress Code = 101 // InvalidAddress represents an invalid host address for an operation
InvalidPublicId Code = 102 // InvalidPublicId represents an invalid public Id for an operation
InvalidFieldMask Code = 103 // InvalidFieldMask represents an invalid field mast for an operation
EmptyFieldMask Code = 104 // EmptyFieldMask represents an empty field mask for an operation
// DB errors are resevered Codes from 1000-1999
// DB errors are reserved Codes from 1000-1999
CheckConstraint Code = 1000 // CheckConstraint represents a check constraint error
NotNull Code = 1001 // NotNull represents a value must not be null error
NotUnique Code = 1002 // NotUnique represents a value must be unique error
NotSpecificIntegrity Code = 1003 // NotSpecificIntegrity represents an integrity error that has no specificy domain error code
NotSpecificIntegrity Code = 1003 // NotSpecificIntegrity represents an integrity error that has no specific domain error code
MissingTable Code = 1004 // Missing table represents an undefined table error
RecordNotFound Code = 1100 // RecordNotFound represents that a record/row was not found matching the criteria
MultipleRecords Code = 1101 // MultipleRecords represents that multiple records/rows were found matching the criteria
+20
View File
@@ -32,6 +32,26 @@ func TestCode_Both_String_Info(t *testing.T) {
c: InvalidParameter,
want: InvalidParameter,
},
{
name: "InvalidAddress",
c: InvalidAddress,
want: InvalidAddress,
},
{
name: "InvalidPublicId",
c: InvalidPublicId,
want: InvalidPublicId,
},
{
name: "InvalidFieldMask",
c: InvalidFieldMask,
want: InvalidFieldMask,
},
{
name: "EmptyFieldMask",
c: EmptyFieldMask,
want: EmptyFieldMask,
},
{
name: "CheckConstraint",
c: CheckConstraint,
+89 -19
View File
@@ -13,9 +13,8 @@ import (
type Op string
// Err provides the ability to specify a Msg, Op, Code and Wrapped error.
// Errs must have a Code and all other fields are optional. We've chosen Err
// 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().
// We've chosen Err 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().
type Err struct {
// Code is the error's code, which can be used to get the error's
// errorCodeInfo, which contains the error's Kind and Message
@@ -32,22 +31,85 @@ type Err struct {
Wrapped error
}
// 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
// E creates a new Err with provided code 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
// msg for the error Code is not sufficient.
// WithWrap() - allows you to specify
// an error to wrap
func New(c Code, opt ...Option) error {
//
// * WithWrap() - allows you to specify an error to wrap.
// If the wrapped error is a boundary domain error, the wrapped error code
// will be used as the returned error's code.
//
// * WithCode() - allows you to specify an optional Code, this code will be prioritized
// over a code used from WithWrap().
func E(opt ...Option) error {
opts := GetOpts(opt...)
var code Code
// check if options includes a wrapped error to take code from
var err *Err
if As(opts.withErrWrapped, &err) {
code = err.Code
}
// if options include withCode prioritize using that code
// even if one was set via wrapped error above
if opts.withCode != Unknown {
code = opts.withCode
}
return &Err{
Code: c,
Code: code,
Op: opts.withOp,
Wrapped: opts.withErrWrapped,
Msg: opts.withErrMsg,
}
}
// New creates a new Err with provided code, op and msg
// It supports the options of:
//
// * WithWrap() - allows you to specify an error to wrap
func New(c Code, op Op, msg string, opt ...Option) error {
if c != Unknown {
opt = append(opt, WithCode(c))
}
if op != "" {
opt = append(opt, WithOp(op))
}
if msg != "" {
opt = append(opt, WithMsg(msg))
}
return E(opt...)
}
// Wrap creates a new Err from the provided err and op,
// preserving the code from the originating error.
// It supports the options of:
//
// * WithMsg() - allows you to specify an optional error msg, if the default
// msg for the error Code is not sufficient.
func Wrap(e error, op Op, opt ...Option) error {
if op != "" {
opt = append(opt, WithOp(op))
}
if e != nil {
// TODO: once db package has been refactored to only return domain errors,
// this convert can be removed
err := Convert(e)
if err != nil {
// wrapped converted error
e = err
}
opt = append(opt, WithWrap(e))
}
return E(opt...)
}
// 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
// will return nil
@@ -55,7 +117,8 @@ func Convert(e error) *Err {
if e == nil {
return nil
}
if err, ok := e.(*Err); ok {
var err *Err
if As(e, &err) {
return err
}
var pqError *pq.Error
@@ -63,19 +126,19 @@ func Convert(e error) *Err {
if pqError.Code.Class() == "23" { // class of integrity constraint violations
switch pqError.Code {
case "23505": // unique_violation
return New(NotUnique, WithMsg(pqError.Detail), WithWrap(ErrNotUnique)).(*Err)
return E(WithMsg(pqError.Detail), WithWrap(ErrNotUnique)).(*Err)
case "23502": // not_null_violation
msg := fmt.Sprintf("%s must not be empty", pqError.Column)
return New(NotNull, WithMsg(msg), WithWrap(ErrNotNull)).(*Err)
return E(WithMsg(msg), WithWrap(ErrNotNull)).(*Err)
case "23514": // check_violation
msg := fmt.Sprintf("%s constraint failed", pqError.Constraint)
return New(CheckConstraint, WithMsg(msg), WithWrap(ErrCheckConstraint)).(*Err)
return E(WithMsg(msg), WithWrap(ErrCheckConstraint)).(*Err)
default:
return New(NotSpecificIntegrity, WithMsg(pqError.Message)).(*Err)
return E(WithCode(NotSpecificIntegrity), WithMsg(pqError.Message)).(*Err)
}
}
if pqError.Code == "42P01" {
return New(MissingTable, WithMsg(pqError.Message)).(*Err)
return E(WithCode(MissingTable), WithMsg(pqError.Message)).(*Err)
}
}
// unfortunately, we can't help.
@@ -104,18 +167,25 @@ func (e *Err) Error() string {
join(&s, ": ", e.Msg)
}
if info, ok := errorCodeInfo[e.Code]; ok {
var skipInfo bool
var wrapped *Err
if As(e.Wrapped, &wrapped) {
// if wrapped error code is the same as this error, don't print redundant info
skipInfo = wrapped.Code == e.Code
}
if info, ok := errorCodeInfo[e.Code]; ok && !skipInfo {
if e.Msg == "" {
join(&s, ": ", info.Message) // provide a default.
join(&s, ", ", info.Kind.String())
} else {
join(&s, ": ", info.Kind.String())
}
join(&s, ": ", fmt.Sprintf("error #%d", e.Code))
}
join(&s, ": ", fmt.Sprintf("error #%d", e.Code))
if e.Wrapped != nil {
join(&s, ": \n", e.Wrapped.Error())
join(&s, ": ", e.Wrapped.Error())
}
return s.String()
}
+231 -16
View File
@@ -3,6 +3,7 @@ package errors_test
import (
"context"
stderrors "errors"
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/db"
@@ -12,18 +13,17 @@ import (
"github.com/stretchr/testify/require"
)
func Test_NewError(t *testing.T) {
func Test_ErrorE(t *testing.T) {
t.Parallel()
tests := []struct {
name string
code errors.Code
opt []errors.Option
want error
}{
{
name: "all-options",
code: errors.InvalidParameter,
opt: []errors.Option{
errors.WithCode(errors.InvalidParameter),
errors.WithOp("alice.Bob"),
errors.WithWrap(errors.ErrRecordNotFound),
errors.WithMsg("test msg"),
@@ -42,11 +42,204 @@ func Test_NewError(t *testing.T) {
Code: errors.Unknown,
},
},
{
name: "withCode",
opt: []errors.Option{
errors.WithCode(errors.RecordNotFound),
},
want: &errors.Err{
Code: errors.RecordNotFound,
},
},
{
name: "uses-wrapped-code",
opt: []errors.Option{
errors.WithWrap(errors.ErrRecordNotFound),
},
want: &errors.Err{
Code: errors.RecordNotFound,
Wrapped: errors.ErrRecordNotFound,
},
},
{
name: "conflicting-withCode-withWrap",
opt: []errors.Option{
errors.WithCode(errors.InvalidFieldMask),
errors.WithWrap(errors.ErrRecordNotFound),
},
want: &errors.Err{
Code: errors.InvalidFieldMask,
Wrapped: errors.ErrRecordNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := errors.New(tt.code, tt.opt...)
err := errors.E(tt.opt...)
require.Error(err)
assert.Equal(tt.want, err)
})
}
}
func Test_NewError(t *testing.T) {
t.Parallel()
tests := []struct {
name string
code errors.Code
op errors.Op
msg string
opt []errors.Option
want error
}{
{
name: "all-options",
code: errors.InvalidParameter,
op: "alice.Bob",
msg: "test msg",
opt: []errors.Option{
errors.WithWrap(errors.ErrRecordNotFound),
},
want: &errors.Err{
Op: "alice.Bob",
Wrapped: errors.ErrRecordNotFound,
Msg: "test msg",
Code: errors.InvalidParameter,
},
},
{
name: "no-options",
opt: nil,
want: &errors.Err{
Code: errors.Unknown,
},
},
{
name: "conflicting-op",
op: "alice.Bob",
opt: []errors.Option{
errors.WithOp("bab.Op"),
},
want: &errors.Err{
Op: "alice.Bob",
Code: errors.Unknown,
},
},
{
name: "conflicting-msg",
msg: "test msg",
opt: []errors.Option{
errors.WithMsg("dont use this message"),
},
want: &errors.Err{
Msg: "test msg",
Code: errors.Unknown,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := errors.New(tt.code, tt.op, tt.msg, tt.opt...)
require.Error(err)
assert.Equal(tt.want, err)
})
}
}
func Test_WrapError(t *testing.T) {
t.Parallel()
testErr := errors.E(errors.WithCode(errors.InvalidParameter), errors.WithOp("alice.Bob"), errors.WithMsg("test msg"))
tests := []struct {
name string
opt []errors.Option
err error
op errors.Op
want error
}{
{
name: "boundary-error",
err: testErr,
op: "alice.Bob",
opt: []errors.Option{
errors.WithMsg("test msg"),
},
want: &errors.Err{
Wrapped: testErr,
Op: "alice.Bob",
Msg: "test msg",
Code: errors.InvalidParameter,
},
},
{
name: "boundary-error-no-op",
err: testErr,
opt: []errors.Option{
errors.WithMsg("test msg"),
},
want: &errors.Err{
Wrapped: testErr,
Msg: "test msg",
Code: errors.InvalidParameter,
},
},
{
name: "boundary-error-no-options",
err: testErr,
want: &errors.Err{
Wrapped: testErr,
Code: errors.InvalidParameter,
},
},
{
name: "std-error",
err: fmt.Errorf("std error"),
want: &errors.Err{
Wrapped: fmt.Errorf("std error"),
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,
Code: errors.InvalidParameter,
},
},
{
name: "conflicting-with-op",
err: testErr,
op: "alice.Bob",
opt: []errors.Option{
errors.WithOp("bad.Op"),
},
want: &errors.Err{
Wrapped: testErr,
Op: "alice.Bob",
Code: errors.InvalidParameter,
},
},
{
name: "NotSpecificIntegrity",
err: &pq.Error{
Code: pq.ErrorCode("23001"),
Message: "test msg",
},
want: &errors.Err{
Wrapped: errors.E(errors.WithCode(errors.NotSpecificIntegrity), errors.WithMsg("test msg")),
Code: errors.NotSpecificIntegrity,
},
},
}
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, tt.op, tt.opt...)
require.Error(err)
assert.Equal(tt.want, err)
})
@@ -67,12 +260,12 @@ func TestError_Info(t *testing.T) {
},
{
name: "Unknown",
err: errors.New(errors.Unknown).(*errors.Err),
err: errors.E().(*errors.Err),
want: errors.Unknown,
},
{
name: "InvalidParameter",
err: errors.New(errors.InvalidParameter).(*errors.Err),
err: errors.E(errors.WithCode(errors.InvalidParameter)).(*errors.Err),
want: errors.InvalidParameter,
},
}
@@ -93,24 +286,39 @@ func TestError_Error(t *testing.T) {
}{
{
name: "msg",
err: errors.New(errors.Unknown, errors.WithMsg("test msg")),
err: errors.E(errors.WithMsg("test msg")),
want: "test msg: unknown: error #0",
},
{
name: "code",
err: errors.New(errors.CheckConstraint),
err: errors.E(errors.WithCode(errors.CheckConstraint)),
want: "constraint check failed, integrity violation: error #1000",
},
{
name: "op-msg-and-code",
err: errors.New(errors.CheckConstraint, errors.WithOp("alice.bob"), errors.WithMsg("test msg")),
err: errors.E(errors.WithCode(errors.CheckConstraint), errors.WithOp("alice.bob"), errors.WithMsg("test msg")),
want: "alice.bob: test msg: integrity violation: error #1000",
},
{
name: "unknown",
err: errors.New(errors.Unknown),
err: errors.E(),
want: "unknown, unknown: error #0",
},
{
name: "wrapped-no-code",
err: errors.E(errors.WithWrap(errors.E(errors.WithCode(errors.InvalidParameter), errors.WithMsg("wrapped msg"))), errors.WithMsg("test msg")),
want: "test msg: wrapped msg: parameter violation: error #100",
},
{
name: "wrapped-different-error-codes",
err: errors.E(errors.WithCode(errors.CheckConstraint), errors.WithWrap(errors.E(errors.WithCode(errors.InvalidParameter), errors.WithMsg("wrapped msg"))), errors.WithMsg("test msg")),
want: "test msg: integrity violation: error #1000: wrapped msg: parameter violation: error #100",
},
{
name: "wrapped-same-error-codes",
err: errors.E(errors.WithCode(errors.CheckConstraint), errors.WithWrap(errors.E(errors.WithCode(errors.CheckConstraint), errors.WithMsg("wrapped msg"))), errors.WithMsg("test msg")),
want: "test msg: wrapped msg: integrity violation: error #1000",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -129,7 +337,7 @@ func TestError_Error(t *testing.T) {
func TestError_Unwrap(t *testing.T) {
t.Parallel()
testErr := errors.New(errors.Unknown, errors.WithMsg("test error"))
testErr := errors.E(errors.WithMsg("test error"))
tests := []struct {
name string
@@ -139,7 +347,7 @@ func TestError_Unwrap(t *testing.T) {
}{
{
name: "ErrInvalidParameter",
err: errors.New(errors.InvalidParameter, errors.WithWrap(errors.ErrInvalidParameter)),
err: errors.E(errors.WithWrap(errors.ErrInvalidParameter)),
want: errors.ErrInvalidParameter,
wantIsErr: errors.ErrInvalidParameter,
},
@@ -170,6 +378,7 @@ func TestError_Unwrap(t *testing.T) {
func TestConvertError(t *testing.T) {
t.Parallel()
testErr := errors.E(errors.WithCode(errors.InvalidParameter), errors.WithOp("alice.Bob"), errors.WithMsg("test msg"))
const (
createTable = `
create table if not exists test_table (
@@ -210,7 +419,12 @@ func TestConvertError(t *testing.T) {
e: &pq.Error{
Code: pq.ErrorCode("23001"),
},
wantErr: errors.New(errors.NotSpecificIntegrity),
wantErr: errors.E(errors.WithCode(errors.NotSpecificIntegrity)),
},
{
name: "convert-domain-error",
e: testErr,
wantErr: testErr,
},
}
for _, tt := range tests {
@@ -237,7 +451,7 @@ func TestConvertError(t *testing.T) {
e := errors.Convert(err)
require.NotNil(e)
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.: unique constraint violation: integrity violation: error #1002", e.Error())
})
t.Run("ErrCodeNotNull", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -249,7 +463,7 @@ func TestConvertError(t *testing.T) {
e := errors.Convert(err)
require.NotNil(e)
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: not null constraint violated: integrity violation: error #1001", e.Error())
})
t.Run("ErrCodeCheckConstraint", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -261,12 +475,13 @@ func TestConvertError(t *testing.T) {
e := errors.Convert(err)
require.NotNil(e)
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: check constraint violated: integrity violation: error #1000", e.Error())
})
t.Run("MissingTable", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
_, err := rw.Exec(ctx, missingTable, nil)
require.Error(err)
e := errors.Convert(err)
require.NotNil(e)
assert.True(errors.Match(errors.T(errors.MissingTable), e))
+21 -1
View File
@@ -1,7 +1,11 @@
package errors
// Info contains details of the specific error code
type Info struct {
Kind Kind
// Kind specifies the kind of error (unknown, parameter, integrity, etc).
Kind Kind
// Message provides a default message for the error code
Message string
}
@@ -16,6 +20,22 @@ var errorCodeInfo = map[Code]Info{
Message: "invalid parameter",
Kind: Parameter,
},
InvalidAddress: {
Message: "invalid address",
Kind: Parameter,
},
InvalidPublicId: {
Message: "invalid public id",
Kind: Parameter,
},
InvalidFieldMask: {
Message: "invalid field mask",
Kind: Parameter,
},
EmptyFieldMask: {
Message: "empty field mask",
Kind: Parameter,
},
CheckConstraint: {
Message: "constraint check failed",
Kind: Integrity,
+17
View File
@@ -89,3 +89,20 @@ func IsMissingTableError(err error) bool {
}
return false
}
// IsNotFoundError returns a boolean indicating whether the error is known to
// report a not found violation.
func IsNotFoundError(err error) bool {
if err == nil {
return false
}
var domainErr *Err
if errors.As(err, &domainErr) {
if domainErr.Code == RecordNotFound {
return true
}
}
return false
}
+64 -5
View File
@@ -2,6 +2,7 @@ package errors_test
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/db"
@@ -44,7 +45,7 @@ func TestError_IsUnique(t *testing.T) {
},
{
name: "wrapped-pq-is-unique",
in: errors.New(errors.NotUnique,
in: errors.E(errors.WithCode(errors.NotUnique),
errors.WithWrap(&pq.Error{
Code: pq.ErrorCode("23505"),
}),
@@ -56,6 +57,11 @@ func TestError_IsUnique(t *testing.T) {
in: errors.ErrRecordNotFound,
want: false,
},
{
name: "conflicting-wrapped-code",
in: errors.E(errors.WithCode(errors.NotNull), errors.WithWrap(errors.E(errors.WithCode(errors.NotUnique)))),
want: false,
},
}
for _, tt := range tests {
tt := tt
@@ -96,12 +102,12 @@ func TestError_IsCheckConstraint(t *testing.T) {
},
{
name: "ErrCodeCheckConstraint",
in: errors.New(errors.CheckConstraint),
in: errors.E(errors.WithCode(errors.CheckConstraint)),
want: true,
},
{
name: "wrapped-pq-is-check-constraint",
in: errors.New(errors.CheckConstraint,
in: errors.E(errors.WithCode(errors.CheckConstraint),
errors.WithWrap(&pq.Error{
Code: pq.ErrorCode("23514"),
}),
@@ -113,6 +119,11 @@ func TestError_IsCheckConstraint(t *testing.T) {
in: errors.ErrRecordNotFound,
want: false,
},
{
name: "conflicting-wrapped-code",
in: errors.E(errors.WithCode(errors.NotNull), errors.WithWrap(errors.E(errors.WithCode(errors.CheckConstraint)))),
want: false,
},
}
for _, tt := range tests {
tt := tt
@@ -160,12 +171,12 @@ func TestError_IsNotNullError(t *testing.T) {
},
{
name: "ErrCodeNotNull",
in: errors.New(errors.NotNull),
in: errors.E(errors.WithCode(errors.NotNull)),
want: true,
},
{
name: "wrapped-pq-is-not-null",
in: errors.New(errors.NotNull,
in: errors.E(errors.WithCode(errors.NotNull),
errors.WithWrap(&pq.Error{
Code: pq.ErrorCode("23502"),
}),
@@ -177,6 +188,11 @@ func TestError_IsNotNullError(t *testing.T) {
in: errors.ErrRecordNotFound,
want: false,
},
{
name: "conflicting-wrapped-code",
in: errors.E(errors.WithCode(errors.CheckConstraint), errors.WithWrap(errors.E(errors.WithCode(errors.NotNull)))),
want: false,
},
}
for _, tt := range tests {
tt := tt
@@ -240,3 +256,46 @@ func TestError_IsMissingTableError(t *testing.T) {
assert.True(errors.IsMissingTableError(err))
})
}
func TestError_IsNotFoundError(t *testing.T) {
var tests = []struct {
name string
in error
want bool
}{
{
name: "nil-error",
in: nil,
want: false,
},
{
name: "not-found-error",
in: errors.E(errors.WithCode(errors.RecordNotFound)),
want: true,
},
{
name: "sentinel-not-found",
in: errors.ErrRecordNotFound,
want: true,
},
{
name: "std-err",
in: fmt.Errorf("std error"),
want: false,
},
{
name: "conflicting-wrapped-code",
in: errors.E(errors.WithCode(errors.NotNull), errors.WithWrap(errors.E(errors.WithCode(errors.RecordNotFound)))),
want: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
err := tt.in
got := errors.IsNotFoundError(err)
assert.Equal(tt.want, got)
})
}
}
+23 -23
View File
@@ -149,7 +149,7 @@ func TestMatch(t *testing.T) {
{
name: "nil template",
template: nil,
err: New(NotUnique, WithMsg("this thing was must be unique")),
err: E(WithCode(NotUnique), WithMsg("this thing was must be unique")),
want: false,
},
{
@@ -161,8 +161,8 @@ func TestMatch(t *testing.T) {
{
name: "match on Kind only",
template: T(Integrity),
err: New(
NotUnique,
err: E(
WithCode(NotUnique),
WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -172,8 +172,8 @@ func TestMatch(t *testing.T) {
{
name: "no match on Kind only",
template: T(Integrity),
err: New(
RecordNotFound,
err: E(
WithCode(RecordNotFound),
WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -183,8 +183,8 @@ func TestMatch(t *testing.T) {
{
name: "match on Code only",
template: T(NotUnique),
err: New(
NotUnique,
err: E(
WithCode(NotUnique),
WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -194,8 +194,8 @@ func TestMatch(t *testing.T) {
{
name: "no match on Code only",
template: T(NotUnique),
err: New(
RecordNotFound,
err: E(
WithCode(RecordNotFound),
WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -205,8 +205,8 @@ func TestMatch(t *testing.T) {
{
name: "match on Op only",
template: T(Op("alice.Bob")),
err: New(
NotUnique,
err: E(
WithCode(NotUnique),
WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -216,8 +216,8 @@ func TestMatch(t *testing.T) {
{
name: "no match on Op only",
template: T(Op("alice.Alice")),
err: New(
RecordNotFound,
err: E(
WithCode(RecordNotFound),
WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -233,8 +233,8 @@ func TestMatch(t *testing.T) {
ErrInvalidFieldMask,
Op("alice.Bob"),
),
err: New(
InvalidParameter,
err: E(
WithCode(InvalidParameter),
WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -244,8 +244,8 @@ func TestMatch(t *testing.T) {
{
name: "match on Wrapped only",
template: T(ErrInvalidFieldMask),
err: New(
NotUnique,
err: E(
WithCode(NotUnique),
WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -255,8 +255,8 @@ func TestMatch(t *testing.T) {
{
name: "no match on Wrapped only",
template: T(ErrNotUnique),
err: New(
RecordNotFound,
err: E(
WithCode(RecordNotFound),
WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(ErrInvalidFieldMask),
@@ -266,8 +266,8 @@ func TestMatch(t *testing.T) {
{
name: "match on Wrapped only stderror",
template: T(stdErr),
err: New(
NotUnique,
err: E(
WithCode(NotUnique),
WithMsg("this thing must be unique"),
WithOp("alice.Bob"),
WithWrap(stdErr),
@@ -277,8 +277,8 @@ func TestMatch(t *testing.T) {
{
name: "no match on Wrapped only stderror",
template: T(stderrors.New("no match")),
err: New(
RecordNotFound,
err: E(
WithCode(RecordNotFound),
WithMsg("this thing is missing"),
WithOp("alice.Bob"),
WithWrap(stdErr),
+9
View File
@@ -14,6 +14,7 @@ type Option func(*Options)
// Options - how Options are represented.
type Options struct {
withCode Code
withErrWrapped error
withErrMsg string
withOp Op
@@ -46,3 +47,11 @@ func WithOp(op Op) Option {
o.withOp = op
}
}
// WithCode provides an option to provide a code when creating a new
// error.
func WithCode(code Code) Option {
return func(o *Options) {
o.withCode = code
}
}
+13
View File
@@ -48,4 +48,17 @@ func Test_getOpts(t *testing.T) {
testOpts.withOp = "alice.bob"
assert.Equal(opts, testOpts)
})
t.Run("WithCode", 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(WithCode(NotUnique))
testOpts.withCode = NotUnique
assert.Equal(opts, testOpts)
})
}
+9 -9
View File
@@ -6,39 +6,39 @@ package errors
// Matching function.
var (
// ErrInvalidPublicId indicates an invalid PublicId.
ErrInvalidPublicId = New(InvalidParameter, WithMsg("invalid publicId"))
ErrInvalidPublicId = E(WithCode(InvalidParameter), WithMsg("invalid publicId"))
// ErrInvalidParameter is returned by create and update methods if
// an attribute on a struct contains illegal or invalid values.
ErrInvalidParameter = New(InvalidParameter, WithMsg("invalid parameter"))
ErrInvalidParameter = E(WithCode(InvalidParameter), WithMsg("invalid parameter"))
// ErrInvalidFieldMask is returned by update methods if the field mask
// contains unknown fields or fields that cannot be updated.
ErrInvalidFieldMask = New(InvalidParameter, WithMsg("invalid field mask"))
ErrInvalidFieldMask = E(WithCode(InvalidParameter), WithMsg("invalid field mask"))
// ErrEmptyFieldMask is returned by update methods if the field mask is
// empty.
ErrEmptyFieldMask = New(InvalidParameter, WithMsg("empty field mask"))
ErrEmptyFieldMask = E(WithCode(InvalidParameter), WithMsg("empty field mask"))
// ErrNotUnique is returned by create and update methods when a write
// to the repository resulted in a unique constraint violation.
ErrNotUnique = New(NotUnique, WithMsg("unique constraint violation"))
ErrNotUnique = E(WithCode(NotUnique), WithMsg("unique constraint violation"))
// ErrNotNull is returned by methods when a write to the repository resulted
// in a check constraint violation
ErrCheckConstraint = New(CheckConstraint, WithMsg("check constraint violated"))
ErrCheckConstraint = E(WithCode(CheckConstraint), WithMsg("check constraint violated"))
// ErrNotNull is returned by methods when a write to the repository resulted
// in a not null constraint violation
ErrNotNull = New(NotNull, WithMsg("not null constraint violated"))
ErrNotNull = E(WithCode(NotNull), WithMsg("not null constraint violated"))
// ErrRecordNotFound returns a "record not found" error and it only occurs
// when attempting to read from the database into struct.
// When reading into a slice it won't return this error.
ErrRecordNotFound = New(RecordNotFound, WithMsg("record not found"))
ErrRecordNotFound = E(WithCode(RecordNotFound), WithMsg("record not found"))
// ErrMultipleRecords is returned by update and delete methods when a
// write to the repository would result in more than one record being
// changed resulting in the transaction being rolled back.
ErrMultipleRecords = New(MultipleRecords, WithMsg("multiple records"))
ErrMultipleRecords = E(WithCode(MultipleRecords), WithMsg("multiple records"))
)