diff --git a/errors/errors.go b/errors/errors.go index fb501b9..3f15b24 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -28,6 +28,14 @@ import ( "github.com/dapr/kit/logger" ) +const ( + Domain = "dapr.io" + + errStringFormat = "api error: code = %s desc = %s" + + typeGoogleAPI = "type.googleapis.com/" +) + var log = logger.NewLogger("dapr.kit") // Error implements the Error interface and the interface that complies with "google.golang.org/grpc/status".FromError(). @@ -51,12 +59,12 @@ type Error struct { } // ErrorBuilder is used to build the error -type ErrorBuilder struct { +type errorBuilder struct { err Error } -// ErrorJSON is used to build the error for the HTTP Methods json output -type ErrorJSON struct { +// errorJSON is used to build the error for the HTTP Methods json output +type errorJSON struct { ErrorCode string `json:"errorCode"` Message string `json:"message"` Details []any `json:"details,omitempty"` @@ -147,7 +155,7 @@ func (e Error) JSONErrorValue() []byte { httpStatus = http.StatusText(e.httpCode) } - errJSON := ErrorJSON{ + errJSON := errorJSON{ ErrorCode: httpStatus, Message: grpcStatus.GetMessage(), } @@ -301,9 +309,9 @@ func (e Error) JSONErrorValue() []byte { ErrorBuilder **************************************/ -// NewBuilder create a new ErrorBuilder using the supplied required error fields -func NewBuilder(grpcCode grpcCodes.Code, httpCode int, message string, tag string) *ErrorBuilder { - return &ErrorBuilder{ +// NewBuilder create a new errorBuilder using the supplied required error fields +func NewBuilder(grpcCode grpcCodes.Code, httpCode int, message string, tag string) *errorBuilder { + return &errorBuilder{ err: Error{ details: make([]proto.Message, 0), grpcCode: grpcCode, @@ -315,7 +323,7 @@ func NewBuilder(grpcCode grpcCodes.Code, httpCode int, message string, tag strin } // WithResourceInfo is used to pass ResourceInfo error details to the Error struct. -func (b *ErrorBuilder) WithResourceInfo(resourceType string, resourceName string, owner string, description string) *ErrorBuilder { +func (b *errorBuilder) WithResourceInfo(resourceType string, resourceName string, owner string, description string) *errorBuilder { resourceInfo := &errdetails.ResourceInfo{ ResourceType: resourceType, ResourceName: resourceName, @@ -329,7 +337,7 @@ func (b *ErrorBuilder) WithResourceInfo(resourceType string, resourceName string } // WithHelpLink is used to pass HelpLink error details to the Error struct. -func (b *ErrorBuilder) WithHelpLink(url string, description string) *ErrorBuilder { +func (b *errorBuilder) WithHelpLink(url string, description string) *errorBuilder { link := errdetails.Help_Link{ Description: description, Url: url, @@ -344,16 +352,16 @@ func (b *ErrorBuilder) WithHelpLink(url string, description string) *ErrorBuilde } // WithHelp is used to pass Help error details to the Error struct. -func (b *ErrorBuilder) WithHelp(links []*errdetails.Help_Link) *ErrorBuilder { +func (b *errorBuilder) WithHelp(links []*errdetails.Help_Link) *errorBuilder { b.err.details = append(b.err.details, &errdetails.Help{Links: links}) return b } // WithErrorInfo adds error information to the Error struct. -func (b *ErrorBuilder) WithErrorInfo(reason string, metadata map[string]string) *ErrorBuilder { +func (b *errorBuilder) WithErrorInfo(reason string, metadata map[string]string) *errorBuilder { errorInfo := &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: reason, Metadata: metadata, } @@ -363,7 +371,7 @@ func (b *ErrorBuilder) WithErrorInfo(reason string, metadata map[string]string) } // WithFieldViolation is used to pass FieldViolation error details to the Error struct. -func (b *ErrorBuilder) WithFieldViolation(fieldName string, msg string) *ErrorBuilder { +func (b *errorBuilder) WithFieldViolation(fieldName string, msg string) *errorBuilder { br := &errdetails.BadRequest{ FieldViolations: []*errdetails.BadRequest_FieldViolation{{ Field: fieldName, @@ -377,15 +385,15 @@ func (b *ErrorBuilder) WithFieldViolation(fieldName string, msg string) *ErrorBu } // WithDetails is used to pass any error details to the Error struct. -func (b *ErrorBuilder) WithDetails(details ...proto.Message) *ErrorBuilder { +func (b *errorBuilder) WithDetails(details ...proto.Message) *errorBuilder { b.err.details = append(b.err.details, details...) return b } // Build builds our error -func (b *ErrorBuilder) Build() error { - // Check for ErrorInfo, since its required per the proposal +func (b *errorBuilder) Build() error { + // Check for ErrorInfo, since it's required per the proposal containsErrorInfo := false for _, detail := range b.err.details { if _, ok := detail.(*errdetails.ErrorInfo); ok { diff --git a/errors/errors_test.go b/errors/errors_test.go index 581ca41..310ebec 100644 --- a/errors/errors_test.go +++ b/errors/errors_test.go @@ -16,10 +16,13 @@ package errors import ( "encoding/json" "fmt" + "go/types" "net/http" "reflect" "testing" + "golang.org/x/tools/go/packages" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/genproto/googleapis/rpc/context" @@ -65,7 +68,7 @@ func TestError_AddDetails(t *testing.T) { metadata := map[string]string{"key": "value"} details1 := &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: reason, Metadata: metadata, } @@ -106,7 +109,7 @@ func TestError_Error(t *testing.T) { } tests := []struct { name string - builder *ErrorBuilder + builder *errorBuilder fields fields want string }{ @@ -168,7 +171,7 @@ func TestErrorBuilder_WithErrorInfo(t *testing.T) { reason := "fake" metadata := map[string]string{"fake": "test"} details := &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: reason, Metadata: metadata, } @@ -237,7 +240,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) { }, args: args{a: []proto.Message{ &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: "example_reason", Metadata: map[string]string{"key": "value"}, }, @@ -254,7 +257,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) { tag: "DAPR_FAKE_TAG", details: []proto.Message{ &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: "example_reason", Metadata: map[string]string{"key": "value"}, }, @@ -363,7 +366,7 @@ func TestError_JSONErrorValue(t *testing.T) { fields: fields{ details: []proto.Message{ &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: "test_reason", Metadata: map[string]string{"key": "value"}, }, @@ -385,7 +388,7 @@ func TestError_JSONErrorValue(t *testing.T) { fields: fields{ details: []proto.Message{ &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: "test_reason", Metadata: map[string]string{"key": "value"}, }, @@ -720,7 +723,7 @@ func TestError_GRPCStatus(t *testing.T) { fields: fields{ details: []proto.Message{ &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: "FAKE_REASON", Metadata: map[string]string{"key": "value"}, }, @@ -739,7 +742,7 @@ func TestError_GRPCStatus(t *testing.T) { s, _ := status.New(grpcCodes.ResourceExhausted, "fake_message"). WithDetails( &errdetails.ErrorInfo{ - Domain: ErrMsgDomain, + Domain: Domain, Reason: "FAKE_REASON", Metadata: map[string]string{"key": "value"}, }, @@ -810,3 +813,77 @@ func TestErrorBuilder_Build(t *testing.T) { }) }) } + +// This test ensures that all the error details google provides are covered in our switch case +// in errors.go. If google adds an error detail, this test should fail, and we should add +// that specific error detail to the switch case +func TestEnsureAllErrDetailsCovered(t *testing.T) { + packagePath := "google.golang.org/genproto/googleapis/rpc/errdetails" + + // Load the package + cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo} + pkgs, err := packages.Load(cfg, packagePath) + if err != nil { + t.Error(err) + } + + if packages.PrintErrors(pkgs) > 0 { + t.Errorf("ensure package is correct: %v", packages.ListError) + } + + // This is hard-coded from the switch statement in errors.go to ensure we stay up + // to date on the error types we support, and to ensure we update our supported + // error types when google adds to their error details + mySwitchTypes := []string{ + "ErrorInfo", + "RetryInfo", + "DebugInfo", + "QuotaFailure", + "PreconditionFailure", + "PreconditionFailure_Violation", + "BadRequest", + "BadRequest_FieldViolation", + "RequestInfo", + "ResourceInfo", + "Help", + "LocalizedMessage", + "QuotaFailure_Violation", + "Help_Link", + } + + coveredTypes := make(map[string]bool) + + // Iterate through the types in googles error detail package + for _, name := range pkgs[0].Types.Scope().Names() { + obj := pkgs[0].Types.Scope().Lookup(name) + if typ, ok := obj.Type().(*types.Named); ok { + typeFullName := typ.Obj().Name() + + // Check if the type is covered in errors.go switch cases + if containsType(mySwitchTypes, typ.Obj().Name()) { + coveredTypes[typeFullName] = true + } else { + coveredTypes[typeFullName] = false + } + } + } + + // Check if there are any uncovered types + for typeName, covered := range coveredTypes { + // Skip "FileDescriptor" && "Once" since those aren't types we care about + if !covered && typeName != "FileDescriptor" && typeName != "Once" { + t.Errorf("Type %s is not handled in switch cases, please update the switch case in errors.go", + typeName) + } + } +} + +// containsType checks if the slice of types contains a specific type +func containsType(types []string, target string) bool { + for _, t := range types { + if t == target { + return true + } + } + return false +} diff --git a/errors/messages.go b/errors/messages.go deleted file mode 100644 index 3dde739..0000000 --- a/errors/messages.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package errors - -const ( - ErrMsgDomain = "dapr.io" - - errStringFormat = "api error: code = %s desc = %s" - - typeGoogleAPI = "type.googleapis.com/" - - // Messages - MsgStateGet = "failed to get %s from state store %s: %s" - MsgStateDelete = "failed deleting state with key %s: %s" - MsgStateSave = "failed saving state in state store %s: %s" - MsgStateDeleteBulk = "failed deleting state in state store %s: %s" - - // StateTransaction - MsgStateTransactionsNotSupported = "state store %s doesn't support transaction" - MsgStateOperationNotSupported = "operation type %s not supported" - MsgStateTransaction = "error while executing state transaction: %s" -) diff --git a/go.mod b/go.mod index 5c98fbb..14df794 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/tools v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -33,6 +34,7 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.0 // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 6c7fcc6..27db2a2 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -82,13 +84,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -120,6 +123,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=