Skip to content

Commit fbc5a49

Browse files
committed
feat: add errs and validator package
1 parent af14797 commit fbc5a49

File tree

8 files changed

+459
-0
lines changed

8 files changed

+459
-0
lines changed

errs/errs.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package errs
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"runtime"
7+
"strings"
8+
9+
errs "errors"
10+
11+
"github.com/pkg/errors"
12+
)
13+
14+
// UserName is a string representing a user
15+
type UserName string
16+
17+
// Kind defines the kind of error this is
18+
type Kind uint8
19+
20+
// Code is a human-readable, short representation of the error
21+
type Code string
22+
23+
// Parameter represents the parameter related to the error.
24+
type Parameter string
25+
26+
// Error is the type that implements the error interface.
27+
// It contains a number of fields, each of different type.
28+
// An Error value may leave some values unset.
29+
type Error struct {
30+
// User is the username of the user attempting the operation.
31+
User UserName
32+
33+
// Kind is the class of error, such as permission failure,
34+
// or "Other" if its class is unknown or irrelevant.
35+
Kind Kind
36+
37+
// Code is a human-readable, short representation of the error
38+
Code Code
39+
40+
// Param represents the parameter related to the error.
41+
Param Parameter
42+
43+
// The underlying error that triggered this one, if any.
44+
Err error
45+
}
46+
47+
// Is is method to satisfy errors.Is interface
48+
func (e *Error) Is(target error) bool {
49+
return errs.Is(e.Err, target)
50+
}
51+
52+
// As is method to satisfy errors.As interface
53+
func (w *Error) As(target interface{}) bool {
54+
return errs.As(w.Err, target)
55+
}
56+
57+
func (e *Error) Cause() error {
58+
return e.Err
59+
}
60+
61+
func (e Error) Unwrap() error {
62+
return e.Err
63+
}
64+
65+
func (e *Error) Error() string {
66+
return e.Err.Error()
67+
}
68+
69+
// StackTrace satisfy errors.StackTrace interface
70+
func (e Error) StackTrace() errors.StackTrace {
71+
type stackTracer interface {
72+
StackTrace() errors.StackTrace
73+
}
74+
75+
if st, ok := e.Err.(stackTracer); ok {
76+
return st.StackTrace()
77+
}
78+
79+
return nil
80+
}
81+
82+
type ValidationErrors []error
83+
84+
func (v *ValidationErrors) Append(args ...interface{}) {
85+
*v = append(*v, E(args...))
86+
}
87+
88+
func (v ValidationErrors) Error() string {
89+
buff := bytes.NewBufferString("")
90+
91+
for i := 0; i < len(v); i++ {
92+
93+
if err, ok := v[i].(*Error); ok {
94+
buff.WriteString(fmt.Sprintf("%s: %s", err.Param, err.Error()))
95+
buff.WriteString("\n")
96+
}
97+
}
98+
99+
return strings.TrimSpace(buff.String())
100+
}
101+
102+
const (
103+
Unknown Kind = iota // Unknown error. This value is expected for unknown error
104+
Other // Unclassified error. This value is not printed in the error message
105+
IO // External I/O error such as network failure
106+
Private // Information withheld
107+
Internal // Internal error or inconsistency
108+
Database // Database error
109+
Exist // Resource already exist
110+
NotExist // Resource does not exists
111+
Invalid // Invalid operation for this type of item
112+
Validation // Input validation error
113+
InvalidRequest // Invalid request
114+
Permission // Permission error request
115+
Unauthenticated // Unauthenticated error if unauthenticated request occur
116+
)
117+
118+
func (k Kind) String() string {
119+
switch k {
120+
case Other:
121+
return "other_error"
122+
case IO:
123+
return "I/O_error"
124+
case Private:
125+
return "private"
126+
case Internal:
127+
return "internal_error"
128+
case Database:
129+
return "database_error"
130+
case Exist:
131+
return "resource_already_exists"
132+
case NotExist:
133+
return "resource_does_not_exist"
134+
case Invalid:
135+
return "invalid_operation"
136+
case Validation:
137+
return "input_validation_error"
138+
case InvalidRequest:
139+
return "invalid_request_error"
140+
case Permission:
141+
return "permission_denied"
142+
case Unauthenticated:
143+
return "unauthenticated_request"
144+
}
145+
146+
return "unknown_error"
147+
}
148+
149+
func Match(err1, err2 error) bool {
150+
e1, ok := err1.(*Error)
151+
if !ok {
152+
return false
153+
}
154+
e2, ok := err2.(*Error)
155+
if !ok {
156+
return false
157+
}
158+
if e1.User != "" && e2.User != e1.User {
159+
return false
160+
}
161+
if e1.Kind != Other && e2.Kind != e1.Kind {
162+
return false
163+
}
164+
if e1.Param != "" && e2.Param != e1.Param {
165+
return false
166+
}
167+
if e1.Code != "" && e2.Code != e1.Code {
168+
return false
169+
}
170+
if e1.Err != nil {
171+
if _, ok := e1.Err.(*Error); ok {
172+
return Match(e1.Err, e2.Err)
173+
}
174+
if e2.Err == nil || e2.Err.Error() != e1.Err.Error() {
175+
return false
176+
}
177+
}
178+
return true
179+
}
180+
181+
func GetKind(err error) Kind {
182+
e, ok := err.(*Error)
183+
if !ok {
184+
return Unknown
185+
}
186+
187+
return e.Kind
188+
}
189+
190+
func E(args ...interface{}) error {
191+
type stackTracer interface {
192+
StackTrace() errors.StackTrace
193+
}
194+
195+
if len(args) == 0 {
196+
panic("call to errors.E with no arguments")
197+
}
198+
199+
e := &Error{}
200+
for _, arg := range args {
201+
switch arg := arg.(type) {
202+
case Kind:
203+
e.Kind = arg
204+
case UserName:
205+
e.User = arg
206+
case Code:
207+
e.Code = arg
208+
case Parameter:
209+
e.Param = arg
210+
case string:
211+
e.Err = errors.New(arg)
212+
case *Error:
213+
e.Err = arg
214+
case error:
215+
// if the error is validation errors, skipping the stacktrace
216+
if verr, ok := arg.(ValidationErrors); ok {
217+
e.Err = verr
218+
continue
219+
}
220+
221+
// if the error implements stackTracer, then it is
222+
// a pkg/errors error type and does not need to have
223+
// the stack added
224+
_, ok := arg.(stackTracer)
225+
if ok {
226+
e.Err = arg
227+
} else {
228+
e.Err = errors.WithStack(arg)
229+
}
230+
default:
231+
_, file, line, _ := runtime.Caller(1)
232+
return fmt.Errorf("errors.E: bad call from %s:%d: %v, unknown type %T, value %v in error call", file, line, args, arg, arg)
233+
}
234+
}
235+
236+
prev, ok := e.Err.(*Error)
237+
if !ok {
238+
return e
239+
}
240+
// If this error has Kind unset or Other, pull up the inner one.
241+
if e.Kind == Other {
242+
e.Kind = prev.Kind
243+
prev.Kind = Other
244+
}
245+
246+
if prev.Code == e.Code {
247+
prev.Code = ""
248+
}
249+
// If this error has Code == "", pull up the inner one.
250+
if e.Code == "" {
251+
e.Code = prev.Code
252+
prev.Code = ""
253+
}
254+
255+
if prev.Param == e.Param {
256+
prev.Param = ""
257+
}
258+
// If this error has Code == "", pull up the inner one.
259+
if e.Param == "" {
260+
e.Param = prev.Param
261+
prev.Param = ""
262+
}
263+
264+
return e
265+
}

errs/errs_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package errs_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/ardikabs/golib/errs"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestE(t *testing.T) {
12+
13+
t.Run("new errs.Error", func(t *testing.T) {
14+
err := errs.E(errs.Other, errs.Code("another_code"), fmt.Errorf("some new error"))
15+
assert.NotNil(t, err)
16+
assert.Equal(t, errs.Other, errs.GetKind(err))
17+
assert.Equal(t, "some new error", err.Error())
18+
})
19+
20+
t.Run("stacked error", func(t *testing.T) {
21+
err := errs.E(errs.Other, errs.Code("another_code"), errs.Parameter("param"))
22+
err = errs.E(errs.Validation, err)
23+
assert.Equal(t, errs.Validation, errs.GetKind(err))
24+
25+
e, ok := err.(*errs.Error)
26+
assert.True(t, ok)
27+
assert.Equal(t, errs.Parameter("param"), e.Param)
28+
assert.Equal(t, errs.Code("another_code"), e.Code)
29+
})
30+
31+
t.Run("string error", func(t *testing.T) {
32+
err := errs.E(errs.Internal, "internal server error")
33+
assert.Equal(t, "internal server error", err.Error())
34+
})
35+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require github.com/stretchr/testify v1.8.0
66

77
require (
88
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pkg/errors v0.9.1 // indirect
910
github.com/pmezard/go-difflib v1.0.0 // indirect
1011
gopkg.in/yaml.v3 v3.0.1 // indirect
1112
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
5+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
46
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
57
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
68
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

tool/tool.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package tool
22

3+
import (
4+
"regexp"
5+
"time"
6+
)
7+
38
// In returns true if a given value exists in the list.
49
func In[T comparable](value T, list ...T) bool {
510
for i := range list {
@@ -9,3 +14,28 @@ func In[T comparable](value T, list ...T) bool {
914
}
1015
return false
1116
}
17+
18+
// Matches return true if a given string value matches regex provided
19+
func Matches(value string, rx *regexp.Regexp) bool {
20+
return rx.MatchString(value)
21+
}
22+
23+
// Unique returns true if all given values in the list are unique.
24+
func Unique[T comparable](values []T) bool {
25+
uniqueValues := make(map[T]bool)
26+
27+
for _, value := range values {
28+
uniqueValues[value] = true
29+
}
30+
31+
return len(values) == len(uniqueValues)
32+
}
33+
34+
// RFC3339 return true if value is compatible with layout RFC3339
35+
func RFC3339(value string) bool {
36+
if _, err := time.Parse(time.RFC3339, value); err != nil {
37+
return false
38+
}
39+
40+
return true
41+
}

0 commit comments

Comments
 (0)