dynamic ConfigPreprocessor::expandMacros()

in mcrouter/lib/config/ConfigPreprocessor.cpp [1938:2041]


dynamic ConfigPreprocessor::expandMacros(dynamic json, const Context& context)
    const {
  NestedLimitGuard nestedGuard(nestedLimit_);

  if (json.isString()) {
    // look for macros in string
    return expandStringMacro(json.stringPiece(), context);
  } else if (json.isObject()) {
    folly::Optional<Context> extContext;
    if (auto jVars = json.get_ptr("vars")) {
      checkLogic(
          jVars->isObject(), "vars is {}, expected object", jVars->typeName());

      extContext.emplace(Context::Extended, context);
      // since vars may use other vars from local context, we
      // do the initialization in two steps: first add them to context,
      // then lazily expand
      for (auto& it : jVars->items()) {
        auto var = const_cast<dynamic&>(it.second);
        extContext->addLocal(it.first.stringPiece(), std::move(var));
      }
      for (const auto& varName : jVars->keys()) {
        extContext->doLazyExpand(varName.stringPiece());
      }
      json.erase("vars");
    }
    const auto& localContext = extContext ? *extContext : context;

    // check for built-in calls and long-form macros
    auto typeIt = json.find("type");
    if (typeIt != json.items().end()) {
      auto type = expandMacros(typeIt->second, localContext);
      if (type.isString()) {
        auto typeStr = type.stringPiece();
        // built-in call
        auto builtInIt = builtInCalls_.find(typeStr);
        if (builtInIt != builtInCalls_.end()) {
          try {
            return builtInIt->second(std::move(json), localContext);
          } catch (const std::logic_error& e) {
            throwLogic("Built-in '{}':\n{}", typeStr, e.what());
          }
        }
        // long form macro substitution
        auto macroIt = macros_.find(typeStr);
        if (macroIt != macros_.end()) {
          const auto& inner = macroIt->second;
          try {
            return inner->getResult(std::move(json), localContext);
          } catch (const std::logic_error& e) {
            throwLogic("Macro '{}':\n{}", typeStr, e.what());
          }
        }
      }
    }

    // raw object
    dynamic result = dynamic::object();
    for (const auto& it : json.items()) {
      auto& value = const_cast<dynamic&>(it.second);
      try {
        auto nKey = expandMacros(it.first, localContext);
        checkLogic(nKey.isString(), "Expanded key is not a string");
        result.insert(
            std::move(nKey), expandMacros(std::move(value), localContext));
        // Since new json is being created with expanded macros we need
        // to re-populate the config metadata map with new dynamic objects
        // created in the process.
        const auto nKeyPtr = result.get_ptr(it.first);
        const auto nKeyJsonPtr = json.get_ptr(it.first);
        if (nKeyPtr && nKeyJsonPtr) {
          const auto resMetadataPtr = configMetadataMap_.find(nKeyJsonPtr);
          const auto jsonMetadataPtr = configMetadataMap_.find(nKeyPtr);
          if (resMetadataPtr != configMetadataMap_.end()) {
            // If it already exists in the map, replace it
            // Otherwise, create an entry in the map
            if (jsonMetadataPtr == configMetadataMap_.end()) {
              configMetadataMap_.emplace(nKeyPtr, resMetadataPtr->second);
            } else {
              jsonMetadataPtr->second = resMetadataPtr->second;
            }
          }
        }
      } catch (const std::logic_error& e) {
        throwLogic(
            "Raw object property '{}':\n{}", it.first.stringPiece(), e.what());
      }
    }
    return result;
  } else if (json.isArray()) {
    for (size_t i = 0; i < json.size(); ++i) {
      auto& value = json[i];
      try {
        value = expandMacros(std::move(value), context);
      } catch (const std::logic_error& e) {
        throwLogic("Array element #{}:\n{}", i, e.what());
      }
    }
    return json;
  } else {
    // some number or other type of json. Return 'as is'.
    return json;
  }
}