diff --git a/client_test.go b/client_test.go index fae0757..77c67ec 100644 --- a/client_test.go +++ b/client_test.go @@ -183,9 +183,7 @@ func TestInvalidLogin(t *testing.T) { require.Error(t, err, "dial succeeded; want error") - var devErr *DeviceError - require.Truef(t, errors.As(err, &devErr), "wait for device error: %v", err) - require.Contains(t, []string{"cannot log in", "invalid user name or password (6)"}, devErr.fetchMessage()) + require.Equal(t, ErrInvalidUserNameOrPassword, err) } func TestTrapHandling(tt *testing.T) { diff --git a/error.go b/error.go index 8585d0f..8753457 100644 --- a/error.go +++ b/error.go @@ -27,6 +27,19 @@ type DeviceError struct { Sentence *proto.Sentence } +type decodedDeviceError string + +// Well-known errors. We could just dynamically create these out of the various +// strings we get back from RouterOS, but by listing them here we capture knowledge +// and experience recognizing the different messages. This makes it easier for clients +// to write good error-handling code. +var ( + ErrInvalidUserNameOrPassword = decodedDeviceError("invalid user name or password (6)") + ErrIncorrectLogin = decodedDeviceError("incorrect login") + ErrAlreadyExists = decodedDeviceError("failure:entry already exists") +) + +func (e decodedDeviceError) Error() string { return string(e) } func (err *DeviceError) fetchMessage() string { if m := err.Sentence.Map["message"]; m != "" { return m diff --git a/proto_test.go b/proto_test.go index a40cbd3..35bdbe7 100644 --- a/proto_test.go +++ b/proto_test.go @@ -77,9 +77,8 @@ func TestLoginIncorrectPre643(t *testing.T) { err := c.Login("userTest", "passTest") require.Error(t, err, "Login succeeded; want error") - var top *DeviceError - require.Truef(t, errors.As(err, &top), "want=DeviceError, have=%#v", err) - require.Contains(t, []string{"incorrect login"}, top.fetchMessage()) + require.Truef(t, errors.Is(err, ErrIncorrectLogin), + "want=ErrIncorrectLogin, have=%#v", err) } func TestLoginIncorrectPost643(t *testing.T) { @@ -96,9 +95,8 @@ func TestLoginIncorrectPost643(t *testing.T) { err := c.Login("userTest", "passTest") require.Error(t, err, "Login succeeded; want error") - var top *DeviceError - require.Truef(t, errors.As(err, &top), "want=DeviceError, have=%#v", err) - require.Contains(t, []string{"invalid user name or password (6)"}, top.fetchMessage()) + require.Truef(t, errors.Is(err, ErrInvalidUserNameOrPassword), + "want=ErrInvalidUserNameOrPassword, have=%#v", err) } func TestLoginNoChallenge(t *testing.T) { diff --git a/reply.go b/reply.go index db2be53..322b28f 100644 --- a/reply.go +++ b/reply.go @@ -1,6 +1,7 @@ package routeros import ( + "errors" "strings" "github.com/go-routeros/routeros/v3/proto" @@ -26,6 +27,7 @@ func (r *Reply) String() string { return sb.String() } +// Return whether or not we are done processing, and any error detected in the sentence func (r *Reply) processSentence(sen *proto.Sentence) (bool, error) { switch sen.Word { case reSentence: @@ -34,7 +36,11 @@ func (r *Reply) processSentence(sen *proto.Sentence) (bool, error) { r.Done = sen return true, nil case trapSentence, fatalSentence: - return sen.Word == fatalSentence, &DeviceError{sen} + if msg, ok := sen.Map["message"]; ok { + // custom error found + return sen.Word == fatalSentence, errors.Join(&DeviceError{Sentence: sen}, decodedDeviceError(msg)) + } + return sen.Word == fatalSentence, &DeviceError{Sentence: sen} case "": // API docs say that empty sentences should be ignored default: