cleanup: move json configuration functions in their own files

Improving maintainability by increasing 'separation of concern'.

Change-Id: I80355abf741c037f8834527e3304c80b83c8166e
Signed-off-by: Christopher Meis <christopher.meis@9elements.com>
diff --git a/src/configuration.cpp b/src/configuration.cpp
new file mode 100644
index 0000000..8646f57
--- /dev/null
+++ b/src/configuration.cpp
@@ -0,0 +1,197 @@
+#include "configuration.hpp"
+
+#include "perform_probe.hpp"
+#include "utils.hpp"
+
+#include <nlohmann/json.hpp>
+#include <valijson/adapters/nlohmann_json_adapter.hpp>
+#include <valijson/schema.hpp>
+#include <valijson/schema_parser.hpp>
+#include <valijson/validator.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <list>
+#include <string>
+#include <vector>
+
+namespace configuration
+{
+// writes output files to persist data
+bool writeJsonFiles(const nlohmann::json& systemConfiguration)
+{
+    std::filesystem::create_directory(configurationOutDir);
+    std::ofstream output(currentConfiguration);
+    if (!output.good())
+    {
+        return false;
+    }
+    output << systemConfiguration.dump(4);
+    output.close();
+    return true;
+}
+
+// reads json files out of the filesystem
+bool loadConfigurations(std::list<nlohmann::json>& configurations)
+{
+    // find configuration files
+    std::vector<std::filesystem::path> jsonPaths;
+    if (!findFiles(
+            std::vector<std::filesystem::path>{configurationDirectory,
+                                               hostConfigurationDirectory},
+            R"(.*\.json)", jsonPaths))
+    {
+        std::cerr << "Unable to find any configuration files in "
+                  << configurationDirectory << "\n";
+        return false;
+    }
+
+    std::ifstream schemaStream(
+        std::string(schemaDirectory) + "/" + globalSchema);
+    if (!schemaStream.good())
+    {
+        std::cerr
+            << "Cannot open schema file,  cannot validate JSON, exiting\n\n";
+        std::exit(EXIT_FAILURE);
+        return false;
+    }
+    nlohmann::json schema =
+        nlohmann::json::parse(schemaStream, nullptr, false, true);
+    if (schema.is_discarded())
+    {
+        std::cerr
+            << "Illegal schema file detected, cannot validate JSON, exiting\n";
+        std::exit(EXIT_FAILURE);
+        return false;
+    }
+
+    for (auto& jsonPath : jsonPaths)
+    {
+        std::ifstream jsonStream(jsonPath.c_str());
+        if (!jsonStream.good())
+        {
+            std::cerr << "unable to open " << jsonPath.string() << "\n";
+            continue;
+        }
+        auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
+        if (data.is_discarded())
+        {
+            std::cerr << "syntax error in " << jsonPath.string() << "\n";
+            continue;
+        }
+        /*
+        * todo(james): reenable this once less things are in flight
+        *
+        if (!validateJson(schema, data))
+        {
+            std::cerr << "Error validating " << jsonPath.string() << "\n";
+            continue;
+        }
+        */
+
+        if (data.type() == nlohmann::json::value_t::array)
+        {
+            for (auto& d : data)
+            {
+                configurations.emplace_back(d);
+            }
+        }
+        else
+        {
+            configurations.emplace_back(data);
+        }
+    }
+    return true;
+}
+
+// Iterate over new configuration and erase items from old configuration.
+void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
+                            nlohmann::json& newConfiguration)
+{
+    for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
+    {
+        auto findKey = oldConfiguration.find(it.key());
+        if (findKey != oldConfiguration.end())
+        {
+            it = newConfiguration.erase(it);
+        }
+        else
+        {
+            it++;
+        }
+    }
+}
+
+// validates a given input(configuration) with a given json schema file.
+bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
+{
+    valijson::Schema schema;
+    valijson::SchemaParser parser;
+    valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
+    parser.populateSchema(schemaAdapter, schema);
+    valijson::Validator validator;
+    valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
+    return validator.validate(schema, targetAdapter, nullptr);
+}
+
+// Extract the D-Bus interfaces to probe from the JSON config files.
+std::set<std::string> getProbeInterfaces()
+{
+    std::set<std::string> interfaces;
+    std::list<nlohmann::json> configurations;
+    if (!configuration::loadConfigurations(configurations))
+    {
+        return interfaces;
+    }
+
+    for (auto it = configurations.begin(); it != configurations.end();)
+    {
+        auto findProbe = it->find("Probe");
+        if (findProbe == it->end())
+        {
+            std::cerr << "configuration file missing probe:\n " << *it << "\n";
+            it++;
+            continue;
+        }
+
+        nlohmann::json probeCommand;
+        if ((*findProbe).type() != nlohmann::json::value_t::array)
+        {
+            probeCommand = nlohmann::json::array();
+            probeCommand.push_back(*findProbe);
+        }
+        else
+        {
+            probeCommand = *findProbe;
+        }
+
+        for (const nlohmann::json& probeJson : probeCommand)
+        {
+            const std::string* probe = probeJson.get_ptr<const std::string*>();
+            if (probe == nullptr)
+            {
+                std::cerr << "Probe statement wasn't a string, can't parse";
+                continue;
+            }
+            // Skip it if the probe cmd doesn't contain an interface.
+            if (probe::findProbeType(*probe))
+            {
+                continue;
+            }
+
+            // syntax requires probe before first open brace
+            auto findStart = probe->find('(');
+            if (findStart != std::string::npos)
+            {
+                std::string interface = probe->substr(0, findStart);
+                interfaces.emplace(interface);
+            }
+        }
+        it++;
+    }
+
+    return interfaces;
+}
+
+} // namespace configuration