Skip to content

Commit 9f80b5b

Browse files
committed
feat: required fields by moving error check out of constructor
1 parent ac5118e commit 9f80b5b

File tree

2 files changed

+61
-7
lines changed

2 files changed

+61
-7
lines changed

src/cmap/connection.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ import type { ClientMetadata } from './handshake/client_metadata';
6262
import { StreamDescription, type StreamDescriptionOptions } from './stream_description';
6363
import { type CompressorName, decompressResponse } from './wire_protocol/compression';
6464
import { onData } from './wire_protocol/on_data';
65-
import { MongoDBResponse, type MongoDBResponseConstructor } from './wire_protocol/responses';
65+
import {
66+
isErrorResponse,
67+
MongoDBResponse,
68+
type MongoDBResponseConstructor
69+
} from './wire_protocol/responses';
6670
import { getReadPreference, isSharded } from './wire_protocol/shared';
6771

6872
/** @internal */
@@ -443,7 +447,12 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
443447
this.socket.setTimeout(0);
444448
const bson = response.parse();
445449

446-
const document = new (responseType ?? MongoDBResponse)(bson, 0, false);
450+
const document =
451+
responseType == null
452+
? new MongoDBResponse(bson)
453+
: isErrorResponse(bson)
454+
? new MongoDBResponse(bson)
455+
: new responseType(bson);
447456

448457
yield document;
449458
this.throwIfAborted();

src/cmap/wire_protocol/responses.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,60 @@ import {
33
BSONType,
44
type Document,
55
Long,
6+
parseToElementsToArray,
67
type Timestamp
78
} from '../../bson';
89
import { MongoUnexpectedServerResponseError } from '../../error';
910
import { type ClusterTime } from '../../sdam/common';
1011
import { type MongoDBNamespace, ns } from '../../utils';
1112
import { OnDemandDocument } from './on_demand/document';
1213

14+
// eslint-disable-next-line no-restricted-syntax
15+
const enum BSONElementOffset {
16+
type = 0,
17+
nameOffset = 1,
18+
nameLength = 2,
19+
offset = 3,
20+
length = 4
21+
}
22+
/**
23+
* Accepts a BSON payload and checks for na "ok: 0" element.
24+
* This utility is intended to prevent calling response class constructors
25+
* that expect the result to be a success and demand certain properties to exist.
26+
*
27+
* For example, a cursor response always expects a cursor embedded document.
28+
* In order to write the class such that the properties reflect that assertion (non-null)
29+
* we cannot invoke the subclass constructor if the BSON represents an error.
30+
*
31+
* @param bytes - BSON document returned from the server
32+
*/
33+
export function isErrorResponse(bson: Uint8Array): boolean {
34+
const elements = parseToElementsToArray(bson, 0);
35+
for (let eIdx = 0; eIdx < elements.length; eIdx++) {
36+
const element = elements[eIdx];
37+
38+
if (element[BSONElementOffset.nameLength] === 2) {
39+
const nameOffset = element[BSONElementOffset.nameOffset];
40+
41+
// 111 == "o", 107 == "k"
42+
if (bson[nameOffset] === 111 && bson[nameOffset + 1] === 107) {
43+
const valueOffset = element[BSONElementOffset.offset];
44+
const valueLength = element[BSONElementOffset.length];
45+
46+
// If any byte in the length of the ok number (works for any type) is non zero,
47+
// then it is considered "ok: 1"
48+
for (let i = valueOffset; i < valueOffset + valueLength; i++) {
49+
if (bson[i] !== 0x00) return false;
50+
}
51+
52+
return true;
53+
}
54+
}
55+
}
56+
57+
return true;
58+
}
59+
1360
/** @internal */
1461
export type MongoDBResponseConstructor = {
1562
new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
@@ -142,18 +189,16 @@ export class CursorResponse extends MongoDBResponse {
142189
return value instanceof CursorResponse;
143190
}
144191

145-
public id: Long | null = null;
146-
public ns: MongoDBNamespace | null = null;
192+
public id: Long;
193+
public ns: MongoDBNamespace;
147194
public batchSize = 0;
148195

149-
private batch: OnDemandDocument | null = null;
196+
private batch: OnDemandDocument;
150197
private iterated = 0;
151198

152199
constructor(bytes: Uint8Array, offset?: number, isArray?: boolean) {
153200
super(bytes, offset, isArray);
154201

155-
if (this.isError) return;
156-
157202
const cursor = this.get('cursor', BSONType.object, true);
158203

159204
const id = cursor.get('id', BSONType.long, true);

0 commit comments

Comments
 (0)