export function resolve()

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;
}