Skip to content

GODRIVER-1992 Implement ServerErrors interface for driver errors #667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed

GODRIVER-1992 Implement ServerErrors interface for driver errors #667

wants to merge 3 commits into from

Conversation

benjirewis
Copy link
Contributor

GODRIVER-1992

Implements the ServerError interface for driver.WriteCommandError and driver.WriteConcernError, so users will have access to the error helpers HasErrorCode, HasErrorLabel, HasErrorMessage and HasErrorCodeWithMessage for those driver errors.

Adds tests for these new functions in errors_test.go.

Copy link
Contributor Author

@benjirewis benjirewis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe driver.WriteCommandError and driver.WriteConcernError are the only types that could implement ServerError in the driver package, but let me know if I'm missing some @iwysiu .

@@ -202,13 +202,13 @@ type ServerError interface {
HasErrorMessage(string) bool
// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
HasErrorCodeWithMessage(int, string) bool

serverError()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementing ServerError for types outside of the mongo package (in this case in driver) means that I must remove this guard. Without serverError(), users can now implement the ServerError interface for their own types. We do document that this is discouraged, but if there's some way I'm missing to keep this guard in, let me know 🌺

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily think its better, but we could alternatively have a drivers.ServerError interface that has the same functions and have the helpers that check for the ServerError interface also check for the drivers.ServerError interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't love the repetition, but it might be better than removing the guard altogether. Any thoughts @kevinAlbs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iwysiu does copying the interface take away from the usability?

I thought the motivation of the ServerError interface was to be a convenient interface to inspect all server errors.

_, err = client.Database("db").Collection("coll").InsertOne(ctx, bson.D{{"_id", "foo"}})
	if err != nil {
		fmt.Println("Insert failed", err)
		var serverError mongo.ServerError

		if errors.As(err, &serverError) {
			if serverError.HasErrorLabel("RetryableWrite") {
				fmt.Println("Going to retry...")
			}
		}
	}

A distinct driver.ServerError would be less accessible if it is in the x package, and it would require the user to check for both interfaces.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make it more annoying for end users. If we're not worried about users implementing it, i think removing the guard is fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too familiar with the nuances of whether this should be a sealed interface.

No functions take a ServerError as a direct argument. The helper IsDuplicateKeyError could accept an error that the user has defined which implements ServerError. But that seems harmless to me.

Are there other risks?

Copy link
Collaborator

@matthewdale matthewdale May 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's much risk to "unsealing" the ServerError interface. As @kevinAlbs mentioned, nothing uses it as an argument directly, and even if someone implemented the interface, they wouldn't get any unexpected behavior, it just doesn't make much sense to do so.

Alternately, there's actually no reason that ServerError needs to be exported. We could leave ServerError as is, define a new unexported interface like serverError with the same function signatures, add a note to ServerError that it's unused and only there to maintain public API compatibility.

Edit:
My understanding is that a common use case is for users to type-assert an error is mongo.ServerError so they can access functions like HasErrorCode() and HasErrorLabel().

E.g.

err := mongo.Something()
if sErr, ok := err.(mongo.ServerError); ok {
    fmt.Println(sErr.HasErrorCode(21))
}

In that case, creating a new unexported serverError that is implemented by errors returned by packages in x would not satisfy an expected use case of ServerError.

@benjirewis benjirewis requested review from iwysiu and kevinAlbs May 13, 2021 16:50
@benjirewis benjirewis marked this pull request as ready for review May 13, 2021 16:53
Copy link
Contributor

@iwysiu iwysiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

driver.Error can also implement this interface

@@ -202,13 +202,13 @@ type ServerError interface {
HasErrorMessage(string) bool
// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
HasErrorCodeWithMessage(int, string) bool

serverError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily think its better, but we could alternatively have a drivers.ServerError interface that has the same functions and have the helpers that check for the ServerError interface also check for the drivers.ServerError interface.

@benjirewis benjirewis requested a review from iwysiu May 13, 2021 21:35
Copy link
Contributor

@kevinAlbs kevinAlbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment #651 (comment) spurred this ticket. But since this can have API impact, I have some high level questions about the motivation and alternatives.

I apologize for not asking about it sooner, I should have asked to clarify the motivation when triaging or the first round of review.

@@ -202,13 +202,13 @@ type ServerError interface {
HasErrorMessage(string) bool
// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
HasErrorCodeWithMessage(int, string) bool

serverError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too familiar with the nuances of whether this should be a sealed interface.

No functions take a ServerError as a direct argument. The helper IsDuplicateKeyError could accept an error that the user has defined which implements ServerError. But that seems harmless to me.

Are there other risks?

}

var _ ServerError = CommandError{}
var _ ServerError = WriteException{}
var _ ServerError = BulkWriteException{}
var _ ServerError = driver.WriteCommandError{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would returning a driver.WriteCommandError or driver.WriteConcernError just be a bug?

IIUC, processWriteError rewrites them in terms of mongo errors

}

var _ ServerError = CommandError{}
var _ ServerError = WriteException{}
var _ ServerError = BulkWriteException{}
var _ ServerError = driver.WriteCommandError{}
var _ ServerError = driver.WriteConcernError{}
var _ ServerError = driver.Error{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue that spurred this ticket was a deeply wrapped driver.Error that contained error labels: #651 (comment)

Would a preferable alternative be to have the error translation functions translate wrapped errors as well? E.g. have replaceErrors also inspect wrapped errors?

@iwysiu would the fix of GODRIVER-1991 solve this problem entirely? If no errors are returned in x, I imagine the fix would involve having to rewrite wrapped errors as well.

@benjirewis benjirewis requested a review from matthewdale May 20, 2021 18:25
@benjirewis benjirewis marked this pull request as draft June 1, 2021 17:48
@benjirewis benjirewis closed this Jun 11, 2021
@benjirewis benjirewis deleted the driverServerErrors.1992 branch October 24, 2022 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants