REST: Add framework for returning method response

If a method has an output argument in the introspection
data, it has data that should be returned in the response JSON.

This code finds the output type in the introspection
data, and adds two function stubs: one to convert the output
data in the sd_bus message to JSON, and another to handle
combining the possible multiple method responses (from
different services or interfaces) into 1 output.

It also adds some logic on what to do if multiple methods
are called and only a subset fail or return data.
That logic can be summarized as:
* if any method passes, consider it a success
* return all the data that comes back, though the
  merging is done in a future commit.

Change-Id: Ic50a3a88bb9d83672f7aed2b832217122e182048
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
index 7eae73b..895f436 100644
--- a/include/openbmc_dbus_rest.hpp
+++ b/include/openbmc_dbus_rest.hpp
@@ -40,6 +40,7 @@
 const std::string methodNotAllowedMsg = "405 Method Not Allowed";
 const std::string forbiddenMsg = "403 Forbidden";
 const std::string methodFailedMsg = "500 Method Call Failed";
+const std::string methodOutputFailedMsg = "500 Method Output Error";
 
 const std::string notFoundDesc =
     "org.freedesktop.DBus.Error.FileNotFound: path or object not found";
@@ -402,12 +403,50 @@
     InProgressActionData(crow::Response &res) : res(res){};
     ~InProgressActionData()
     {
-        // If still no JSON filled in, then we never found the method.
-        if (res.jsonValue.is_null())
+        // Methods could have been called across different owners
+        // and interfaces, where some calls failed and some passed.
+        //
+        // The rules for this are:
+        // * if no method was called - error
+        // * if a method failed and none passed - error
+        //   (converse: if at least one method passed - OK)
+        // * for the method output:
+        //   * if output processing didn't fail, return the data
+
+        // Only deal with method returns if nothing failed earlier
+        if (res.result() == boost::beast::http::status::ok)
         {
-            setErrorResponse(res, boost::beast::http::status::not_found,
-                             methodNotFoundDesc, notFoundMsg);
+            if (!methodPassed)
+            {
+                if (methodFailed)
+                {
+                    setErrorResponse(res,
+                                     boost::beast::http::status::bad_request,
+                                     "Method call failed", methodFailedMsg);
+                }
+                else
+                {
+                    setErrorResponse(res, boost::beast::http::status::not_found,
+                                     methodNotFoundDesc, notFoundMsg);
+                }
+            }
+            else
+            {
+                if (outputFailed)
+                {
+                    setErrorResponse(
+                        res, boost::beast::http::status::internal_server_error,
+                        "Method output failure", methodOutputFailedMsg);
+                }
+                else
+                {
+                    res.jsonValue = {{"status", "ok"},
+                                     {"message", "200 OK"},
+                                     {"data", methodResponse}};
+                }
+            }
         }
+
         res.end();
     }
 
@@ -420,6 +459,10 @@
     std::string path;
     std::string methodName;
     std::string interfaceName;
+    bool methodPassed = false;
+    bool methodFailed = false;
+    bool outputFailed = false;
+    nlohmann::json methodResponse;
     nlohmann::json arguments;
 };
 
@@ -752,6 +795,18 @@
     return r;
 }
 
+int convertDBusToJSON(const std::string &returnType,
+                      sdbusplus::message::message &m, nlohmann::json &response)
+{
+    return 0;
+}
+
+void handleMethodResponse(std::shared_ptr<InProgressActionData> transaction,
+                          sdbusplus::message::message &m,
+                          const std::string &returnType)
+{
+}
+
 void findActionOnInterface(std::shared_ptr<InProgressActionData> transaction,
                            const std::string &connectionName)
 {
@@ -818,9 +873,31 @@
                             tinyxml2::XMLElement *argumentNode =
                                 methodNode->FirstChildElement("arg");
 
+                            std::string returnType;
+
+                            // Find the output type
+                            while (argumentNode != nullptr)
+                            {
+                                const char *argDirection =
+                                    argumentNode->Attribute("direction");
+                                const char *argType =
+                                    argumentNode->Attribute("type");
+                                if (argDirection != nullptr &&
+                                    argType != nullptr &&
+                                    std::string(argDirection) == "out")
+                                {
+                                    returnType = argType;
+                                    break;
+                                }
+                                argumentNode =
+                                    argumentNode->NextSiblingElement("arg");
+                            }
+
                             nlohmann::json::const_iterator argIt =
                                 transaction->arguments.begin();
 
+                            argumentNode = methodNode->FirstChildElement("arg");
+
                             while (argumentNode != nullptr)
                             {
                                 const char *argDirection =
@@ -853,23 +930,21 @@
                             }
 
                             crow::connections::systemBus->async_send(
-                                m,
-                                [transaction](boost::system::error_code ec,
-                                              sdbusplus::message::message &m) {
+                                m, [transaction, returnType](
+                                       boost::system::error_code ec,
+                                       sdbusplus::message::message &m) {
                                     if (ec)
                                     {
-                                        setErrorResponse(
-                                            transaction->res,
-                                            boost::beast::http::status::
-                                                internal_server_error,
-                                            "Method call failed",
-                                            methodFailedMsg);
+                                        transaction->methodFailed = true;
                                         return;
                                     }
-                                    transaction->res.jsonValue = {
-                                        {"status", "ok"},
-                                        {"message", "200 OK"},
-                                        {"data", nullptr}};
+                                    else
+                                    {
+                                        transaction->methodPassed = true;
+                                    }
+
+                                    handleMethodResponse(transaction, m,
+                                                         returnType);
                                 });
                             break;
                         }