Skip to content

Add HasTag method to OopsError #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,21 @@ The library provides an error builder. Each method can be used standalone (eg: `
The `oops.OopsError` builder must finish with either `.Errorf(...)`, `.Wrap(...)`, `.Wrapf(...)`, `.Join(...)`, `.Recover(...)` or `.Recoverf(...)`.

| Builder method | Getter | Description |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|-----------------------------------------|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.With(string, any)` | `err.Context() map[string]any` | Supply a list of attributes key+value. Values of type `func() any {}` are accepted and evaluated lazily. |
| `.WithContext(context.Context, ...any)` | `err.Context() map[string]any` | Supply a list of values declared in context. Values of type `func() any {}` are accepted and evaluated lazily. |
| `.Code(string)` | `err.Code() string` | Set a code or slug that describes the error. Error messages are intented to be read by humans, but such code is expected to be read by machines and be transported over different services |
| `.Code(string)` | `err.Code() string` | Set a code or slug that describes the error. Error messages are intended to be read by humans, but such code is expected to be read by machines and be transported over different services |
| `.Public(string)` | `err.Public() string` | Set a message that is safe to show to an end user |
| `.Time(time.Time)` | `err.Time() time.Time` | Set the error time (default: `time.Now()`) |
| `.Since(time.Time)` | `err.Duration() time.Duration` | Set the error duration |
| `.Duration(time.Duration)` | `err.Duration() time.Duration` | Set the error duration |
| `.In(string)` | `err.Domain() string` | Set the feature category or domain |
| `.Tags(...string)` | `err.Tags() []string` | Add multiple tags, describing the feature returning an error |
| | `err.HasTag(string) bool` | Check whether the error contains provided tag |
| `.Trace(string)` | `err.Trace() string` | Add a transaction id, trace id, correlation id... (default: ULID) |
| `.Span(string)` | `err.Span() string` | Add a span representing a unit of work or operation... (default: ULID) |
| `.Hint(string)` | `err.Hint() string` | Set a hint for faster debugging |
| `.Owner(string)` | `err.Owner() (string)` | Set the name/email of the collegue/team responsible for handling this error. Useful for alerting purpose |
| `.Owner(string)` | `err.Owner() (string)` | Set the name/email of the colleague/team responsible for handling this error. Useful for alerting purpose |
| `.User(string, any...)` | `err.User() (string, map[string]any)` | Supply user id and a chain of key/value |
| `.Tenant(string, any...)` | `err.Tenant() (string, map[string]any)` | Supply tenant id and a chain of key/value |
| `.Request(*http.Request, bool)` | `err.Request() *http.Request` | Supply http request |
Expand Down
17 changes: 17 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ func (o OopsError) Tags() []string {
return lo.Uniq(tags)
}

// HasTag returns true if the tags of the error contain provided value.
func (o OopsError) HasTag(tag string) bool {
if lo.Contains(o.tags, tag) {
return true
}

if o.err == nil {
return false
}

if child, ok := AsOops(o.err); ok {
return child.HasTag(tag)
}

return false
}

// Context returns a k/v context of the error.
func (o OopsError) Context() map[string]any {
return dereferencePointers(
Expand Down
30 changes: 26 additions & 4 deletions oops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,33 @@ func TestOopsIn(t *testing.T) {
func TestOopsTags(t *testing.T) {
is := assert.New(t)

err := new().Tags("iam", "authz", "iam").Wrap(assert.AnError)
is.Error(err)
is.Equal(assert.AnError, err.(OopsError).err)
err := new().Tags("iam", "authz", "iam").Join(
new().Tags("iam", "internal").Wrap(assert.AnError),
new().Tags("not-found").Wrap(assert.AnError))
join, ok := err.(OopsError).err.(interface{ Unwrap() []error })
is.True(ok)
is.Len(join.Unwrap(), 2)
is.Equal(assert.AnError, join.Unwrap()[0].(OopsError).err)
is.Equal(assert.AnError, join.Unwrap()[1].(OopsError).err)
is.Equal([]string{"iam", "authz", "iam"}, err.(OopsError).tags) // not deduplicated
is.Equal([]string{"iam", "authz"}, err.(OopsError).Tags()) // deduplicated
is.Equal([]string{"iam", "internal"}, join.Unwrap()[0].(OopsError).tags)
is.Equal([]string{"not-found"}, join.Unwrap()[1].(OopsError).tags)
is.Equal([]string{"iam", "authz", "internal"}, err.(OopsError).Tags()) // deduplicated and recursive
}

func TestOopsHasTag(t *testing.T) {
is := assert.New(t)

err := new().Tags("iam", "authz").Join(
new().Tags("internal").Wrap(assert.AnError),
new().Tags("not-found").Wrap(assert.AnError))
is.Error(err)
is.True(err.(OopsError).HasTag("internal"))
is.True(err.(OopsError).HasTag("authz"))
is.False(err.(OopsError).HasTag("not-found")) // Does not go over all joined errors so far
is.False(err.(OopsError).HasTag("1234"))

is.False(OopsError{}.HasTag("not-found"))
}

func TestOopsTx(t *testing.T) {
Expand Down
Loading