REST: If necessary, combine method response data

There is a possibility that a method may be implemented
by either different services or interfaces, and 1 or more
of them may return data.

In the unlikely case that is encountered, attempt to handle
that by first setting the final response data to the first
data back from a method, and then on future method responses
that return data:

* If the new and old responses are both dictionaries,
  add the new keys/values to the original ones.

* If the new and old responses are both arrays,
  add the new array elements to the original array.

* If the new data is of a different type than the
  previous data, convert the overall response into
  an array and add the new and original responses
  as array elements.

Change-Id: I23edc3d9f8154aba1ba4276112cde6ecb4345fdf
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
index 9f282a6..e310b1a 100644
--- a/include/openbmc_dbus_rest.hpp
+++ b/include/openbmc_dbus_rest.hpp
@@ -462,6 +462,7 @@
     bool methodPassed = false;
     bool methodFailed = false;
     bool outputFailed = false;
+    bool convertedToArray = false;
     nlohmann::json methodResponse;
     nlohmann::json arguments;
 };
@@ -1190,6 +1191,64 @@
                           sdbusplus::message::message &m,
                           const std::string &returnType)
 {
+    nlohmann::json data;
+
+    int r = convertDBusToJSON(returnType, m, data);
+    if (r < 0)
+    {
+        transaction->outputFailed = true;
+        return;
+    }
+
+    if (data.is_null())
+    {
+        return;
+    }
+
+    if (transaction->methodResponse.is_null())
+    {
+        transaction->methodResponse = std::move(data);
+        return;
+    }
+
+    // If they're both dictionaries or arrays, merge into one.
+    // Otherwise, make the results an array with every result
+    // an entry.  Could also just fail in that case, but it
+    // seems better to get the data back somehow.
+
+    if (transaction->methodResponse.is_object() && data.is_object())
+    {
+        for (const auto &obj : data.items())
+        {
+            // Note: Will overwrite the data for a duplicate key
+            transaction->methodResponse.emplace(obj.key(),
+                                                std::move(obj.value()));
+        }
+        return;
+    }
+
+    if (transaction->methodResponse.is_array() && data.is_array())
+    {
+        for (auto &obj : data)
+        {
+            transaction->methodResponse.push_back(std::move(obj));
+        }
+        return;
+    }
+
+    if (!transaction->convertedToArray)
+    {
+        // They are different types. May as well turn them into an array
+        nlohmann::json j = std::move(transaction->methodResponse);
+        transaction->methodResponse = nlohmann::json::array();
+        transaction->methodResponse.push_back(std::move(j));
+        transaction->methodResponse.push_back(std::move(data));
+        transaction->convertedToArray = true;
+    }
+    else
+    {
+        transaction->methodResponse.push_back(std::move(data));
+    }
 }
 
 void findActionOnInterface(std::shared_ptr<InProgressActionData> transaction,