CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
deserializer.ts628 linesDownload Raw Back to parser
1import { Binary, UUID } from '../binary';2import type { Document } from '../bson';3import { Code } from '../code';4import * as constants from '../constants';5import { DBRef, type DBRefLike, isDBRefLike } from '../db_ref';6import { Decimal128 } from '../decimal128';7import { Double } from '../double';8import { BSONError } from '../error';9import { Int32 } from '../int_32';10import { Long } from '../long';11import { MaxKey } from '../max_key';12import { MinKey } from '../min_key';13import { ObjectId } from '../objectid';14import { BSONRegExp } from '../regexp';15import { BSONSymbol } from '../symbol';16import { Timestamp } from '../timestamp';17import { ByteUtils } from '../utils/byte_utils';18import { NumberUtils } from '../utils/number_utils';19 20/** @public */21export interface DeserializeOptions {22  /**23   * when deserializing a Long return as a BigInt.24   * @defaultValue `false`25   */26  useBigInt64?: boolean;27  /**28   * when deserializing a Long will fit it into a Number if it's smaller than 53 bits.29   * @defaultValue `true`30   */31  promoteLongs?: boolean;32  /**33   * when deserializing a Binary will return it as a node.js Buffer instance.34   * @defaultValue `false`35   */36  promoteBuffers?: boolean;37  /**38   * when deserializing will promote BSON values to their Node.js closest equivalent types.39   * @defaultValue `true`40   */41  promoteValues?: boolean;42  /**43   * allow to specify if there what fields we wish to return as unserialized raw buffer.44   * @defaultValue `null`45   */46  fieldsAsRaw?: Document;47  /**48   * return BSON regular expressions as BSONRegExp instances.49   * @defaultValue `false`50   */51  bsonRegExp?: boolean;52  /**53   * allows the buffer to be larger than the parsed BSON object.54   * @defaultValue `false`55   */56  allowObjectSmallerThanBufferSize?: boolean;57  /**58   * Offset into buffer to begin reading document from59   * @defaultValue `0`60   */61  index?: number;62 63  raw?: boolean;64  /** Allows for opt-out utf-8 validation for all keys or65   * specified keys. Must be all true or all false.66   *67   * @example68   * ```js69   * // disables validation on all keys70   *  validation: { utf8: false }71   *72   * // enables validation only on specified keys a, b, and c73   *  validation: { utf8: { a: true, b: true, c: true } }74   *75   *  // disables validation only on specified keys a, b76   *  validation: { utf8: { a: false, b: false } }77   * ```78   */79  validation?: { utf8: boolean | Record<string, true> | Record<string, false> };80}81 82// Internal long versions83const JS_INT_MAX_LONG = Long.fromNumber(constants.JS_INT_MAX);84const JS_INT_MIN_LONG = Long.fromNumber(constants.JS_INT_MIN);85 86export function internalDeserialize(87  buffer: Uint8Array,88  options: DeserializeOptions,89  isArray?: boolean90): Document {91  options = options == null ? {} : options;92  const index = options && options.index ? options.index : 0;93  // Read the document size94  const size = NumberUtils.getInt32LE(buffer, index);95 96  if (size < 5) {97    throw new BSONError(`bson size must be >= 5, is ${size}`);98  }99 100  if (options.allowObjectSmallerThanBufferSize && buffer.length < size) {101    throw new BSONError(`buffer length ${buffer.length} must be >= bson size ${size}`);102  }103 104  if (!options.allowObjectSmallerThanBufferSize && buffer.length !== size) {105    throw new BSONError(`buffer length ${buffer.length} must === bson size ${size}`);106  }107 108  if (size + index > buffer.byteLength) {109    throw new BSONError(110      `(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})`111    );112  }113 114  // Illegal end value115  if (buffer[index + size - 1] !== 0) {116    throw new BSONError(117      "One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00"118    );119  }120 121  // Start deserialization122  return deserializeObject(buffer, index, options, isArray);123}124 125const allowedDBRefKeys = /^\$ref$|^\$id$|^\$db$/;126 127function deserializeObject(128  buffer: Uint8Array,129  index: number,130  options: DeserializeOptions,131  isArray = false132) {133  const fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw'];134 135  // Return raw bson buffer instead of parsing it136  const raw = options['raw'] == null ? false : options['raw'];137 138  // Return BSONRegExp objects instead of native regular expressions139  const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false;140 141  // Controls the promotion of values vs wrapper classes142  const promoteBuffers = options.promoteBuffers ?? false;143  const promoteLongs = options.promoteLongs ?? true;144  const promoteValues = options.promoteValues ?? true;145  const useBigInt64 = options.useBigInt64 ?? false;146 147  if (useBigInt64 && !promoteValues) {148    throw new BSONError('Must either request bigint or Long for int64 deserialization');149  }150 151  if (useBigInt64 && !promoteLongs) {152    throw new BSONError('Must either request bigint or Long for int64 deserialization');153  }154 155  // Ensures default validation option if none given156  const validation = options.validation == null ? { utf8: true } : options.validation;157 158  // Shows if global utf-8 validation is enabled or disabled159  let globalUTFValidation = true;160  // Reflects utf-8 validation setting regardless of global or specific key validation161  let validationSetting: boolean;162  // Set of keys either to enable or disable validation on163  let utf8KeysSet;164 165  // Check for boolean uniformity and empty validation option166  const utf8ValidatedKeys = validation.utf8;167  if (typeof utf8ValidatedKeys === 'boolean') {168    validationSetting = utf8ValidatedKeys;169  } else {170    globalUTFValidation = false;171    const utf8ValidationValues = Object.keys(utf8ValidatedKeys).map(function (key) {172      return utf8ValidatedKeys[key];173    });174    if (utf8ValidationValues.length === 0) {175      throw new BSONError('UTF-8 validation setting cannot be empty');176    }177    if (typeof utf8ValidationValues[0] !== 'boolean') {178      throw new BSONError('Invalid UTF-8 validation option, must specify boolean values');179    }180    validationSetting = utf8ValidationValues[0];181    // Ensures boolean uniformity in utf-8 validation (all true or all false)182    if (!utf8ValidationValues.every(item => item === validationSetting)) {183      throw new BSONError('Invalid UTF-8 validation option - keys must be all true or all false');184    }185  }186 187  // Add keys to set that will either be validated or not based on validationSetting188  if (!globalUTFValidation) {189    utf8KeysSet = new Set();190 191    for (const key of Object.keys(utf8ValidatedKeys)) {192      utf8KeysSet.add(key);193    }194  }195 196  // Set the start index197  const startIndex = index;198 199  // Validate that we have at least 4 bytes of buffer200  if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long');201 202  // Read the document size203  const size = NumberUtils.getInt32LE(buffer, index);204  index += 4;205 206  // Ensure buffer is valid size207  if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message');208 209  // Create holding object210  const object: Document = isArray ? [] : {};211  // Used for arrays to skip having to perform utf8 decoding212  let arrayIndex = 0;213  const done = false;214 215  let isPossibleDBRef = isArray ? false : null;216 217  // While we have more left data left keep parsing218  while (!done) {219    // Read the type220    const elementType = buffer[index++];221 222    // If we get a zero it's the last byte, exit223    if (elementType === 0) break;224 225    // Get the start search index226    let i = index;227    // Locate the end of the c string228    while (buffer[i] !== 0x00 && i < buffer.length) {229      i++;230    }231 232    // If are at the end of the buffer there is a problem with the document233    if (i >= buffer.byteLength) throw new BSONError('Bad BSON Document: illegal CString');234 235    // Represents the key236    const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i, false);237 238    // shouldValidateKey is true if the key should be validated, false otherwise239    let shouldValidateKey = true;240    if (globalUTFValidation || utf8KeysSet?.has(name)) {241      shouldValidateKey = validationSetting;242    } else {243      shouldValidateKey = !validationSetting;244    }245 246    if (isPossibleDBRef !== false && (name as string)[0] === '$') {247      isPossibleDBRef = allowedDBRefKeys.test(name as string);248    }249    let value;250 251    index = i + 1;252 253    if (elementType === constants.BSON_DATA_STRING) {254      const stringSize = NumberUtils.getInt32LE(buffer, index);255      index += 4;256      if (257        stringSize <= 0 ||258        stringSize > buffer.length - index ||259        buffer[index + stringSize - 1] !== 0260      ) {261        throw new BSONError('bad string length in bson');262      }263      value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);264      index = index + stringSize;265    } else if (elementType === constants.BSON_DATA_OID) {266      const oid = ByteUtils.allocateUnsafe(12);267      for (let i = 0; i < 12; i++) oid[i] = buffer[index + i];268      value = new ObjectId(oid);269      index = index + 12;270    } else if (elementType === constants.BSON_DATA_INT && promoteValues === false) {271      value = new Int32(NumberUtils.getInt32LE(buffer, index));272      index += 4;273    } else if (elementType === constants.BSON_DATA_INT) {274      value = NumberUtils.getInt32LE(buffer, index);275      index += 4;276    } else if (elementType === constants.BSON_DATA_NUMBER) {277      value = NumberUtils.getFloat64LE(buffer, index);278      index += 8;279      if (promoteValues === false) value = new Double(value);280    } else if (elementType === constants.BSON_DATA_DATE) {281      const lowBits = NumberUtils.getInt32LE(buffer, index);282      const highBits = NumberUtils.getInt32LE(buffer, index + 4);283      index += 8;284 285      value = new Date(new Long(lowBits, highBits).toNumber());286    } else if (elementType === constants.BSON_DATA_BOOLEAN) {287      if (buffer[index] !== 0 && buffer[index] !== 1)288        throw new BSONError('illegal boolean type value');289      value = buffer[index++] === 1;290    } else if (elementType === constants.BSON_DATA_OBJECT) {291      const _index = index;292      const objectSize = NumberUtils.getInt32LE(buffer, index);293 294      if (objectSize <= 0 || objectSize > buffer.length - index)295        throw new BSONError('bad embedded document length in bson');296 297      // We have a raw value298      if (raw) {299        value = buffer.subarray(index, index + objectSize);300      } else {301        let objectOptions = options;302        if (!globalUTFValidation) {303          objectOptions = { ...options, validation: { utf8: shouldValidateKey } };304        }305        value = deserializeObject(buffer, _index, objectOptions, false);306      }307 308      index = index + objectSize;309    } else if (elementType === constants.BSON_DATA_ARRAY) {310      const _index = index;311      const objectSize = NumberUtils.getInt32LE(buffer, index);312      let arrayOptions: DeserializeOptions = options;313 314      // Stop index315      const stopIndex = index + objectSize;316 317      // All elements of array to be returned as raw bson318      if (fieldsAsRaw && fieldsAsRaw[name]) {319        arrayOptions = { ...options, raw: true };320      }321 322      if (!globalUTFValidation) {323        arrayOptions = { ...arrayOptions, validation: { utf8: shouldValidateKey } };324      }325      value = deserializeObject(buffer, _index, arrayOptions, true);326      index = index + objectSize;327 328      if (buffer[index - 1] !== 0) throw new BSONError('invalid array terminator byte');329      if (index !== stopIndex) throw new BSONError('corrupted array bson');330    } else if (elementType === constants.BSON_DATA_UNDEFINED) {331      value = undefined;332    } else if (elementType === constants.BSON_DATA_NULL) {333      value = null;334    } else if (elementType === constants.BSON_DATA_LONG) {335      if (useBigInt64) {336        value = NumberUtils.getBigInt64LE(buffer, index);337        index += 8;338      } else {339        // Unpack the low and high bits340        const lowBits = NumberUtils.getInt32LE(buffer, index);341        const highBits = NumberUtils.getInt32LE(buffer, index + 4);342        index += 8;343 344        const long = new Long(lowBits, highBits);345        // Promote the long if possible346        if (promoteLongs && promoteValues === true) {347          value =348            long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)349              ? long.toNumber()350              : long;351        } else {352          value = long;353        }354      }355    } else if (elementType === constants.BSON_DATA_DECIMAL128) {356      // Buffer to contain the decimal bytes357      const bytes = ByteUtils.allocateUnsafe(16);358      // Copy the next 16 bytes into the bytes buffer359      for (let i = 0; i < 16; i++) bytes[i] = buffer[index + i];360      // Update index361      index = index + 16;362      // Assign the new Decimal128 value363      value = new Decimal128(bytes);364    } else if (elementType === constants.BSON_DATA_BINARY) {365      let binarySize = NumberUtils.getInt32LE(buffer, index);366      index += 4;367      const totalBinarySize = binarySize;368      const subType = buffer[index++];369 370      // Did we have a negative binary size, throw371      if (binarySize < 0) throw new BSONError('Negative binary type element size found');372 373      // Is the length longer than the document374      if (binarySize > buffer.byteLength)375        throw new BSONError('Binary type size larger than document size');376 377      // If we have subtype 2 skip the 4 bytes for the size378      if (subType === Binary.SUBTYPE_BYTE_ARRAY) {379        binarySize = NumberUtils.getInt32LE(buffer, index);380        index += 4;381        if (binarySize < 0)382          throw new BSONError('Negative binary type element size found for subtype 0x02');383        if (binarySize > totalBinarySize - 4)384          throw new BSONError('Binary type with subtype 0x02 contains too long binary size');385        if (binarySize < totalBinarySize - 4)386          throw new BSONError('Binary type with subtype 0x02 contains too short binary size');387      }388 389      if (promoteBuffers && promoteValues) {390        value = ByteUtils.toLocalBufferType(buffer.subarray(index, index + binarySize));391      } else {392        value = new Binary(buffer.subarray(index, index + binarySize), subType);393        if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {394          value = value.toUUID();395        }396      }397 398      // Update the index399      index = index + binarySize;400    } else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === false) {401      // Get the start search index402      i = index;403      // Locate the end of the c string404      while (buffer[i] !== 0x00 && i < buffer.length) {405        i++;406      }407      // If are at the end of the buffer there is a problem with the document408      if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');409      // Return the C string410      const source = ByteUtils.toUTF8(buffer, index, i, false);411      // Create the regexp412      index = i + 1;413 414      // Get the start search index415      i = index;416      // Locate the end of the c string417      while (buffer[i] !== 0x00 && i < buffer.length) {418        i++;419      }420      // If are at the end of the buffer there is a problem with the document421      if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');422      // Return the C string423      const regExpOptions = ByteUtils.toUTF8(buffer, index, i, false);424      index = i + 1;425 426      // For each option add the corresponding one for javascript427      const optionsArray = new Array(regExpOptions.length);428 429      // Parse options430      for (i = 0; i < regExpOptions.length; i++) {431        switch (regExpOptions[i]) {432          case 'm':433            optionsArray[i] = 'm';434            break;435          case 's':436            optionsArray[i] = 'g';437            break;438          case 'i':439            optionsArray[i] = 'i';440            break;441        }442      }443 444      value = new RegExp(source, optionsArray.join(''));445    } else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === true) {446      // Get the start search index447      i = index;448      // Locate the end of the c string449      while (buffer[i] !== 0x00 && i < buffer.length) {450        i++;451      }452      // If are at the end of the buffer there is a problem with the document453      if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');454      // Return the C string455      const source = ByteUtils.toUTF8(buffer, index, i, false);456      index = i + 1;457 458      // Get the start search index459      i = index;460      // Locate the end of the c string461      while (buffer[i] !== 0x00 && i < buffer.length) {462        i++;463      }464      // If are at the end of the buffer there is a problem with the document465      if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');466      // Return the C string467      const regExpOptions = ByteUtils.toUTF8(buffer, index, i, false);468      index = i + 1;469 470      // Set the object471      value = new BSONRegExp(source, regExpOptions);472    } else if (elementType === constants.BSON_DATA_SYMBOL) {473      const stringSize = NumberUtils.getInt32LE(buffer, index);474      index += 4;475      if (476        stringSize <= 0 ||477        stringSize > buffer.length - index ||478        buffer[index + stringSize - 1] !== 0479      ) {480        throw new BSONError('bad string length in bson');481      }482      const symbol = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);483      value = promoteValues ? symbol : new BSONSymbol(symbol);484      index = index + stringSize;485    } else if (elementType === constants.BSON_DATA_TIMESTAMP) {486      value = new Timestamp({487        i: NumberUtils.getUint32LE(buffer, index),488        t: NumberUtils.getUint32LE(buffer, index + 4)489      });490      index += 8;491    } else if (elementType === constants.BSON_DATA_MIN_KEY) {492      value = new MinKey();493    } else if (elementType === constants.BSON_DATA_MAX_KEY) {494      value = new MaxKey();495    } else if (elementType === constants.BSON_DATA_CODE) {496      const stringSize = NumberUtils.getInt32LE(buffer, index);497      index += 4;498      if (499        stringSize <= 0 ||500        stringSize > buffer.length - index ||501        buffer[index + stringSize - 1] !== 0502      ) {503        throw new BSONError('bad string length in bson');504      }505      const functionString = ByteUtils.toUTF8(506        buffer,507        index,508        index + stringSize - 1,509        shouldValidateKey510      );511 512      value = new Code(functionString);513 514      // Update parse index position515      index = index + stringSize;516    } else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) {517      const totalSize = NumberUtils.getInt32LE(buffer, index);518      index += 4;519 520      // Element cannot be shorter than totalSize + stringSize + documentSize + terminator521      if (totalSize < 4 + 4 + 4 + 1) {522        throw new BSONError('code_w_scope total size shorter minimum expected length');523      }524 525      // Get the code string size526      const stringSize = NumberUtils.getInt32LE(buffer, index);527      index += 4;528      // Check if we have a valid string529      if (530        stringSize <= 0 ||531        stringSize > buffer.length - index ||532        buffer[index + stringSize - 1] !== 0533      ) {534        throw new BSONError('bad string length in bson');535      }536 537      // Javascript function538      const functionString = ByteUtils.toUTF8(539        buffer,540        index,541        index + stringSize - 1,542        shouldValidateKey543      );544      // Update parse index position545      index = index + stringSize;546      // Parse the element547      const _index = index;548      // Decode the size of the object document549      const objectSize = NumberUtils.getInt32LE(buffer, index);550      // Decode the scope object551      const scopeObject = deserializeObject(buffer, _index, options, false);552      // Adjust the index553      index = index + objectSize;554 555      // Check if field length is too short556      if (totalSize < 4 + 4 + objectSize + stringSize) {557        throw new BSONError('code_w_scope total size is too short, truncating scope');558      }559 560      // Check if totalSize field is too long561      if (totalSize > 4 + 4 + objectSize + stringSize) {562        throw new BSONError('code_w_scope total size is too long, clips outer document');563      }564 565      value = new Code(functionString, scopeObject);566    } else if (elementType === constants.BSON_DATA_DBPOINTER) {567      // Get the code string size568      const stringSize = NumberUtils.getInt32LE(buffer, index);569      index += 4;570      // Check if we have a valid string571      if (572        stringSize <= 0 ||573        stringSize > buffer.length - index ||574        buffer[index + stringSize - 1] !== 0575      )576        throw new BSONError('bad string length in bson');577      // Namespace578      const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);579      // Update parse index position580      index = index + stringSize;581 582      // Read the oid583      const oidBuffer = ByteUtils.allocateUnsafe(12);584      for (let i = 0; i < 12; i++) oidBuffer[i] = buffer[index + i];585      const oid = new ObjectId(oidBuffer);586 587      // Update the index588      index = index + 12;589 590      // Upgrade to DBRef type591      value = new DBRef(namespace, oid);592    } else {593      throw new BSONError(594        `Detected unknown BSON type ${elementType.toString(16)} for fieldname "${name}"`595      );596    }597    if (name === '__proto__') {598      Object.defineProperty(object, name, {599        value,600        writable: true,601        enumerable: true,602        configurable: true603      });604    } else {605      object[name] = value;606    }607  }608 609  // Check if the deserialization was against a valid array/object610  if (size !== index - startIndex) {611    if (isArray) throw new BSONError('corrupt array bson');612    throw new BSONError('corrupt object bson');613  }614 615  // if we did not find "$ref", "$id", "$db", or found an extraneous $key, don't make a DBRef616  if (!isPossibleDBRef) return object;617 618  if (isDBRefLike(object)) {619    const copy = Object.assign({}, object) as Partial<DBRefLike>;620    delete copy.$ref;621    delete copy.$id;622    delete copy.$db;623    return new DBRef(object.$ref, object.$id, object.$db, copy);624  }625 626  return object;627}628