packages/@aws-cdk/aws-custom-resource-sdk-adapter/lib/coerce-api-parameters.ts (77 lines of code) (raw):

import { TypeCoercionStateMachine, typeCoercionStateMachine } from './parameter-types'; type ApiParameters = { [param: string]: any }; type StateOrConversion = TypeCoercionStateMachine[number][string]; /** * Given a minimal AWS SDKv3 call definition (service, action, parameters), * coerces nested parameter values into a Uint8Array if that's what the SDKv3 expects. */ export function coerceApiParameters(v3service: string, action: string, parameters: ApiParameters = {}): ApiParameters { const typeMachine = typeCoercionStateMachine(); return new Coercer(typeMachine).coerceApiParameters(v3service, action, parameters); } /** * Make this a class in order to have multiple entry points for testing that can all share convenience functions */ export class Coercer { constructor(private readonly typeMachine: TypeCoercionStateMachine) { } public coerceApiParameters(v3service: string, action: string, parameters: ApiParameters = {}): ApiParameters { // Get the initial state corresponding to the current service+action, then recurse through the parameters const actionState = this.progress(action.toLowerCase(), this.progress(v3service.toLowerCase(), 0)); return this.recurse(parameters, actionState) as any; } public testCoerce(value: unknown): any { return this.recurse(value, 0); } private recurse(value: unknown, state: StateOrConversion | undefined): any { switch (state) { case undefined: return value; case 'b': return coerceValueToUint8Array(value); case 'n': return coerceValueToNumber(value); case 'd': return coerceValueToDate(value); } if (Array.isArray(value)) { const elState = this.progress('*', state); return elState !== undefined ? value.map((e) => this.recurse(e, elState)) : value; } if (value && typeof value === 'object') { // Mutate the object in-place for efficiency const mapState = this.progress('*', state); for (const key of Object.keys(value)) { const fieldState = this.progress(key, state) ?? mapState; if (fieldState !== undefined) { (value as any)[key] = this.recurse((value as any)[key], fieldState); } } return value; } return value; } /** * From a given state, return the state we would end up in if we followed this field */ private progress(field: string, s: StateOrConversion | undefined): StateOrConversion | undefined { if (s === undefined || typeof s !== 'number') { return undefined; } return this.typeMachine[s][field]; } } function coerceValueToUint8Array(x: unknown): Uint8Array | any { if (x instanceof Uint8Array) { return x; } if (typeof x === 'string' || typeof x === 'number') { return new TextEncoder().encode(x.toString()); } return x; } function coerceValueToNumber(x: unknown): number | any { if (typeof x === 'number') { return x; } if (typeof x === 'string') { const n = Number(x); return isNaN(n) ? x : n; } return x; } function coerceValueToDate(x: unknown): Date | any { if (typeof x === 'string' || typeof x === 'number') { const date = new Date(x); // if x is not a valid date if (isNaN(date.getTime())) { return x; } return date; } return x; }