MongoDB.live, free & fully virtual. Register Now MongoDB.live, free & fully virtual. Register Now

Seeking Developer Feedback: Go Driver Error Improvements

Hi, the team would like user perspective on one of the things we’re working on: error improvements. Currently, writing mgo’s IsDup helper with the driver would look something like this:

func IsDuplicateKeyError(err error) bool {
	switch e := err.(type) {
	case mongo.CommandError:
		return e.Code == 11000 || e.Code == 11001 || e.Code == 12582 || 
(e.Code == 16460 && strings.Contains(e.Error(), " E11000 "))
	case mongo.WriteException:
		if len(e.WriteErrors) > 0 {
			for _, we := range e.WriteErrors {
				if we.Code == 11000 || we.Code == 11001 || we.Code == 12582 || 
(we.Code == 16460 && strings.Contains(we.Error(), " E11000 ")) {
					return true
				}
			}
		}
	case mongo.BulkWriteException:
		if len(e.WriteErrors) > 0 {
			for _, we := range e.WriteErrors {
				if we.Code == 11000 || we.Code == 11001 || we.Code == 12582 || 
(we.Code == 16460 && strings.Contains(we.Error(), " E11000 ")) {
					return true
				}
			}
		}
	default:
		return false
	}
}

Tentatively, the plan is to add an interface with some helpers that CommandError, WriteException, and BulkWriteException will implement:

type ServerError interface {
    error
    HasErrorCode(int) bool
    HasErrorLabel(string) bool
    Unwrap() error
}

Which would turn that DuplicateKey helper function into this:

func IsDuplicateKeyError(err error) bool {
	if e, ok := err.(mongo.ServerError); ok {
		return e.HasErrorCode(11000) || e.HasErrorCode(11001) || e.HasErrorCode(12582) || 
			(e.HasErrorCode(16460) && strings.Contains(e.Error(), " E11000 "))
	}
	return false
}

IsDuplicateKeyError would also be one of the standalone helpers added for commonly checked errors:

IsTimeout(error) bool
IsDuplicateKeyError(error) bool

ex.

Var err error
if mongo.IsTimeout(err) {
	 // handle the error
}

Would this solution serve your needs and are there other helpers that would be nice to have?

6 Likes

Hello @Isabella_Siu and thanks for your work on the golang driver

My first feedback would be: if you plan to use error codes in this way, probably is better to enumerate them uysing golang iota feature

type ErrorCode uint8
const (
    ErrorCodeType1 ErrorCode = iota
    ErrorCodeType2 ErrorCode = iota
)

and change the interface to the following

type ServerError interface {
    error
    HasErrorCode(ErrorCode) bool
    HasErrorLabel(string) bool
    Unwrap() error
}

same reasoning can also be applied to the ErrorLabels, if they can be enumerated in some way, but without iota

type ErrorLabel string

var (
    ErrorLabelType1 ErrorLabel = ErrorLabel("just an example type")
    ErrorLabelType2 ErrorLabel = ErrorLabel("just another example type")
)

hope it helps

There is a whole literature in how to do the IsError “pattern” in golang and usually it’s similar to the way you are proposing to implement, so I have no more feedback.

Just do it for all errors (including for example ErrNoDocuments) to be consistent with current codebase

Again, thanks for your work

3 Likes

@Isabella_Siu -
This is coming along nicely - thank you so much for sharing the proposal with us.
I love the idea of extending the error types to have .HasErrorCode() and .HasErrorLabel(), but I personally am with @Alessandro_Sanino in that I’d also like iota enumerated types so that performing error checking is really light and fast.

Taking a look at the new IsDup error function, I can see that while this proposal simplifies some logic, at the end of the day, it doesn’t seem to solve the fundamental issue. This line in particular is what’s concerning me (e.HasErrorCode(16460) && strings.Contains(e.Error(), " E11000 ")). If there is one error code in the error type and another code in the string, then it doesn’t seem like it’s simple to determine what the actual error is.

Maybe something that might help is to create an internal map in mongo-go-driver. Extrapolate certain error codes to categories. So rather than having an IsDup function, the error could contain an additional field/accessor, ErrorCategory(), in this case it would be set to an iota int const of DuplicateKey, which is set by the driver when the result comes back from the database (as opposed to having to call it after every insert). I would argue that there are quite a few errors that could be categorized.

Having fine-grained access to the specific code/error is nice, but since there’s no documentation on what the individual error codes mean outside of the golang driver (e.g. DuckDuckGo & Google). Having the low level error code just typically isn’t directly helpful.

Possible map

type ErrorCategory int {
  ErrDuplicateKey iota
  ErrDuplicateIndex
}
errCategoryMap := map[int]int{
  11000: ErrDuplicateKey,
  11001: ErrDuplicateKey,
   ...
}
1 Like

Hi, @Alessandro_Sanino and @TopherGopher thanks for your feedback!

The reason that the error code and labels aren’t iota enumerated is that they’re parsed from the server errors, which the driver doesn’t have control over, and this allows us the flexibility we need to handle changes in that. The driver API has stricter constraints than the server error codes, for example, the server can stop including an error code fairly easily, but we would be stuck with the iota value until the next major release. If you want to take a look, the server error codes are listed here.

This is why IsDuplicateKeyError ends up having complex logic. Between major server versions the way the server changed how it signals that, so the function needs to catch all the different ways different versions return it. We’re currently only planning to add standalone helpers for error checking that requires more complex logic and not things that can be checked with equality or by calling the HasErrorCode() or HasErrorLabel() functions.

Specifically in regards to the idea of error categories, our feeling is that any category that could be handled that way either is already handled by the server labels and HasErrorLabel() or could be handled with a standalone helper. If you have ideas for any additional error helpers, we’d be happy to consider them.

1 Like