Skip to content

Commit 31efa9c

Browse files
committed
chore: fix wc throw location
1 parent d9604b3 commit 31efa9c

File tree

14 files changed

+96
-96
lines changed

14 files changed

+96
-96
lines changed

src/client-side-encryption/client_encryption.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -719,8 +719,8 @@ export class ClientEncryption {
719719
});
720720
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
721721

722-
const result = deserialize(await stateMachine.execute(this, context));
723-
return result.v;
722+
const { v } = deserialize(await stateMachine.execute(this, context));
723+
return v;
724724
}
725725
}
726726

src/client-side-encryption/state_machine.ts

+20-11
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,25 @@ export type CSFLEKMSTlsOptions = {
112112
azure?: ClientEncryptionTlsOptions;
113113
};
114114

115-
/** `{ v: [] }` */
116-
const EMPTY_V = Uint8Array.from([13, 0, 0, 0, 4, 118, 0, 5, 0, 0, 0, 0, 0]);
115+
/**
116+
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
117+
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
118+
* nothing. We also have tests for auto encryption that guarantee for `encrypt`
119+
* we return an error when there are no matching keys. This error is generated in
120+
* subsequent iterations of the state machine.
121+
* Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
122+
* do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
123+
* will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
124+
* otherwise we'll return `{ v: [] }`.
125+
*/
126+
const EMPTY_V = Uint8Array.from([
127+
...[13, 0, 0, 0], // document size = 13 bytes
128+
...[
129+
...[4, 118, 0], // array type (4), "v\x00" basic latin "v"
130+
...[5, 0, 0, 0, 0] // empty document (5 byte size, null terminator)
131+
],
132+
0 // null terminator
133+
]);
117134

118135
/**
119136
* @internal
@@ -211,15 +228,7 @@ export class StateMachine {
211228
const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter);
212229

213230
if (keys.length === 0) {
214-
// This is kind of a hack. For `rewrapManyDataKey`, we have tests that
215-
// guarantee that when there are no matching keys, `rewrapManyDataKey` returns
216-
// nothing. We also have tests for auto encryption that guarantee for `encrypt`
217-
// we return an error when there are no matching keys. This error is generated in
218-
// subsequent iterations of the state machine.
219-
// Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
220-
// do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
221-
// will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
222-
// otherwise we'll return `{ v: [] }`.
231+
// See docs on EMPTY_V
223232
result = EMPTY_V;
224233
}
225234
for await (const key of keys) {

src/cmap/wire_protocol/responses.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ export type MongoDBResponseConstructor = {
6666

6767
/** @internal */
6868
export class MongoDBResponse extends OnDemandDocument {
69+
/**
70+
* Devtools need to know which keys were encrypted before the driver automatically decrypted them.
71+
* If decorating is enabled (`Symbol.for('@@mdb.decorateDecryptionResult')`), this field will be set,
72+
* storing the original encrypted response from the server, so that we can build an object that has
73+
* the list of BSON keys that were encrypted stored at a well known symbol: `Symbol.for('@@mdb.decryptedKeys')`.
74+
*/
75+
encryptedResponse?: MongoDBResponse;
76+
6977
static is(value: unknown): value is MongoDBResponse {
7078
return value instanceof MongoDBResponse;
7179
}
@@ -161,13 +169,11 @@ export class MongoDBResponse extends OnDemandDocument {
161169
}
162170
return { utf8: { writeErrors: false } };
163171
}
164-
165-
// TODO: Supports decorating result
166-
encryptedResponse?: MongoDBResponse;
167172
}
168173

169174
// Here's a little blast from the past.
170175
// OLD style method definition so that I can override get without redefining ALL the fancy TS :/
176+
// TODO there must be a better way...
171177
Object.defineProperty(MongoDBResponse.prototype, 'get', {
172178
value: function get(name: any, as: any, required: any) {
173179
try {

src/cursor/abstract_cursor.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,7 @@ export abstract class AbstractCursor<
294294

295295
return bufferedDocs;
296296
}
297-
298-
private async *asyncIterator() {
297+
async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
299298
if (this.closed) {
300299
return;
301300
}
@@ -343,10 +342,6 @@ export abstract class AbstractCursor<
343342
}
344343
}
345344

346-
async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
347-
yield* this.asyncIterator();
348-
}
349-
350345
stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
351346
if (options?.transform) {
352347
const transform = options.transform;

src/error.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -1177,17 +1177,16 @@ export class MongoWriteConcernError extends MongoServerError {
11771177
*
11781178
* @public
11791179
**/
1180-
constructor(
1181-
result: {
1182-
writeConcernError: {
1183-
code: number;
1184-
errmsg: string;
1185-
codeName?: string;
1186-
errInfo?: Document;
1187-
};
1188-
} & Document
1189-
) {
1190-
super(result.writeConcernError);
1180+
constructor(result: {
1181+
writeConcernError: {
1182+
code: number;
1183+
errmsg: string;
1184+
codeName?: string;
1185+
errInfo?: Document;
1186+
};
1187+
errorLabels?: string[];
1188+
}) {
1189+
super({ ...result, ...result.writeConcernError });
11911190
this.errInfo = result.writeConcernError.errInfo;
11921191
this.result = result;
11931192
}

src/operations/bulk_write.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type {
77
import type { Collection } from '../collection';
88
import type { Server } from '../sdam/server';
99
import type { ClientSession } from '../sessions';
10-
import { throwIfWriteConcernError } from '../utils';
1110
import { AbstractOperation, Aspect, defineAspects } from './operation';
1211

1312
/** @internal */
@@ -51,9 +50,7 @@ export class BulkWriteOperation extends AbstractOperation<BulkWriteResult> {
5150
}
5251

5352
// Execute the bulk
54-
const result = await bulk.execute({ ...options, session });
55-
throwIfWriteConcernError(result);
56-
return result;
53+
return await bulk.execute({ ...options, session });
5754
}
5855
}
5956

src/operations/delete.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { MongoCompatibilityError, MongoServerError } from '../error';
44
import { type TODO_NODE_3286 } from '../mongo_types';
55
import type { Server } from '../sdam/server';
66
import type { ClientSession } from '../sessions';
7-
import { type MongoDBNamespace, throwIfWriteConcernError } from '../utils';
8-
import type { WriteConcernOptions } from '../write_concern';
7+
import { type MongoDBNamespace } from '../utils';
8+
import { type WriteConcernOptions } from '../write_concern';
99
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
1010
import { Aspect, defineAspects, type Hint } from './operation';
1111

@@ -96,7 +96,6 @@ export class DeleteOperation extends CommandOperation<DeleteResult> {
9696
}
9797

9898
const res: TODO_NODE_3286 = await super.executeCommand(server, session, command);
99-
throwIfWriteConcernError(res);
10099
return res;
101100
}
102101
}

src/operations/find_and_modify.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,8 @@ import { ReadPreference } from '../read_preference';
55
import type { Server } from '../sdam/server';
66
import type { ClientSession } from '../sessions';
77
import { formatSort, type Sort, type SortForCmd } from '../sort';
8-
import {
9-
decorateWithCollation,
10-
hasAtomicOperators,
11-
maxWireVersion,
12-
throwIfWriteConcernError
13-
} from '../utils';
14-
import type { WriteConcern, WriteConcernSettings } from '../write_concern';
8+
import { decorateWithCollation, hasAtomicOperators, maxWireVersion } from '../utils';
9+
import { type WriteConcern, type WriteConcernSettings } from '../write_concern';
1510
import { CommandOperation, type CommandOperationOptions } from './command';
1611
import { Aspect, defineAspects } from './operation';
1712

@@ -219,7 +214,6 @@ export class FindAndModifyOperation extends CommandOperation<Document> {
219214

220215
// Execute the command
221216
const result = await super.executeCommand(server, session, cmd);
222-
throwIfWriteConcernError(result);
223217
return options.includeResultMetadata ? result : result.value ?? null;
224218
}
225219
}

src/operations/update.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { MongoCompatibilityError, MongoInvalidArgumentError, MongoServerError }
44
import type { InferIdType, TODO_NODE_3286 } from '../mongo_types';
55
import type { Server } from '../sdam/server';
66
import type { ClientSession } from '../sessions';
7-
import { hasAtomicOperators, type MongoDBNamespace, throwIfWriteConcernError } from '../utils';
7+
import { hasAtomicOperators, type MongoDBNamespace } from '../utils';
88
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
99
import { Aspect, defineAspects, type Hint } from './operation';
1010

@@ -123,7 +123,6 @@ export class UpdateOperation extends CommandOperation<Document> {
123123
}
124124

125125
const res = await super.executeCommand(server, session, command);
126-
throwIfWriteConcernError(res);
127126
return res;
128127
}
129128
}

src/sdam/server.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ import {
4646
makeStateMachine,
4747
maxWireVersion,
4848
type MongoDBNamespace,
49-
supportsRetryableWrites,
50-
throwIfWriteConcernError
49+
supportsRetryableWrites
5150
} from '../utils';
51+
import { throwIfWriteConcernError } from '../write_concern';
5252
import {
5353
type ClusterTime,
5454
STATE_CLOSED,
@@ -337,7 +337,9 @@ export class Server extends TypedEventEmitter<ServerEvents> {
337337
) {
338338
await this.pool.reauthenticate(conn);
339339
try {
340-
return await conn.command(ns, cmd, finalOptions, responseType);
340+
const res = await conn.command(ns, cmd, finalOptions, responseType);
341+
throwIfWriteConcernError(res);
342+
return res;
341343
} catch (commandError) {
342344
throw this.decorateCommandError(conn, cmd, finalOptions, commandError);
343345
}

src/utils.ts

+1-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { promisify } from 'util';
1111
import { deserialize, type Document, ObjectId, resolveBSONOptions } from './bson';
1212
import type { Connection } from './cmap/connection';
1313
import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';
14-
import { MongoDBResponse } from './cmap/wire_protocol/responses';
1514
import type { Collection } from './collection';
1615
import { kDecoratedKeys, LEGACY_HELLO_COMMAND } from './constants';
1716
import type { AbstractCursor } from './cursor/abstract_cursor';
@@ -24,8 +23,7 @@ import {
2423
MongoNetworkTimeoutError,
2524
MongoNotConnectedError,
2625
MongoParseError,
27-
MongoRuntimeError,
28-
MongoWriteConcernError
26+
MongoRuntimeError
2927
} from './error';
3028
import type { Explain } from './explain';
3129
import type { MongoClient } from './mongo_client';
@@ -1418,19 +1416,3 @@ export function decorateDecryptionResult(
14181416
decorateDecryptionResult(decrypted[k], originalValue, false);
14191417
}
14201418
}
1421-
1422-
/** Called with either a plain object or MongoDBResponse */
1423-
export function throwIfWriteConcernError(response: unknown): void {
1424-
if (typeof response === 'object' && response != null) {
1425-
const writeConcernError: object | null =
1426-
MongoDBResponse.is(response) && response.has('writeConcernError')
1427-
? response.toObject()
1428-
: !MongoDBResponse.is(response) && 'writeConcernError' in response
1429-
? response
1430-
: null;
1431-
1432-
if (writeConcernError != null) {
1433-
throw new MongoWriteConcernError(writeConcernError as any);
1434-
}
1435-
}
1436-
}

src/write_concern.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { type Document } from './bson';
2+
import { MongoDBResponse } from './cmap/wire_protocol/responses';
3+
import { MongoWriteConcernError } from './error';
24

35
/** @public */
46
export type W = number | 'majority';
@@ -159,3 +161,19 @@ export class WriteConcern {
159161
return undefined;
160162
}
161163
}
164+
165+
/** Called with either a plain object or MongoDBResponse */
166+
export function throwIfWriteConcernError(response: unknown): void {
167+
if (typeof response === 'object' && response != null) {
168+
const writeConcernError: object | null =
169+
MongoDBResponse.is(response) && response.has('writeConcernError')
170+
? response.toObject()
171+
: !MongoDBResponse.is(response) && 'writeConcernError' in response
172+
? response
173+
: null;
174+
175+
if (writeConcernError != null) {
176+
throw new MongoWriteConcernError(writeConcernError as any);
177+
}
178+
}
179+
}

test/integration/retryable-writes/non-server-retryable_writes.test.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ describe('Non Server Retryable Writes', function () {
3434
async () => {
3535
const serverCommandStub = sinon.stub(Server.prototype, 'command');
3636
serverCommandStub.onCall(0).rejects(new PoolClearedError('error'));
37-
serverCommandStub
38-
.onCall(1)
39-
.returns(
40-
Promise.reject(
41-
new MongoWriteConcernError({ errorLabels: ['NoWritesPerformed'], errorCode: 10107 }, {})
42-
)
43-
);
37+
serverCommandStub.onCall(1).returns(
38+
Promise.reject(
39+
new MongoWriteConcernError({
40+
errorLabels: ['NoWritesPerformed'],
41+
writeConcernError: { errmsg: 'NotWritablePrimary error', errorCode: 10107 }
42+
})
43+
)
44+
);
4445

4546
const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
4647
sinon.restore();

test/integration/retryable-writes/retryable_writes.spec.prose.test.ts

+16-17
Original file line numberDiff line numberDiff line change
@@ -277,23 +277,22 @@ describe('Retryable Writes Spec Prose', () => {
277277
{ requires: { topology: 'replicaset', mongodb: '>=4.2.9' } },
278278
async () => {
279279
const serverCommandStub = sinon.stub(Server.prototype, 'command');
280-
serverCommandStub
281-
.onCall(0)
282-
.returns(
283-
Promise.reject(
284-
new MongoWriteConcernError({ errorLabels: ['RetryableWriteError'], code: 91 }, {})
285-
)
286-
);
287-
serverCommandStub
288-
.onCall(1)
289-
.returns(
290-
Promise.reject(
291-
new MongoWriteConcernError(
292-
{ errorLabels: ['RetryableWriteError', 'NoWritesPerformed'], errorCode: 10107 },
293-
{}
294-
)
295-
)
296-
);
280+
serverCommandStub.onCall(0).returns(
281+
Promise.reject(
282+
new MongoWriteConcernError({
283+
errorLabels: ['RetryableWriteError'],
284+
writeConcernError: { errmsg: 'ShutdownInProgress error', code: 91 }
285+
})
286+
)
287+
);
288+
serverCommandStub.onCall(1).returns(
289+
Promise.reject(
290+
new MongoWriteConcernError({
291+
errorLabels: ['RetryableWriteError', 'NoWritesPerformed'],
292+
writeConcernError: { errmsg: 'NotWritablePrimary error', errorCode: 10107 }
293+
})
294+
)
295+
);
297296

298297
const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
299298
sinon.restore();

0 commit comments

Comments
 (0)