Skip to content

Commit 30e2c24

Browse files
Add error code and category funcs (#109)
1 parent 24b59a8 commit 30e2c24

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

errors/errors.go

+23-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ type Error struct {
5656

5757
// Tag is a string identifying the error, used with HTTP responses only.
5858
tag string
59+
60+
// Category is a string identifying the category of the error (i.e. "actor", "job", "pubsub), used for error code metrics only.
61+
category string
5962
}
6063

6164
// ErrorBuilder is used to build the error
@@ -84,6 +87,24 @@ func (e *Error) GrpcStatusCode() grpcCodes.Code {
8487
return e.grpcCode
8588
}
8689

90+
// ErrorCode returns the error code from the error, prioritizing the legacy Error.Tag, otherwise the ErrorInfo.Reason
91+
func (e *Error) ErrorCode() string {
92+
errorCode := e.tag
93+
for _, detail := range e.details {
94+
if _, ok := detail.(*errdetails.ErrorInfo); ok {
95+
if _, errInfoReason := convertErrorDetails(detail, *e); errInfoReason != "" {
96+
return errInfoReason
97+
}
98+
}
99+
}
100+
return errorCode
101+
}
102+
103+
// Category returns the error code's category
104+
func (e *Error) Category() string {
105+
return e.category
106+
}
107+
87108
// Error implements the error interface.
88109
func (e Error) Error() string {
89110
return e.String()
@@ -334,14 +355,15 @@ ErrorBuilder
334355
**************************************/
335356

336357
// NewBuilder create a new ErrorBuilder using the supplied required error fields
337-
func NewBuilder(grpcCode grpcCodes.Code, httpCode int, message string, tag string) *ErrorBuilder {
358+
func NewBuilder(grpcCode grpcCodes.Code, httpCode int, message string, tag string, category string) *ErrorBuilder {
338359
return &ErrorBuilder{
339360
err: Error{
340361
details: make([]proto.Message, 0),
341362
grpcCode: grpcCode,
342363
httpCode: httpCode,
343364
message: message,
344365
tag: tag,
366+
category: category,
345367
},
346368
}
347369
}

errors/errors_test.go

+44-3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func TestError_HTTPStatusCode(t *testing.T) {
4545
httpStatusCode,
4646
"Test Msg",
4747
"SOME_ERROR",
48+
"some_category",
4849
).
4950
WithErrorInfo("fake", map[string]string{"fake": "test"}).
5051
Build()
@@ -60,6 +61,7 @@ func TestError_GrpcStatusCode(t *testing.T) {
6061
http.StatusTeapot,
6162
"Test Msg",
6263
"SOME_ERROR",
64+
"some_category",
6365
).
6466
WithErrorInfo("fake", map[string]string{"fake": "test"}).
6567
Build()
@@ -125,6 +127,7 @@ func TestError_Error(t *testing.T) {
125127
http.StatusTeapot,
126128
"Msg",
127129
"SOME_ERROR",
130+
"some_category",
128131
).WithErrorInfo("fake", map[string]string{"fake": "test"}),
129132
fields: fields{
130133
message: "Msg",
@@ -139,6 +142,7 @@ func TestError_Error(t *testing.T) {
139142
http.StatusTeapot,
140143
"Msg",
141144
"SOME_ERROR",
145+
"some_category",
142146
).WithErrorInfo("fake", map[string]string{"fake": "test"}),
143147
fields: fields{
144148
message: "Msg",
@@ -152,6 +156,7 @@ func TestError_Error(t *testing.T) {
152156
http.StatusTeapot,
153157
"Msg",
154158
"SOME_ERROR",
159+
"some_category",
155160
).WithErrorInfo("fake", map[string]string{"fake": "test"}),
156161
fields: fields{
157162
grpcCode: grpcCodes.Canceled,
@@ -186,6 +191,7 @@ func TestErrorBuilder_WithErrorInfo(t *testing.T) {
186191
httpCode: http.StatusTeapot,
187192
message: "fake_message",
188193
tag: "DAPR_FAKE_TAG",
194+
category: "some_category",
189195
details: []proto.Message{
190196
details,
191197
},
@@ -196,6 +202,7 @@ func TestErrorBuilder_WithErrorInfo(t *testing.T) {
196202
http.StatusTeapot,
197203
"fake_message",
198204
"DAPR_FAKE_TAG",
205+
"some_category",
199206
).
200207
WithErrorInfo(reason, metadata)
201208

@@ -222,6 +229,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) {
222229
httpCode int
223230
message string
224231
tag string
232+
category string
225233
}
226234

227235
type args struct {
@@ -283,6 +291,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) {
283291
test.fields.httpCode,
284292
test.fields.message,
285293
test.fields.tag,
294+
test.fields.category,
286295
).WithDetails(test.args.a...)
287296

288297
assert.Equal(t, test.want, kitErr.Build())
@@ -292,7 +301,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) {
292301

293302
func TestWithErrorHelp(t *testing.T) {
294303
// Initialize the Error struct with some default values
295-
err := NewBuilder(grpcCodes.InvalidArgument, http.StatusBadRequest, "Internal error", "INTERNAL_ERROR")
304+
err := NewBuilder(grpcCodes.InvalidArgument, http.StatusBadRequest, "Internal error", "INTERNAL_ERROR", "some_category")
296305

297306
// Define test data for the help links
298307
links := []*errdetails.Help_Link{
@@ -319,7 +328,7 @@ func TestWithErrorHelp(t *testing.T) {
319328

320329
func TestWithErrorFieldViolation(t *testing.T) {
321330
// Initialize the Error struct with some default values
322-
err := NewBuilder(grpcCodes.InvalidArgument, http.StatusBadRequest, "Internal error", "INTERNAL_ERROR")
331+
err := NewBuilder(grpcCodes.InvalidArgument, http.StatusBadRequest, "Internal error", "INTERNAL_ERROR", "some_category")
323332

324333
// Define test data for the field violation
325334
fieldName := "testField"
@@ -348,6 +357,7 @@ func TestError_JSONErrorValue(t *testing.T) {
348357
httpCode int
349358
message string
350359
tag string
360+
category string
351361
}
352362

353363
tests := []struct {
@@ -657,7 +667,7 @@ func TestError_JSONErrorValue(t *testing.T) {
657667

658668
for _, test := range tests {
659669
t.Run(test.name, func(t *testing.T) {
660-
kitErr := NewBuilder(test.fields.grpcCode, test.fields.httpCode, test.fields.message, test.fields.tag).
670+
kitErr := NewBuilder(test.fields.grpcCode, test.fields.httpCode, test.fields.message, test.fields.tag, test.fields.category).
661671
WithDetails(test.fields.details...)
662672

663673
got := kitErr.err.JSONErrorValue()
@@ -705,6 +715,7 @@ func TestError_GRPCStatus(t *testing.T) {
705715
httpCode int
706716
message string
707717
tag string
718+
category string
708719
}
709720

710721
tests := []struct {
@@ -769,6 +780,7 @@ func TestError_GRPCStatus(t *testing.T) {
769780
test.fields.httpCode,
770781
test.fields.message,
771782
test.fields.tag,
783+
test.fields.category,
772784
).WithDetails(test.fields.details...)
773785

774786
got := kitErr.err.GRPCStatus()
@@ -787,6 +799,7 @@ func TestErrorBuilder_Build(t *testing.T) {
787799
http.StatusTeapot,
788800
"Test Msg",
789801
"SOME_ERROR",
802+
"some_category",
790803
).WithErrorInfo("fake", map[string]string{"fake": "test"}).Build()
791804

792805
builtErr, ok := built.(Error)
@@ -803,6 +816,33 @@ func TestErrorBuilder_Build(t *testing.T) {
803816
}
804817

805818
assert.True(t, containsErrorInfo)
819+
assert.Equal(t, "SOME_ERROR", builtErr.ErrorCode())
820+
})
821+
822+
t.Run("With_ErrorInfo (legacy tag absent)", func(t *testing.T) {
823+
built := NewBuilder(
824+
grpcCodes.ResourceExhausted,
825+
http.StatusTeapot,
826+
"Test Msg",
827+
"",
828+
"some_category",
829+
).WithErrorInfo("SOME_ERROR", map[string]string{"fake": "test"}).Build()
830+
831+
builtErr, ok := built.(Error)
832+
require.True(t, ok)
833+
834+
containsErrorInfo := false
835+
836+
for _, detail := range builtErr.details {
837+
_, isErrInfo := detail.(*errdetails.ErrorInfo)
838+
if isErrInfo {
839+
containsErrorInfo = true
840+
break
841+
}
842+
}
843+
844+
assert.True(t, containsErrorInfo)
845+
assert.Equal(t, "SOME_ERROR", builtErr.ErrorCode())
806846
})
807847

808848
t.Run("Without_ErrorInfo", func(t *testing.T) {
@@ -811,6 +851,7 @@ func TestErrorBuilder_Build(t *testing.T) {
811851
http.StatusTeapot,
812852
"Test Msg",
813853
"SOME_ERROR",
854+
"some_category",
814855
)
815856

816857
assert.PanicsWithValue(t, "Must include ErrorInfo in error details.", func() {

0 commit comments

Comments
 (0)