@datastax/astra-db-ts
Version:
Data API TypeScript client
178 lines (177 loc) • 5.85 kB
JavaScript
// 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;
};