Skip to content

Commit

Permalink
Add facility to recognize specific error messages.
Browse files Browse the repository at this point in the history
The initial list is quite short, incorporating just known errors of
invalid username/passwords, incorrect login, and 'entry already exists'.
This list should eventually grow as more errors are recognized.

By adding in these types we prevent a client from having to either bubble
up literally any text from RouterOS or have to rebuild all the different
error strings themselves.
  • Loading branch information
EliRibble committed Oct 8, 2024
1 parent c7c9b83 commit fc3f9b7
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 10 deletions.
4 changes: 1 addition & 3 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 4 additions & 6 deletions proto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion reply.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package routeros

import (
"errors"
"strings"

"github.com/go-routeros/routeros/v3/proto"
Expand All @@ -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:
Expand All @@ -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:
Expand Down

0 comments on commit fc3f9b7

Please sign in to comment.