Skip to content

Commit

Permalink
add test to ensure we have a switch for all google err_detail types
Browse files Browse the repository at this point in the history
Signed-off-by: Cassandra Coyle <[email protected]>
  • Loading branch information
cicoyle committed Dec 15, 2023
1 parent 918ce8f commit 1be4de2
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 59 deletions.
40 changes: 24 additions & 16 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand All @@ -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"`
Expand Down Expand Up @@ -147,7 +155,7 @@ func (e Error) JSONErrorValue() []byte {
httpStatus = http.StatusText(e.httpCode)
}

errJSON := ErrorJSON{
errJSON := errorJSON{
ErrorCode: httpStatus,
Message: grpcStatus.GetMessage(),
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
}
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
95 changes: 86 additions & 9 deletions errors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -106,7 +109,7 @@ func TestError_Error(t *testing.T) {
}
tests := []struct {
name string
builder *ErrorBuilder
builder *errorBuilder
fields fields
want string
}{
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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"},
},
Expand All @@ -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"},
},
Expand Down Expand Up @@ -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"},
},
Expand All @@ -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"},
},
Expand Down Expand Up @@ -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"},
},
Expand All @@ -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"},
},
Expand Down Expand Up @@ -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
}
33 changes: 0 additions & 33 deletions errors/messages.go

This file was deleted.

2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,23 @@ 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=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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=
Expand Down Expand Up @@ -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=
Expand Down

0 comments on commit 1be4de2

Please sign in to comment.