Add create object function

This function takes in a object name, and a map of
variants, converts it to json, and creates a dbus-interface
after validating the json matches the correct schema.

Tested-by: Uploaded new Pid interface through D-Bus.

Change-Id: Ieec47a3f9fb53d5ac4f975f13ae6b8efd323971d
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/include/Utils.hpp b/include/Utils.hpp
index cd44082..39e3794 100644
--- a/include/Utils.hpp
+++ b/include/Utils.hpp
@@ -17,6 +17,7 @@
 #pragma once
 #include <experimental/filesystem>
 #include <nlohmann/json.hpp>
+#include <sdbusplus/exception.hpp>
 
 bool findFiles(const std::experimental::filesystem::path &dirPath,
                const std::string &matchString,
@@ -24,3 +25,19 @@
 
 bool validateJson(const nlohmann::json &schemaFile,
                   const nlohmann::json &input);
+struct DBusInternalError final : public sdbusplus::exception_t
+{
+    const char *name() const noexcept override
+    {
+        return "org.freedesktop.DBus.Error.Failed";
+    };
+    const char *description() const noexcept override
+    {
+        return "internal error";
+    };
+    const char *what() const noexcept override
+    {
+        return "org.freedesktop.DBus.Error.Failed: "
+               "internal error";
+    };
+};
diff --git a/schemas/Pid.json b/schemas/Pid.json
new file mode 100644
index 0000000..aa262fb
--- /dev/null
+++ b/schemas/Pid.json
@@ -0,0 +1,83 @@
+{
+    "$schema": "http://json-schema.org/schema#",
+    "type": "object",
+    "properties": {
+        "Class": {
+            "type": "string"
+        },
+        "FFGainCoefficient": {
+            "type": "number"
+        },
+        "FFOffCoefficient": {
+            "type": "number"
+        },
+        "ICoefficient": {
+            "type": "number"
+        },
+        "ILimitMax": {
+            "type": "number"
+        },
+        "ILimitMin": {
+            "type": "number"
+        },
+        "Inputs": {
+            "type": "array",
+            "items": {
+                "type": "string"
+            }
+        },
+        "Name": {
+            "type": "string"
+        },
+        "OutLimitMax": {
+            "type": "number"
+        },
+        "OutLimitMin": {
+            "type": "number"
+        },
+        "Outputs": {
+            "type": "array",
+            "items": {
+                "type": "string"
+            }
+        },
+        "PCoefficient": {
+            "type": "number"
+        },
+        "SlewNeg": {
+            "type": "number"
+        },
+        "SlewPos": {
+            "type": "number"
+        },
+        "Type": {
+            "type": "string"
+        },
+        "Zones": {
+            "type": "array",
+            "items": {
+                "type": "string"
+            }
+        },
+        "SetPoint": {
+            "type": "number"
+        }
+    },
+    "required": [
+        "Class",
+        "FFGainCoefficient",
+        "FFOffCoefficient",
+        "ICoefficient",
+        "ILimitMax",
+        "ILimitMin",
+        "Inputs",
+        "Name",
+        "OutLimitMax",
+        "OutLimitMin",
+        "PCoefficient",
+        "SlewNeg",
+        "SlewPos",
+        "Type",
+        "Zones"
+    ]
+}
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index ec1b285..866baa7 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -72,7 +72,10 @@
 
 static constexpr std::array<const char *, 1> SETTABLE_INTERFACES = {
     "Thresholds"};
-
+using JsonVariantType =
+    sdbusplus::message::variant<std::vector<std::string>, std::string, int64_t,
+                                uint64_t, double, int32_t, uint32_t, int16_t,
+                                uint16_t, uint8_t, bool>;
 using BasicVariantType =
     sdbusplus::message::variant<std::string, int64_t, uint64_t, double, int32_t,
                                 uint32_t, int16_t, uint16_t, uint8_t, bool>;
@@ -557,22 +560,25 @@
         iface->register_property(propertyName, value);
         return;
     }
-    iface->register_property(propertyName, value, [
-        &systemConfiguration, jsonPointerString{std::string(jsonPointerString)}
-    ](const PropertyType &newVal, PropertyType &val) {
-        val = newVal;
-        if (!setJsonFromPointer(jsonPointerString, val, systemConfiguration))
-        {
-            std::cerr << "error setting json field\n";
+    iface->register_property(
+        propertyName, value,
+        [&systemConfiguration,
+         jsonPointerString{std::string(jsonPointerString)}](
+            const PropertyType &newVal, PropertyType &val) {
+            val = newVal;
+            if (!setJsonFromPointer(jsonPointerString, val,
+                                    systemConfiguration))
+            {
+                std::cerr << "error setting json field\n";
+                return -1;
+            }
+            if (writeJsonFiles(systemConfiguration))
+            {
+                std::cerr << "error setting json file\n";
+                return 1;
+            }
             return -1;
-        }
-        if (writeJsonFiles(systemConfiguration))
-        {
-            std::cerr << "error setting json file\n";
-            return 1;
-        }
-        return -1;
-    });
+        });
 }
 
 // adds simple json types to interface's properties
@@ -712,6 +718,112 @@
     iface->initialize();
 }
 
+void createAddObjectMethod(const std::string &jsonPointerPath,
+                           const std::string &path,
+                           nlohmann::json &systemConfiguration,
+                           sdbusplus::asio::object_server &objServer)
+{
+    auto iface = objServer.add_interface(path, "xyz.openbmc_project.AddObject");
+
+    iface->register_method(
+        "AddObject",
+        [&systemConfiguration, &objServer,
+         jsonPointerPath{std::string(jsonPointerPath)},
+         path{std::string(path)}](
+            const boost::container::flat_map<std::string, JsonVariantType>
+                &data) {
+            nlohmann::json::json_pointer ptr(jsonPointerPath);
+            nlohmann::json &base = systemConfiguration[ptr];
+            auto findExposes = base.find("Exposes");
+
+            if (findExposes == base.end())
+            {
+                throw std::invalid_argument("Entity must have children.");
+            }
+
+            // this will throw invalid-argument to sdbusplus if invalid json
+            nlohmann::json newData{};
+            for (const auto &item : data)
+            {
+                nlohmann::json &newJson = newData[item.first];
+                mapbox::util::apply_visitor(
+                    [&newJson](auto &&val) { newJson = std::move(val); },
+                    item.second);
+            }
+
+            auto findName = newData.find("Name");
+            auto findType = newData.find("Type");
+            if (findName == newData.end() || findType == newData.end())
+            {
+                throw std::invalid_argument("AddObject missing Name or Type");
+            }
+            const std::string *type = findType->get_ptr<const std::string *>();
+            const std::string *name = findName->get_ptr<const std::string *>();
+            if (type == nullptr || name == nullptr)
+            {
+                throw std::invalid_argument("Type and Name must be a string.");
+            }
+
+            size_t lastIndex = 0;
+            // we add in the "exposes"
+            for (; lastIndex < findExposes->size(); lastIndex++)
+            {
+                if (findExposes->at(lastIndex)["Name"] == *name &&
+                    findExposes->at(lastIndex)["Type"] == *type)
+                {
+                    throw std::invalid_argument(
+                        "Field already in JSON, not adding");
+                }
+                lastIndex++;
+            }
+
+            std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
+                                     *type + ".json");
+            // todo(james) we might want to also make a list of 'can add'
+            // interfaces but for now I think the assumption if there is a
+            // schema avaliable that it is allowed to update is fine
+            if (!schemaFile.good())
+            {
+                throw std::invalid_argument(
+                    "No schema avaliable, cannot validate.");
+            }
+            nlohmann::json schema =
+                nlohmann::json::parse(schemaFile, nullptr, false);
+            if (schema.is_discarded())
+            {
+                std::cerr << "Schema not legal" << *type << ".json\n";
+                throw DBusInternalError();
+            }
+            if (!validateJson(schema, newData))
+            {
+                throw std::invalid_argument("Data does not match schema");
+            }
+
+            if (!writeJsonFiles(systemConfiguration))
+            {
+                std::cerr << "Error writing json files\n";
+                throw DBusInternalError();
+            }
+            std::string dbusName = *name;
+
+            std::regex_replace(dbusName.begin(), dbusName.begin(),
+                               dbusName.end(), ILLEGAL_DBUS_REGEX, "_");
+            auto iface = objServer.add_interface(
+                path + "/" + dbusName,
+                "xyz.openbmc_project.Configuration." + *type);
+            // permission is read-write, as since we just created it, must be
+            // runtime modifiable
+            populateInterfaceFromJson(
+                systemConfiguration,
+                jsonPointerPath + "/" + std::to_string(lastIndex), iface.get(),
+                newData, objServer,
+                sdbusplus::asio::PropertyPermission::readWrite);
+            // todo(james) generate patch
+            findExposes->push_back(newData);
+        });
+    iface->initialize();
+}
+
 void postToDbus(const nlohmann::json &newConfiguration,
                 nlohmann::json &systemConfiguration,
                 sdbusplus::asio::object_server &objServer)
@@ -750,9 +862,13 @@
 
         auto inventoryIface = objServer.add_interface(
             boardName, "xyz.openbmc_project.Inventory.Item");
+
         auto boardIface = objServer.add_interface(
             boardName, "xyz.openbmc_project.Inventory.Item." + boardType);
 
+        createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration,
+                              objServer);
+
         populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
                                   boardIface.get(), boardValues, objServer);
         jsonPointerPath += "/";