Repository: TomWright/dasel
Pull request #251 (2022-10-06 16:07:20)

Add `Is()` methods for error types.
## Description

This pull requests adds the `Is(err error) bool` method to the errors.

## Problem
Errors are hard to distinguish. It is not possible to follow custom logic in case of error.
Take the following code as an example:
```go
func getName(node *dasel.Node) (string, error) {
	result, err := node.Query(".Name")
	if err != nil {
		if errors.Is(err, &dasel.ValueNotFound{}) {
			return "", errors.New("name not found")
		}
		return "", err
	}
	return result.String(), nil
}
```

## Solution
Implement the `Is` method so the `errors.Is()` function can be used.



Patch:
diff --git a/error.go b/error.go
index 13b6497..896ad90 100644
--- a/error.go
+++ b/error.go
@@ -19,6 +19,12 @@ func (e UnknownComparisonOperatorErr) Error() string {
 	return fmt.Sprintf("unknown comparison operator: %s", e.Operator)
 }
 
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e UnknownComparisonOperatorErr) Is(err error) bool {
+	_, ok := err.(*UnknownComparisonOperatorErr)
+	return ok
+}
+
 // InvalidIndexErr is returned when a selector targets an index that does not exist.
 type InvalidIndexErr struct {
 	Index string
@@ -29,6 +35,12 @@ func (e InvalidIndexErr) Error() string {
 	return fmt.Sprintf("invalid index: %s", e.Index)
 }
 
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e InvalidIndexErr) Is(err error) bool {
+	_, ok := err.(*InvalidIndexErr)
+	return ok
+}
+
 // UnsupportedSelector is returned when a specific selector type is used in the wrong context.
 type UnsupportedSelector struct {
 	Selector string
@@ -39,6 +51,12 @@ func (e UnsupportedSelector) Error() string {
 	return fmt.Sprintf("selector is not supported here: %s", e.Selector)
 }
 
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e UnsupportedSelector) Is(err error) bool {
+	_, ok := err.(*UnsupportedSelector)
+	return ok
+}
+
 // UnsupportedTypeForSelector is returned when a selector attempts to handle a data type it can't handle.
 type UnsupportedTypeForSelector struct {
 	Selector Selector
@@ -50,6 +68,12 @@ func (e UnsupportedTypeForSelector) Error() string {
 	return fmt.Sprintf("selector [type:%s selector:%s] does not support value: [kind:%s type:%T] %v", e.Selector.Type, e.Selector.Raw, e.Value.Kind().String(), e.Value.Interface(), e.Value.Interface())
 }
 
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e UnsupportedTypeForSelector) Is(err error) bool {
+	_, ok := err.(*UnsupportedTypeForSelector)
+	return ok
+}
+
 // ValueNotFound is returned when a selector string cannot be fully resolved.
 type ValueNotFound struct {
 	Selector      string
@@ -61,6 +85,12 @@ func (e ValueNotFound) Error() string {
 	return fmt.Sprintf("no value found for selector: %s: %v", e.Selector, e.PreviousValue)
 }
 
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e ValueNotFound) Is(err error) bool {
+	_, ok := err.(*ValueNotFound)
+	return ok
+}
+
 // UnexpectedPreviousNilValue is returned when the previous node contains a nil value.
 type UnexpectedPreviousNilValue struct {
 	Selector string
@@ -71,6 +101,12 @@ func (e UnexpectedPreviousNilValue) Error() string {
 	return fmt.Sprintf("previous value is nil: %s", e.Selector)
 }
 
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e UnexpectedPreviousNilValue) Is(err error) bool {
+	_, ok := err.(*UnexpectedPreviousNilValue)
+	return ok
+}
+
 // UnhandledCheckType is returned when the a check doesn't know how to deal with the given type
 type UnhandledCheckType struct {
 	Value interface{}
@@ -80,3 +116,9 @@ type UnhandledCheckType struct {
 func (e UnhandledCheckType) Error() string {
 	return fmt.Sprintf("unhandled check type: %T", e.Value)
 }
+
+// Is implements the errors interface, so the errors.Is() function can be used.
+func (e UnhandledCheckType) Is(err error) bool {
+	_, ok := err.(*UnhandledCheckType)
+	return ok
+}


Test patch:
diff --git a/error_test.go b/error_test.go
index 51d4c38..2bc1621 100644
--- a/error_test.go
+++ b/error_test.go
@@ -1,9 +1,12 @@
 package dasel_test
 
 import (
-	"github.com/tomwright/dasel"
+	"errors"
+	"fmt"
 	"reflect"
 	"testing"
+
+	"github.com/tomwright/dasel"
 )
 
 func TestErrorMessages(t *testing.T) {
@@ -45,3 +48,172 @@ func TestErrorMessages(t *testing.T) {
 		})
 	}
 }
+
+func TestErrorsIs(t *testing.T) {
+	type args struct {
+		Err    error
+		Target error
+	}
+
+	tests := []struct {
+		In  args
+		Out bool
+	}{
+		{
+			In: args{
+				Err:    &dasel.UnknownComparisonOperatorErr{},
+				Target: &dasel.UnknownComparisonOperatorErr{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.UnknownComparisonOperatorErr{}),
+				Target: &dasel.UnknownComparisonOperatorErr{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.UnknownComparisonOperatorErr{},
+			},
+			Out: false,
+		},
+		{
+			In: args{
+				Err:    &dasel.InvalidIndexErr{},
+				Target: &dasel.InvalidIndexErr{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.InvalidIndexErr{}),
+				Target: &dasel.InvalidIndexErr{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.InvalidIndexErr{},
+			},
+			Out: false,
+		},
+		{
+			In: args{
+				Err:    &dasel.UnsupportedSelector{},
+				Target: &dasel.UnsupportedSelector{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.UnsupportedSelector{}),
+				Target: &dasel.UnsupportedSelector{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.UnsupportedSelector{},
+			},
+			Out: false,
+		},
+		{
+			In: args{
+				Err:    &dasel.UnsupportedTypeForSelector{},
+				Target: &dasel.UnsupportedTypeForSelector{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.UnsupportedTypeForSelector{}),
+				Target: &dasel.UnsupportedTypeForSelector{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.UnsupportedTypeForSelector{},
+			},
+			Out: false,
+		},
+		{
+			In: args{
+				Err:    &dasel.ValueNotFound{},
+				Target: &dasel.ValueNotFound{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.ValueNotFound{}),
+				Target: &dasel.ValueNotFound{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.ValueNotFound{},
+			},
+			Out: false,
+		},
+		{
+			In: args{
+				Err:    &dasel.UnexpectedPreviousNilValue{},
+				Target: &dasel.UnexpectedPreviousNilValue{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.UnexpectedPreviousNilValue{}),
+				Target: &dasel.UnexpectedPreviousNilValue{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.UnexpectedPreviousNilValue{},
+			},
+			Out: false,
+		},
+		{
+			In: args{
+				Err:    &dasel.UnhandledCheckType{},
+				Target: &dasel.UnhandledCheckType{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    fmt.Errorf("some error: %w", &dasel.UnhandledCheckType{}),
+				Target: &dasel.UnhandledCheckType{},
+			},
+			Out: true,
+		},
+		{
+			In: args{
+				Err:    errors.New("some error"),
+				Target: &dasel.UnhandledCheckType{},
+			},
+			Out: false,
+		},
+	}
+
+	for _, testCase := range tests {
+		tc := testCase
+		t.Run("ErrorMessage", func(t *testing.T) {
+			if exp, got := tc.Out, errors.Is(tc.In.Err, tc.In.Target); exp != got {
+				t.Errorf("expected %v, got %v", exp, got)
+			}
+		})
+	}
+}