Add MergeWithConfig

When dealing with logs, sometimes you need to add a new channel.  It is
easiest to just specify the json to merge in with the log file.  Make
this a function.

Change-Id: I7bd8bde72aa0103ec9c4ae0019164566787c54a2
diff --git a/aos/BUILD b/aos/BUILD
index 12d25b2..1d6d388 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -477,6 +477,7 @@
         "testdata/config2_multinode.json",
         "testdata/config3.json",
         "testdata/expected.json",
+        "testdata/expected_merge_with.json",
         "testdata/expected_multinode.json",
         "testdata/good_multinode.json",
         "testdata/good_multinode_hostnames.json",
diff --git a/aos/configuration.cc b/aos/configuration.cc
index ac897bf..d6d276e 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -417,6 +417,14 @@
   return MergeConfiguration(ReadConfig(path, &visited_paths));
 }
 
+FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
+    const Configuration *config, std::string_view json) {
+  FlatbufferDetachedBuffer<Configuration> addition =
+      JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
+
+  return MergeConfiguration(MergeFlatBuffers(config, &addition.message()));
+}
+
 const Channel *GetChannel(const Configuration *config, std::string_view name,
                           std::string_view type,
                           std::string_view application_name, const Node *node) {
diff --git a/aos/configuration.h b/aos/configuration.h
index bf2ec6c..9193fd5 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -32,6 +32,11 @@
     const Flatbuffer<Configuration> &config,
     const std::vector<aos::FlatbufferString<reflection::Schema>> &schemas);
 
+// Merges a configuration json snippet into the provided configuration and
+// returns the modified config.
+FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
+    const Configuration *config, std::string_view json);
+
 // Returns the resolved location for a name, type, and application name. Returns
 // nullptr if none is found.
 //
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index c36bd93..0ffa5df 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -85,6 +85,31 @@
       "aos/testdata/config1_bad.json");
 }
 
+// Tests that we can modify a config with a json snippet.
+TEST_F(ConfigurationTest, MergeWithConfig) {
+  FlatbufferDetachedBuffer<Configuration> config =
+      ReadConfig("aos/testdata/config1.json");
+  LOG(INFO) << "Read: " << FlatbufferToJson(config, true);
+
+  FlatbufferDetachedBuffer<Configuration> updated_config =
+      MergeWithConfig(&config.message(),
+                      R"channel({
+  "channels": [
+    {
+      "name": "/foo",
+      "type": ".aos.bar",
+      "max_size": 100
+    }
+  ]
+})channel");
+
+  EXPECT_EQ(
+      absl::StripSuffix(util::ReadFileToStringOrDie(
+                            "aos/testdata/expected_merge_with.json"),
+                        "\n"),
+      FlatbufferToJson(updated_config, true));
+}
+
 // Tests that we can lookup a location, complete with maps, from a merged
 // config.
 TEST_F(ConfigurationTest, GetChannel) {
diff --git a/aos/testdata/expected_merge_with.json b/aos/testdata/expected_merge_with.json
new file mode 100644
index 0000000..0ce2056
--- /dev/null
+++ b/aos/testdata/expected_merge_with.json
@@ -0,0 +1,64 @@
+{
+ "channels": [
+  {
+   "name": "/foo",
+   "type": ".aos.bar",
+   "max_size": 100
+  },
+  {
+   "name": "/foo2",
+   "type": ".aos.bar"
+  },
+  {
+   "name": "/foo3",
+   "type": ".aos.bar",
+   "max_size": 9
+  }
+ ],
+ "maps": [
+  {
+   "match": {
+    "name": "/batman"
+   },
+   "rename": {
+    "name": "/bar"
+   }
+  },
+  {
+   "match": {
+    "name": "/batman"
+   },
+   "rename": {
+    "name": "/foo"
+   }
+  }
+ ],
+ "applications": [
+  {
+   "name": "app1",
+   "maps": [
+    {
+     "match": {
+      "name": "/bar"
+     },
+     "rename": {
+      "name": "/foo"
+     }
+    }
+   ]
+  },
+  {
+   "name": "app2",
+   "maps": [
+    {
+     "match": {
+      "name": "/baz"
+     },
+     "rename": {
+      "name": "/foo"
+     }
+    }
+   ]
+  }
+ ]
+}