Skip to content

Commit

Permalink
chore: fix wc throw location
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken committed Jun 4, 2024
1 parent 1c10a1f commit be871b8
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 96 deletions.
4 changes: 2 additions & 2 deletions src/client-side-encryption/client_encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,8 @@ export class ClientEncryption {
});
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);

const result = deserialize(await stateMachine.execute(this, context));
return result.v;
const { v } = deserialize(await stateMachine.execute(this, context));
return v;
}
}

Expand Down
31 changes: 20 additions & 11 deletions src/client-side-encryption/state_machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,25 @@ export type CSFLEKMSTlsOptions = {
azure?: ClientEncryptionTlsOptions;
};

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

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

if (keys.length === 0) {
// This is kind of a hack. For `rewrapManyDataKey`, we have tests that
// guarantee that when there are no matching keys, `rewrapManyDataKey` returns
// nothing. We also have tests for auto encryption that guarantee for `encrypt`
// we return an error when there are no matching keys. This error is generated in
// subsequent iterations of the state machine.
// Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
// do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
// will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
// otherwise we'll return `{ v: [] }`.
// See docs on EMPTY_V
result = EMPTY_V;
}
for await (const key of keys) {
Expand Down
12 changes: 9 additions & 3 deletions src/cmap/wire_protocol/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export type MongoDBResponseConstructor = {

/** @internal */
export class MongoDBResponse extends OnDemandDocument {
/**
* Devtools need to know which keys were encrypted before the driver automatically decrypted them.
* If decorating is enabled (`Symbol.for('@@mdb.decorateDecryptionResult')`), this field will be set,
* storing the original encrypted response from the server, so that we can build an object that has
* the list of BSON keys that were encrypted stored at a well known symbol: `Symbol.for('@@mdb.decryptedKeys')`.
*/
encryptedResponse?: MongoDBResponse;

static is(value: unknown): value is MongoDBResponse {
return value instanceof MongoDBResponse;
}
Expand Down Expand Up @@ -161,13 +169,11 @@ export class MongoDBResponse extends OnDemandDocument {
}
return { utf8: { writeErrors: false } };
}

// TODO: Supports decorating result
encryptedResponse?: MongoDBResponse;
}

// Here's a little blast from the past.
// OLD style method definition so that I can override get without redefining ALL the fancy TS :/
// TODO there must be a better way...
Object.defineProperty(MongoDBResponse.prototype, 'get', {
value: function get(name: any, as: any, required: any) {
try {
Expand Down
7 changes: 1 addition & 6 deletions src/cursor/abstract_cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,7 @@ export abstract class AbstractCursor<

return bufferedDocs;
}

private async *asyncIterator() {
async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
if (this.closed) {
return;
}
Expand Down Expand Up @@ -343,10 +342,6 @@ export abstract class AbstractCursor<
}
}

async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
yield* this.asyncIterator();
}

stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
if (options?.transform) {
const transform = options.transform;
Expand Down
21 changes: 10 additions & 11 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,17 +1177,16 @@ export class MongoWriteConcernError extends MongoServerError {
*
* @public
**/
constructor(
result: {
writeConcernError: {
code: number;
errmsg: string;
codeName?: string;
errInfo?: Document;
};
} & Document
) {
super(result.writeConcernError);
constructor(result: {
writeConcernError: {
code: number;
errmsg: string;
codeName?: string;
errInfo?: Document;
};
errorLabels?: string[];
}) {
super({ ...result, ...result.writeConcernError });
this.errInfo = result.writeConcernError.errInfo;
this.result = result;
}
Expand Down
5 changes: 1 addition & 4 deletions src/operations/bulk_write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
import type { Collection } from '../collection';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { throwIfWriteConcernError } from '../utils';
import { AbstractOperation, Aspect, defineAspects } from './operation';

/** @internal */
Expand Down Expand Up @@ -51,9 +50,7 @@ export class BulkWriteOperation extends AbstractOperation<BulkWriteResult> {
}

// Execute the bulk
const result = await bulk.execute({ ...options, session });
throwIfWriteConcernError(result);
return result;
return await bulk.execute({ ...options, session });
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/operations/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { MongoCompatibilityError, MongoServerError } from '../error';
import { type TODO_NODE_3286 } from '../mongo_types';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { type MongoDBNamespace, throwIfWriteConcernError } from '../utils';
import type { WriteConcernOptions } from '../write_concern';
import { type MongoDBNamespace } from '../utils';
import { type WriteConcernOptions } from '../write_concern';
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
import { Aspect, defineAspects, type Hint } from './operation';

Expand Down Expand Up @@ -96,7 +96,6 @@ export class DeleteOperation extends CommandOperation<DeleteResult> {
}

const res: TODO_NODE_3286 = await super.executeCommand(server, session, command);
throwIfWriteConcernError(res);
return res;
}
}
Expand Down
10 changes: 2 additions & 8 deletions src/operations/find_and_modify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,8 @@ import { ReadPreference } from '../read_preference';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { formatSort, type Sort, type SortForCmd } from '../sort';
import {
decorateWithCollation,
hasAtomicOperators,
maxWireVersion,
throwIfWriteConcernError
} from '../utils';
import type { WriteConcern, WriteConcernSettings } from '../write_concern';
import { decorateWithCollation, hasAtomicOperators, maxWireVersion } from '../utils';
import { type WriteConcern, type WriteConcernSettings } from '../write_concern';
import { CommandOperation, type CommandOperationOptions } from './command';
import { Aspect, defineAspects } from './operation';

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

// Execute the command
const result = await super.executeCommand(server, session, cmd);
throwIfWriteConcernError(result);
return options.includeResultMetadata ? result : result.value ?? null;
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MongoCompatibilityError, MongoInvalidArgumentError, MongoServerError }
import type { InferIdType, TODO_NODE_3286 } from '../mongo_types';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { hasAtomicOperators, type MongoDBNamespace, throwIfWriteConcernError } from '../utils';
import { hasAtomicOperators, type MongoDBNamespace } from '../utils';
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
import { Aspect, defineAspects, type Hint } from './operation';

Expand Down Expand Up @@ -123,7 +123,6 @@ export class UpdateOperation extends CommandOperation<Document> {
}

const res = await super.executeCommand(server, session, command);
throwIfWriteConcernError(res);
return res;
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/sdam/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ import {
makeStateMachine,
maxWireVersion,
type MongoDBNamespace,
supportsRetryableWrites,
throwIfWriteConcernError
supportsRetryableWrites
} from '../utils';
import { throwIfWriteConcernError } from '../write_concern';
import {
type ClusterTime,
STATE_CLOSED,
Expand Down Expand Up @@ -337,7 +337,9 @@ export class Server extends TypedEventEmitter<ServerEvents> {
) {
await this.pool.reauthenticate(conn);
try {
return await conn.command(ns, cmd, finalOptions, responseType);
const res = await conn.command(ns, cmd, finalOptions, responseType);
throwIfWriteConcernError(res);
return res;
} catch (commandError) {
throw this.decorateCommandError(conn, cmd, finalOptions, commandError);
}
Expand Down
20 changes: 1 addition & 19 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { promisify } from 'util';
import { deserialize, type Document, ObjectId, resolveBSONOptions } from './bson';
import type { Connection } from './cmap/connection';
import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';
import { MongoDBResponse } from './cmap/wire_protocol/responses';
import type { Collection } from './collection';
import { kDecoratedKeys, LEGACY_HELLO_COMMAND } from './constants';
import type { AbstractCursor } from './cursor/abstract_cursor';
Expand All @@ -24,8 +23,7 @@ import {
MongoNetworkTimeoutError,
MongoNotConnectedError,
MongoParseError,
MongoRuntimeError,
MongoWriteConcernError
MongoRuntimeError
} from './error';
import type { Explain } from './explain';
import type { MongoClient } from './mongo_client';
Expand Down Expand Up @@ -1418,19 +1416,3 @@ export function decorateDecryptionResult(
decorateDecryptionResult(decrypted[k], originalValue, false);
}
}

/** Called with either a plain object or MongoDBResponse */
export function throwIfWriteConcernError(response: unknown): void {
if (typeof response === 'object' && response != null) {
const writeConcernError: object | null =
MongoDBResponse.is(response) && response.has('writeConcernError')
? response.toObject()
: !MongoDBResponse.is(response) && 'writeConcernError' in response
? response
: null;

if (writeConcernError != null) {
throw new MongoWriteConcernError(writeConcernError as any);
}
}
}
18 changes: 18 additions & 0 deletions src/write_concern.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type Document } from './bson';
import { MongoDBResponse } from './cmap/wire_protocol/responses';
import { MongoWriteConcernError } from './error';

/** @public */
export type W = number | 'majority';
Expand Down Expand Up @@ -159,3 +161,19 @@ export class WriteConcern {
return undefined;
}
}

/** Called with either a plain object or MongoDBResponse */
export function throwIfWriteConcernError(response: unknown): void {
if (typeof response === 'object' && response != null) {
const writeConcernError: object | null =
MongoDBResponse.is(response) && response.has('writeConcernError')
? response.toObject()
: !MongoDBResponse.is(response) && 'writeConcernError' in response
? response
: null;

if (writeConcernError != null) {
throw new MongoWriteConcernError(writeConcernError as any);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ describe('Non Server Retryable Writes', function () {
async () => {
const serverCommandStub = sinon.stub(Server.prototype, 'command');
serverCommandStub.onCall(0).rejects(new PoolClearedError('error'));
serverCommandStub
.onCall(1)
.returns(
Promise.reject(
new MongoWriteConcernError({ errorLabels: ['NoWritesPerformed'], errorCode: 10107 }, {})
)
);
serverCommandStub.onCall(1).returns(
Promise.reject(
new MongoWriteConcernError({
errorLabels: ['NoWritesPerformed'],
writeConcernError: { errmsg: 'NotWritablePrimary error', errorCode: 10107 }
})
)
);

const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
sinon.restore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,23 +277,22 @@ describe('Retryable Writes Spec Prose', () => {
{ requires: { topology: 'replicaset', mongodb: '>=4.2.9' } },
async () => {
const serverCommandStub = sinon.stub(Server.prototype, 'command');
serverCommandStub
.onCall(0)
.returns(
Promise.reject(
new MongoWriteConcernError({ errorLabels: ['RetryableWriteError'], code: 91 }, {})
)
);
serverCommandStub
.onCall(1)
.returns(
Promise.reject(
new MongoWriteConcernError(
{ errorLabels: ['RetryableWriteError', 'NoWritesPerformed'], errorCode: 10107 },
{}
)
)
);
serverCommandStub.onCall(0).returns(
Promise.reject(
new MongoWriteConcernError({
errorLabels: ['RetryableWriteError'],
writeConcernError: { errmsg: 'ShutdownInProgress error', code: 91 }
})
)
);
serverCommandStub.onCall(1).returns(
Promise.reject(
new MongoWriteConcernError({
errorLabels: ['RetryableWriteError', 'NoWritesPerformed'],
writeConcernError: { errmsg: 'NotWritablePrimary error', errorCode: 10107 }
})
)
);

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

0 comments on commit be871b8

Please sign in to comment.