/** * Utilities for internal library usage */ import { Schema, SchemaContextCreator, SchemaValidationError } from './schema'; export function arrayEntries(arr: T[]) { const entries: Array<[number, T]> = []; for (let index = 0; index < arr.length; index++) { const element = arr[index]; entries.push([index, element]); } return entries; } export function objectEntries>( obj: T ): Array<[Extract, T[keyof T]]> { const ownProps = Object.keys(obj); let i = ownProps.length; const resArray = new Array(i); // preallocate the Array while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; } return resArray; } export function literalToString(value: any): string { return typeof value === 'string' ? `"${value.replace(/"/g, '"')}"` : `${value}`; } export function identityFn(value: T): T { return value; } export function toValidator( fn: (value: unknown, strict?: boolean) => boolean ): (value: unknown, ctxt: SchemaContextCreator) => SchemaValidationError[] { return (value, ctxt) => (fn(value, ctxt.strictValidation) ? [] : ctxt.fail()); } /** * Schema in which the mapping and unmapping is done the same way */ export interface SymmetricSchema { type: string; validate: ( value: unknown, ctxt: SchemaContextCreator ) => SchemaValidationError[]; map: (value: T, ctxt: SchemaContextCreator) => T; } /** * Create a schema in which the mapping and unmapping is done the same way */ export function createSymmetricSchema( schema: SymmetricSchema ): Schema { return createBasicSchema({ type: () => schema.type, validateBeforeMap: schema.validate, validateBeforeUnmap: schema.validate, map: schema.map, unmap: schema.map, }); } interface BasicSchema { type: () => string; validateBeforeMap: ( value: unknown, ctxt: SchemaContextCreator ) => SchemaValidationError[]; validateBeforeUnmap: ( value: unknown, ctxt: SchemaContextCreator ) => SchemaValidationError[]; map: (value: S, ctxt: SchemaContextCreator) => T; unmap: (value: T, ctxt: SchemaContextCreator) => S; } /** Create a basic schema where XML mapping and validation is the same as for JSON */ function createBasicSchema(basicSchema: BasicSchema): Schema { return { ...basicSchema, validateBeforeMapXml: basicSchema.validateBeforeUnmap, mapXml: basicSchema.map, unmapXml: basicSchema.unmap, }; } export function isNumericString( value: unknown, strict?: boolean ): value is number | string { return strict ? typeof value === 'number' : typeof value === 'number' || (typeof value === 'string' && !isNaN(value as any)); } export function coerceNumericStringToNumber(value: number | string): number { return typeof value === 'number' ? value : +value; } export function coerceStringOrNumberToBigInt( value: bigint | string | number ): bigint { return typeof value === 'bigint' ? value : BigInt(value); } export function once( func: (...args: Args) => R ): (...args: Args) => R { let ran = false; let memo: R; return function (this: any, ...args) { if (ran) { return memo; } ran = true; memo = func.apply(this, args); return memo; }; } /** * Returns a copy of the object with the given keys omitted. */ export function omitKeysFromObject( object: Record, keysToOmit: string[] ): Record { const omitSet = new Set(keysToOmit); const output: Record = {}; for (const key in object) { if ( Object.prototype.hasOwnProperty.call(object, key) && !omitSet.has(key) ) { output[key] = object[key]; } } return output; } export function objectKeyEncode(key: string): string { return key.indexOf(' ') !== -1 ? literalToString(key) : key; } export function isNullOrMissing(value: unknown): value is null | undefined { return value === null || typeof value === 'undefined'; } export function isOptional(type: string, value: unknown): boolean { return type.startsWith('Optional<') && typeof value === 'undefined'; } export function isOptionalNullable(type: string, value: unknown): boolean { return isOptionalAndNullableType(type) && isNullOrMissing(value); } export function isOptionalAndNullableType(type: string): boolean { return ( type.startsWith('Optional