-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
feat(NODE-5484)!: mark MongoError for internal use and remove Node14 cause assignment logic #3800
Conversation
…de14 cause assignment logic
if (cause) this.cause = cause; | ||
/** @internal */ | ||
constructor(message: string, options: { cause?: Error } = {}) { | ||
super(message, options); | ||
} |
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.
What exactly is it that adding @internal
is supposed to achieve? It won’t prevent users from creating class instances, it’s only going to make the class look like it doesn’t have its own constructor, i.e. uses the constructor(...args) { super(...args); }
one.
If you want to prevent users from creating instances of these classes, you may need to do something like
export class MongoSomethingError extends MongoError {
private constructor(...) {}
/** @internal */
static create(...args) { return new this(...args); }
}
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.
We're doing this in an attempt to communicate that these classes shouldn't be instantiated by users. It's a pattern we have a lot elsewhere in the driver that probably could use some work with regards to getting that message across.
Wouldn't this solution also have the same effect? The user might not have documentation for the static create
method, but they would still be able to call it on their end, right?
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 get the intention here, and I agree that it's a goal that's worth putting effort into. :)
The problem is specifically with marking constructors as @internal
, which is different from all other methods, because that it does not make it appear to consumers of your .d.ts file that there is no publicly accessible constructor; instead, the TS definition effectively says that "the constructor of this class has the same signature as the constructor of the parent class".
That's different with a static method; yeah, of course users could still call it by casting the class to as any
or just not using TS validation at all, but at least it would not show up in the generated TS definitions.
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.
Okay, I see what you're saying now. I'll check with the team to see if we want to start with that new pattern here or deal with it in a separate ticket that takes care of this across the driver. Thanks for the clarification!
etc/notes/errors.md
Outdated
@@ -144,6 +136,21 @@ These are errors which originate from faulty environment setup. | |||
- #### MongoServerSelectionError | |||
- Thrown when the driver fails to select a server to complete an operation | |||
|
|||
### `MongoCryptError` | |||
|
|||
These are errors thrown from the `mongodb-client-encryption` bindings |
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.
Rather than linking the package, maybe we could just say that these errors are thrown from the driver's CSFLE logic?
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 the error descriptions were a little clunky - can we reword them?
Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
test/unit/error.test.ts
Outdated
@@ -99,7 +99,7 @@ describe('MongoErrors', () => { | |||
expect(err).to.be.an.instanceof(Error); | |||
expect(err.name).to.equal('MongoError'); | |||
expect(err.message).to.equal(errorMessage); | |||
expect(err).to.have.property('cause', inputError); | |||
expect(err).to.not.have.property('cause'); |
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.
Why did this change? IIUC we still want errors passed as the first argument to become the cause
. Unless we want to go through and make sure we stop passing errors as the first argument and move them to the options.cause
?
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 say no, and that we should fix all places that rely on the message
being assigned to the cause
now. There are only a few errors that provide constructors that take errors as the first property.
Relying solely on the cause is simpler and more aligned with how errors work in JS generally. Also, it avoid the ambiguous situation where both an error message
is provided and a cause
is as well (
new MongoError(
new Error('ahh'),
{ cause: new Error('other error') }
) // what is `cause` assigned to?
the ticket does note that the signature of MongoError should be constructor(message: string, { cause?: AnyError })
and it currently doesn't match this, good catch
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.
Updated errors. Going through the tests to update their assertions now.
src/error.ts
Outdated
* | ||
* @public | ||
**/ | ||
constructor(message: string | Error, options?: { cause?: Error }) { | ||
constructor(message: string, options?: { cause?: Error }) { | ||
super(MongoError.buildErrorMessage(message), options); |
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.
buildErrorMessage
relied on being passed the potential AggregateError, this change should result in some test failures.
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.
Is this comment suggesting we fix the tests or that we revert the change?
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.
Either, I don't feel strongly. I don't see test failures which is intriguing, maybe because the tests are still just passing in an error as the first argument?
src/cmap/connect.ts
Outdated
@@ -502,7 +502,7 @@ function makeSocks5Connection(options: MakeConnectionOptions, callback: Callback | |||
function connectionFailureError(type: ErrorHandlerEventName, err: Error) { | |||
switch (type) { | |||
case 'error': | |||
return new MongoNetworkError(err); | |||
return new MongoNetworkError('error', { cause: err }); |
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 going to suggest stealing the message from the error here but I think this is our aggregate error case so the message will be an empty string.
Maybe we allow the string argument to be nullish, then buildMessage's job will be to obtain it from the cause, or we make this say "error during connection establishment"
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'm a fan of the "make all consumers specify the error message". It's more explicit and it makes it easier to reason about what error messages are for given errors.
If we want to handle aggregate errors, there's nothing stopping us from using buildErrorMessage
wherever we want:
return new MongoNetworkError(MongoError.buildErrorMessage(err), { cause: err });
Description
What is changing?
MongoError
and allMongoError
subclasses now have documentation stating that they are no longer covered under semverMongoError
constructor now defers to Node16+'s Error constructor to set thecause
fieldMongoCryptError
is now a subclass ofMongoError
MongoCryptError
are subclasses ofMongoError
MongoCrypt
to error hierarchyIs there new documentation needed for these changes?
No
What is the motivation for this change?
NODE-5484
Release Highlight
MongoError
and its subclasses now have internal constructorsMongoError
and its subclasses are not meant to be constructed by users as they are thrown within the driver on specific error conditions to allow users to react to these conditions in ways which match their use cases. The constructors for these types are now subject to change outside of major versions and their documentation updated to reflect this.MongoCryptError
is now a subclass ofMongoError
Since
MongoCryptError
made use of NodeJS 16'sError
API, it has long supported setting theError.cause
field using options passed in via the constructor. Now that Node 16 is our minimum supported version,MongoError
has been modified to make use of this API as well, allowing us to letMongoCryptError
subclass from it directly.Double check the following
npm run check:lint
scripttype(NODE-xxxx)[!]: description
feat(NODE-1234)!: rewriting everything in coffeescript