in packages/aws-cdk-lib/core/lib/private/resolve.ts [91:262]
export function resolve(obj: any, options: IResolveOptions): any {
const prefix = options.prefix || [];
const pathName = '/' + prefix.join('/');
/**
* Make a new resolution context
*/
function makeContext(appendPath?: string): [IResolveContext, IPostProcessor] {
const newPrefix = appendPath !== undefined ? prefix.concat([appendPath]) : options.prefix;
let postProcessor: IPostProcessor | undefined;
const context: IResolveContext = {
preparing: options.preparing,
scope: options.scope as IConstruct,
documentPath: newPrefix ?? [],
registerPostProcessor(pp) { postProcessor = pp; },
resolve(x: any, changeOptions?: ResolveChangeContextOptions) { return resolve(x, { ...options, ...changeOptions, prefix: newPrefix }); },
};
return [context, { postProcess(x) { return postProcessor ? postProcessor.postProcess(x, context) : x; } }];
}
// protect against cyclic references by limiting depth.
if (prefix.length > 200) {
throw new Error('Unable to resolve object tree with circular reference. Path: ' + pathName);
}
// whether to leave the empty elements when resolving - false by default
const leaveEmpty = options.removeEmpty === false;
//
// undefined
//
if (typeof(obj) === 'undefined') {
return undefined;
}
//
// null
//
if (obj === null) {
return null;
}
//
// functions - not supported (only tokens are supported)
//
if (typeof(obj) === 'function') {
throw new Error(`Trying to resolve a non-data object. Only token are supported for lazy evaluation. Path: ${pathName}. Object: ${obj}`);
}
//
// string - potentially replace all stringified Tokens
//
if (typeof(obj) === 'string') {
// If this is a "list element" Token, it should never occur by itself in string context
if (TokenString.forListToken(obj).test()) {
throw new Error('Found an encoded list token string in a scalar string context. Use \'Fn.select(0, list)\' (not \'list[0]\') to extract elements from token lists.');
}
// Otherwise look for a stringified Token in this object
const str = TokenString.forString(obj);
if (str.test()) {
const fragments = str.split(tokenMap.lookupToken.bind(tokenMap));
return tagResolvedValue(options.resolver.resolveString(fragments, makeContext()[0]), ResolutionTypeHint.STRING);
}
return obj;
}
//
// number - potentially decode Tokenized number
//
if (typeof(obj) === 'number') {
return tagResolvedValue(resolveNumberToken(obj, makeContext()[0]), ResolutionTypeHint.NUMBER);
}
//
// primitives - as-is
//
if (typeof(obj) !== 'object' || obj instanceof Date) {
return obj;
}
//
// arrays - resolve all values, remove undefined and remove empty arrays
//
if (Array.isArray(obj)) {
if (containsListTokenElement(obj)) {
return tagResolvedValue(options.resolver.resolveList(obj, makeContext()[0]), ResolutionTypeHint.STRING_LIST);
}
const arr = obj
.map((x, i) => makeContext(`${i}`)[0].resolve(x))
.filter(x => leaveEmpty || typeof(x) !== 'undefined');
return arr;
}
//
// literal null -- from JsonNull resolution, preserved as-is (semantically meaningful)
//
if (obj === null) {
return obj;
}
//
// tokens - invoke 'resolve' and continue to resolve recursively
//
if (unresolved(obj)) {
const [context, postProcessor] = makeContext();
const ret = tagResolvedValue(options.resolver.resolveToken(obj, context, postProcessor), ResolutionTypeHint.STRING);
return ret;
}
//
// objects - deep-resolve all values
//
// Must not be a Construct at this point, otherwise you probably made a typo
// mistake somewhere and resolve will get into an infinite loop recursing into
// child.parent <---> parent.children
if (isConstruct(obj)) {
throw new Error('Trying to resolve() a Construct at ' + pathName);
}
const result: any = { };
let intrinsicKeyCtr = 0;
for (const key of Object.keys(obj)) {
const value = makeContext(String(key))[0].resolve(obj[key]);
// skip undefined
if (typeof(value) === 'undefined') {
if (leaveEmpty) {
result[key] = undefined;
}
continue;
}
// Simple case -- not an unresolved key
if (!unresolved(key)) {
result[key] = value;
continue;
}
const resolvedKey = makeContext()[0].resolve(key);
if (typeof(resolvedKey) === 'string') {
result[resolvedKey] = value;
} else {
if (!options.allowIntrinsicKeys) {
// eslint-disable-next-line max-len
throw new Error(`"${String(key)}" is used as the key in a map so must resolve to a string, but it resolves to: ${JSON.stringify(resolvedKey)}. Consider using "CfnJson" to delay resolution to deployment-time`);
}
// Can't represent this object in a JavaScript key position, but we can store it
// in value position. Use a unique symbol as the key.
result[`${INTRINSIC_KEY_PREFIX}${intrinsicKeyCtr++}`] = [resolvedKey, value];
}
}
// Because we may be called to recurse on already resolved values (that already have type hints applied)
// and we just copied those values into a fresh object, be sure to retain any type hints.
const previousTypeHint = resolvedTypeHint(obj);
return previousTypeHint ? tagResolvedValue(result, previousTypeHint) : result;
}