diff --git a/java/google/registry/config/YamlUtils.java b/java/google/registry/config/YamlUtils.java index 05af37431..c2a64f60c 100644 --- a/java/google/registry/config/YamlUtils.java +++ b/java/google/registry/config/YamlUtils.java @@ -18,6 +18,7 @@ import static google.registry.config.RegistryEnvironment.UNITTEST; import static google.registry.util.FormattingLogger.getLoggerForCallerClass; import static google.registry.util.ResourceUtils.readResourceUtf8; +import com.google.common.base.Optional; import com.google.common.io.CharStreams; import google.registry.util.FormattingLogger; import java.io.File; @@ -84,19 +85,30 @@ public final class YamlUtils { *

Only maps are handled recursively; lists are simply overridden in place as-is. */ static String mergeYaml(String defaultYaml, String customYaml) { + Yaml yaml = new Yaml(); + Map yamlMap = loadAsMap(yaml, defaultYaml).get(); try { - Yaml yaml = new Yaml(); - Map defaultObj = loadAsMap(yaml, defaultYaml); - Map customObj = loadAsMap(yaml, customYaml); - Object mergedObj = mergeMaps(defaultObj, customObj); - logger.infofmt("Successfully loaded custom YAML configuration file."); - return yaml.dump(mergedObj); + Optional> customMap = loadAsMap(yaml, customYaml); + if (customMap.isPresent()) { + yamlMap = mergeMaps(yamlMap, customMap.get()); + logger.infofmt("Successfully loaded custom YAML configuration file."); + } else { + logger.infofmt("Ignoring empty custom YAML configuration file."); + } + return yaml.dump(yamlMap); } catch (Exception e) { throw new IllegalStateException("Fatal error: Custom YAML configuration file is invalid", e); } } - private static Object mergeMaps(Map defaultMap, Map customMap) { + /** + * Recursively merges a custom map into a default map, and returns the merged result. + * + *

All keys in the default map that are also specified in the custom map are overridden with + * the custom map's value. This runs recursively on all contained maps. + */ + private static Map mergeMaps( + Map defaultMap, Map customMap) { for (String key : defaultMap.keySet()) { if (!customMap.containsKey(key)) { continue; @@ -113,9 +125,15 @@ public final class YamlUtils { return defaultMap; } + /** + * Returns a structured map loaded from a YAML config string. + * + *

If the YAML string is empty or does not contain any data (e.g. it's only comments), then + * absent is returned. + */ @SuppressWarnings("unchecked") - private static Map loadAsMap(Yaml yaml, String yamlString) { - return (Map) yaml.load(yamlString); + private static Optional> loadAsMap(Yaml yaml, String yamlString) { + return Optional.fromNullable((Map) yaml.load(yamlString)); } private YamlUtils() {} diff --git a/javatests/google/registry/config/YamlUtilsTest.java b/javatests/google/registry/config/YamlUtilsTest.java index c52693c45..fb5bfcc7a 100644 --- a/javatests/google/registry/config/YamlUtilsTest.java +++ b/javatests/google/registry/config/YamlUtilsTest.java @@ -17,6 +17,7 @@ package google.registry.config; import static com.google.common.truth.Truth.assertThat; import static google.registry.config.YamlUtils.mergeYaml; +import com.google.common.base.Joiner; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -48,7 +49,14 @@ public class YamlUtilsTest { .isEqualTo(join("non: ay", "list: [crackle, pop var]")); } + @Test + public void testSuccess_mergeEmptyMap_isNoop() { + String defaultYaml = join("one: ay", "two: bee", "three: sea"); + assertThat(mergeYaml(defaultYaml, "# Just a comment\n")) + .isEqualTo("{one: ay, two: bee, three: sea}\n"); + } + private static String join(CharSequence... strings) { - return String.join("\n", strings) + "\n"; + return Joiner.on('\n').join(strings) + "\n"; } }