-
Notifications
You must be signed in to change notification settings - Fork 700
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
[IMPROVED] Add JetStream error codes, extract ErrConsumerNameAlreadyInUse #1044
Conversation
One thing I'm not certain about is removing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work @piotrpio 👍, left some comments
return js.subscribe(subj, _EMPTY_, nil, mch, true, true, append(opts, Durable(durable))) | ||
if durable != "" { | ||
opts = append(opts, Durable(durable)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this change behavior to support the ephemerals?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, pull ephemerals are already supported. This change is introduced because nats.Durable()
should not accept empty consumer name (or else checkConsumerName()
will fail)
jsm.go
Outdated
return nil, err | ||
} | ||
if consInfo != nil { | ||
return nil, fmt.Errorf("creating consumer %q on stream %q: %w", cfg.Durable, stream, ErrConsumerNameAlreadyInUse) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think need to add nats:
prefix here? maybe not an error if the intended config is the same as the result of Info (idempotent) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, idempotency check is a good idea. As to nats:
prefix, I'll just reverse the order of the message and put ErrConsumerNameAlreadyInUse
at the beginning.
@@ -268,6 +285,30 @@ type consumerResponse struct { | |||
|
|||
// AddConsumer will add a JetStream consumer. | |||
func (js *js) AddConsumer(stream string, cfg *ConsumerConfig, opts ...JSOpt) (*ConsumerInfo, error) { | |||
if cfg != nil && cfg.Durable != _EMPTY_ { | |||
consInfo, err := js.ConsumerInfo(stream, cfg.Durable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is good to call Info before, but also changes the behavior slightly since need to give permissions to info in across account cases which they probably would have anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I would lean towards leaving that, as without that AddConsumer()
I feel does not work as the name suggests (it will simply update a consumer if it already exists).
jsm.go
Outdated
ConsumerNameExists ErrorCode = 10013 | ||
ConsumerAlreadyExists ErrorCode = 10105 | ||
|
||
MessageNotFound ErrorCode = 10037 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit worried that we are putting too many of these into the nats namespace package, wonder if it is time to move them into their own package... Or maybe suffix them like MessageNotFoundErrorCode
? code in the client then would read like: esp.Error.ErrorCode == MessageNotFoundErrorCode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about extracting those codes together with errors to a separate package, but moving already defined errors would be a breaking change, so I decided against it. I think the format with ErrorCode
prefix makes sense, I'll change that.
@@ -144,7 +144,6 @@ var ( | |||
ErrNoStreamResponse = errors.New("nats: no response from stream") | |||
ErrNotJSMessage = errors.New("nats: not a jetstream message") | |||
ErrInvalidStreamName = errors.New("nats: invalid stream name") | |||
ErrInvalidDurableName = errors.New("nats: invalid durable name") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably fine to remove though... maybe safer to mark as deprecated and that it will be removed since not used and use instead ErrConsumerNameAlreadyInUse
?
jsm.go
Outdated
@@ -384,10 +414,10 @@ func (js *js) DeleteConsumer(stream, consumer string, opts ...JSOpt) error { | |||
} | |||
|
|||
if resp.Error != nil { | |||
if resp.Error.Code == 404 { | |||
if resp.Error.ErrorCode == ConsumerNotFound { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if we could make these ErrConsumerNotFound
plain error types, instead conform to the Error interface and maybe a JetStreamAPIError interface and stamp them with their error codes there, something like:
ErrConsumerNotFound = &apiError{ErrorCode: ...}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. I have an idea on how to make the implementation a bit cleaner, but that would require changing the way errors are compared:
- As you mentioned, change the defined error types to
APIError
, like so:
ErrConsumerNotFound = &APIError{ErrorCode: 10014, Code: 404, Description: "nats: consumer not found"}
- Implement custom
Is()
method onAPIError
, comparing error codes:
func (e *APIError) Is(target error) bool {
err, ok := target.(*APIError)
if !ok {
return false
}
return err.ErrorCode == e.ErrorCode && err.Code == e.Code
}
- Simplify client method implementations so that we do not have to compare the error codes manually at all, e.g.:
if info.Error != nil {
return nil, info.Error
}
instead of
if info.Error != nil {
if info.Error.ErrorCode == ConsumerNotFound {
return nil, ErrConsumerNotFound
}
if info.Error.ErrorCode == StreamNotFound {
return nil, ErrStreamNotFound
}
return nil, info.Error
}
However, that means user would not be able to check errors by simple comparison (err == ErrConsumerNotFound
), but instead rely on errors.Is(err, ErrConsumerNotFound)
(which usually should be the preferred way to go).
The other approach would be to leave the client method implementations untouched and simply change the variable definitions to be &APIError{}
instead of errors.New()
.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, what do you mean by implementing JetStreamAPIError
interface (i.e. what would it do). The error
interface is implemented as part of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put together an example of what we could do via an interface here: #1047
} else { | ||
err = errors.New(info.Error.Description) | ||
if info.Error.ErrorCode == JetStreamNotEnabledForAccount { | ||
return nil, ErrJetStreamNotEnabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the server not enabled for account and not enabled looks like they are two different errors actually... maybe we should start distinguishing them too and use ErrJetStreamNotEnabledForAccount
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another factor why maybe we should clean it up is that JetStreamNotEnabledForAccount
is a hard error since means that account can never use JetStream in current setup, whereas things are a bit less certain in ErrJetStreamNotEnabled
, it can be either a temporary error due to JS not ready yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, distinguishing these errors is a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Resolves #977
Additional changes in this PR:
*APIError
implementerror
interfaceErrInvalidDurable
andcheckDurName()
in favor ofErrInvalidConsumerName
andcheckConsumerName()
ErrJetStreamNotEnabledForAccount
error with error code when not enabled for an account instead of generalErrJetStreamNotEnabled