From 6f663bf8633363fb677dbf77b3fdd632c784af03 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 15 Feb 2021 07:46:26 +0100 Subject: [PATCH] Return NotFounder error, if ID attribute is not present on plugin --- ast/ast.go | 4 +- ast/errors.go | 66 +++++++++++++++++++++++++++++++++ ast/errors_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 ast/errors.go create mode 100644 ast/errors_test.go diff --git a/ast/ast.go b/ast/ast.go index 652cc5a..97c56c3 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -165,6 +165,8 @@ func (p Plugin) String() string { // The id attribute is one of the common options, that is optionally available // on every Logstash plugin. In generall, it is highly recommended for a Logstash // plugin to have an id. +// If the ID attribute is not present, an error is returned, who implements +// the NotFounder interface. func (p Plugin) ID() (string, error) { for _, attr := range p.Attributes { if attr != nil && attr.Name() == "id" { @@ -176,7 +178,7 @@ func (p Plugin) ID() (string, error) { } } } - return "", fmt.Errorf("plugin %s does not contain an id attribute", p.name) + return "", NotFoundErrorf("plugin %s does not contain an id attribute", p.name) } // Attribute interface combines Logstash plugin attribute types. diff --git a/ast/errors.go b/ast/errors.go new file mode 100644 index 0000000..24c969b --- /dev/null +++ b/ast/errors.go @@ -0,0 +1,66 @@ +package ast + +import ( + "errors" + "fmt" +) + +// NotFounder interface is implemented by errors, that indicate that a record +// is not found. +type NotFounder interface { + Error() string + NotFound() +} + +type notFoundError struct { + err error +} + +// NewNotFoundError wraps an error as a not found error. +func NewNotFoundError(err error) error { + if err == nil { + return nil + } + + return notFoundError{ + err: err, + } +} + +// NotFoundErrorf formats according to a format specifier and returns the string +// as a value that satisfies NotFounder. +func NotFoundErrorf(format string, a ...interface{}) error { + return NewNotFoundError(fmt.Errorf(format, a...)) +} + +// NotFound implements the NotFounder interface to indicate, that the error is +// of type not found. +func (e notFoundError) NotFound() {} + +func (e notFoundError) Error() string { + return "not found: " + e.err.Error() +} + +// Format implements the fmt.Formatter interface to print the error differently +// depending on the format verb. +func (e notFoundError) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "not found: %+v", e.err) + return + } + fallthrough + case 's': + fmt.Fprint(s, e.Error()) + case 'q': + fmt.Fprintf(s, "%q", e.Error()) + } +} + +// IsNotFoundError returns true, if the provided error implements the NotFound +// interface. +func IsNotFoundError(e error) bool { + var notFoundErr NotFounder + return errors.As(e, ¬FoundErr) +} diff --git a/ast/errors_test.go b/ast/errors_test.go new file mode 100644 index 0000000..76bb4e8 --- /dev/null +++ b/ast/errors_test.go @@ -0,0 +1,91 @@ +package ast_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/breml/logstash-config/ast" +) + +func TestNotFoundError(t *testing.T) { + cases := []struct { + formatVerb string + + wantErrorString string + }{ + { + formatVerb: "%s", + + wantErrorString: "not found: error", + }, + { + formatVerb: "%v", + + wantErrorString: "not found: error", + }, + { + formatVerb: "%+v", + + wantErrorString: "not found: error", + }, + { + formatVerb: "%q", + + wantErrorString: `"not found: error"`, + }, + } + + for _, test := range cases { + t.Run(test.formatVerb, func(t *testing.T) { + err := ast.NotFoundErrorf("%s", "error") + + if err == nil { + t.Fatal("Expect an error, but got none.") + } + + if test.wantErrorString != fmt.Sprintf(test.formatVerb, err) { + t.Errorf("Expect error to print %q with verb %q, but got: %q", test.wantErrorString, test.formatVerb, fmt.Sprintf(test.formatVerb, err)) + } + + if !ast.IsNotFoundError(err) { + t.Fatalf("Expect err %v to implement NotFounder interface, but it does not.", err) + } + }) + } +} + +func TestNotFoundErrorNotFound(t *testing.T) { + ast.NewNotFoundError(errors.New("error")).(ast.NotFounder).NotFound() +} + +func TestIsNotFoundError(t *testing.T) { + cases := []struct { + name string + err error + + want bool + }{ + { + name: "nil", + }, + { + name: "nil not founder", + err: ast.NewNotFoundError(nil), + }, + { + name: "error", + err: ast.NewNotFoundError(errors.New("error")), + want: true, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + got := ast.IsNotFoundError(test.err) + if test.want != got { + t.Fatalf("Expectation (%v) not met: %v", test.want, got) + } + }) + } +}