UNPKG

@datastax/astra-db-ts

Version:
178 lines (177 loc) 5.85 kB
// Copyright Datastax, Inc // SPDX-License-Identifier: Apache-2.0 import { BigNumber } from 'bignumber.js'; import { isBigNumber } from '../../../lib/utils.js'; import { pathMatches } from '../../../lib/api/ser-des/utils.js'; import { unescapeFieldPath } from '../../../lib/index.js'; const $NumCoercion = Symbol('NumCoercion'); export const buildGetNumCoercionForPathFn = (cfg) => { return (typeof cfg?.enableBigNumbers === 'object') ? collNumCoercionFnFromCfg(cfg.enableBigNumbers) : cfg?.enableBigNumbers; }; const collNumCoercionFnFromCfg = (cfg) => { const defaultCoercion = cfg['*']; if (!defaultCoercion) { throw new Error('The configuration must contain a "*" key'); } // Minor optimization to make `{ '*': 'xyz' }` equal in performance to `() => 'xyz'` if (Object.keys(cfg).length === 1) { return () => defaultCoercion; } const tree = buildNumCoercionTree(cfg); return (path) => { return findMatchingPath(path, tree) ?? defaultCoercion; }; }; const buildNumCoercionTree = (cfg) => { const result = Object.create(null); Object.entries(cfg).forEach(([path, coercion]) => { const keys = unescapeFieldPath(path); let current = result; keys.forEach((key, index) => { current[key] ?? (current[key] = Object.create(null)); if (index === keys.length - 1) { current[key][$NumCoercion] = coercion; } current = current[key]; }); }); return result; }; const findMatchingPath = (path, tree) => { let coercion = undefined; for (let i = 0; tree && i <= path.length; i++) { if (i === path.length) { return tree[$NumCoercion]; } const exactMatch = tree[path[i]]; if (exactMatch) { tree = exactMatch; } else { tree = tree['*']; coercion = tree?.[$NumCoercion] ?? coercion; } } return coercion; }; export class NumCoercionError extends Error { constructor(path, value, from, to) { super(`Failed to coerce value from ${from} to ${to} at path: ${path.join('.')}`); Object.defineProperty(this, "path", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "value", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "from", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "to", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.path = path; this.value = value; this.from = from; this.to = to; } } export const coerceBigNumber = (value, path, getNumCoercionForPath, pathMatches) => { const coercer = getNumCoercionForPath(path, pathMatches); if (typeof coercer === 'function') { return coercer(value, path); } switch (coercer) { case 'number': { return value.toNumber(); } case 'strict_number': { const asNum = value.toNumber(); if (!value.isEqualTo(asNum)) { throw new NumCoercionError(path, value, 'bignumber', 'number'); } return asNum; } case 'bigint': { if (!value.isInteger()) { throw new NumCoercionError(path, value, 'bignumber', 'bigint'); } return BigInt(value.toFixed(0)); } case 'bignumber': return value; case 'string': return value.toString(); case 'number_or_string': { const asNum = value.toNumber(); if (!value.isEqualTo(asNum)) { return value.toString(); } return asNum; } } }; export const coerceNumber = (value, path, getNumCoercionForPath, pathMatches) => { const coercer = getNumCoercionForPath(path, pathMatches); if (typeof coercer === 'function') { return coercer(value, path); } switch (coercer) { case 'bigint': { if (!Number.isInteger(value)) { throw new NumCoercionError(path, value, 'number', 'bigint'); } return BigInt(value); } case 'bignumber': return BigNumber(value); case 'string': return String(value); case 'number': case 'strict_number': case 'number_or_string': return value; } }; export const coerceNums = (val, getNumCoercionForPath) => { return coerceNumsImpl(val, [], getNumCoercionForPath, (p) => pathMatches([], p)); }; const coerceNumsImpl = (val, path, getNumCoercionForPath, pathMatchesFn) => { if (typeof val === 'number') { return coerceNumber(val, path, getNumCoercionForPath, pathMatchesFn); } if (!val || typeof val !== 'object') { return val; } if (isBigNumber(val)) { return coerceBigNumber(val, path, getNumCoercionForPath, pathMatchesFn); } path.push('<temp>'); if (Array.isArray(val)) { for (let i = 0; i < val.length; i++) { path[path.length - 1] = i; val[i] = coerceNumsImpl(val[i], path, getNumCoercionForPath, (p) => pathMatches(path, p)); } } else { for (const key of Object.keys(val)) { path[path.length - 1] = key; val[key] = coerceNumsImpl(val[key], path, getNumCoercionForPath, (p) => pathMatches(path, p)); } } path.pop(); return val; };