sdbus++: events: unpack an event from JSON

Generate code necessary to unpack a generated event from its
JSON representation.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I7a28617f3f0374968609328a44587b655a9141c1
diff --git a/include/sdbusplus/exception.hpp b/include/sdbusplus/exception.hpp
index 80f0d22..88055f2 100644
--- a/include/sdbusplus/exception.hpp
+++ b/include/sdbusplus/exception.hpp
@@ -7,6 +7,7 @@
 #include <sdbusplus/utility/consteval_string.hpp>
 
 #include <exception>
+#include <source_location>
 #include <string>
 
 namespace sdbusplus
@@ -198,6 +199,18 @@
     int get_errno() const noexcept override;
 };
 
+/** Throw a generated_event from the JSON representation.
+ *
+ *  @param[in] j - JSON representation of the event.
+ *  @param[in] source - The source code location of the origin.
+ */
+void throw_via_json(
+    const nlohmann::json& j,
+    const std::source_location& source = std::source_location::current());
+
+/** Get the list of known events by name. */
+auto known_events() -> std::vector<std::string>;
+
 } // namespace exception
 
 using exception_t = exception::exception;
diff --git a/include/sdbusplus/sdbuspp_support/event.hpp b/include/sdbusplus/sdbuspp_support/event.hpp
new file mode 100644
index 0000000..eea8947
--- /dev/null
+++ b/include/sdbusplus/sdbuspp_support/event.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <sdbusplus/exception.hpp>
+
+#include <source_location>
+
+namespace sdbusplus::sdbuspp
+{
+
+using register_hook =
+    std::function<void(const nlohmann::json&, const std::source_location&)>;
+
+void register_event(const std::string&, register_hook);
+
+template <typename T>
+struct register_event_helper
+{
+    static void hook()
+    {
+        register_event(T::errName, throw_event);
+    }
+
+    static void throw_event(const nlohmann::json& j,
+                            const std::source_location& location)
+    {
+        throw T(j, location);
+    }
+};
+} // namespace sdbusplus::sdbuspp
diff --git a/src/exception.cpp b/src/exception.cpp
index 565e059..cd09c0c 100644
--- a/src/exception.cpp
+++ b/src/exception.cpp
@@ -1,4 +1,5 @@
 #include <sdbusplus/exception.hpp>
+#include <sdbusplus/sdbuspp_support/event.hpp>
 
 #include <cerrno>
 #include <stdexcept>
@@ -9,9 +10,7 @@
 #pragma clang diagnostic ignored "-Wc99-extensions"
 #endif
 
-namespace sdbusplus
-{
-namespace exception
+namespace sdbusplus::exception
 {
 
 void exception::unused() const noexcept {}
@@ -212,8 +211,45 @@
     return ECANCELED;
 }
 
-} // namespace exception
-} // namespace sdbusplus
+static std::unordered_map<std::string, sdbusplus::sdbuspp::register_hook>
+    event_hooks = {};
+
+void throw_via_json(const nlohmann::json& j, const std::source_location& source)
+{
+    for (const auto& i : j.items())
+    {
+        if (auto it = event_hooks.find(i.key()); it != event_hooks.end())
+        {
+            it->second(j, source);
+        }
+    }
+}
+
+auto known_events() -> std::vector<std::string>
+{
+    std::vector<std::string> result{};
+
+    for (const auto& [key, _] : event_hooks)
+    {
+        result.emplace_back(key);
+    }
+
+    std::ranges::sort(result);
+
+    return result;
+}
+
+} // namespace sdbusplus::exception
+
+namespace sdbusplus::sdbuspp
+{
+
+void register_event(const std::string& event, register_hook throw_hook)
+{
+    sdbusplus::exception::event_hooks.emplace(event, throw_hook);
+}
+
+} // namespace sdbusplus::sdbuspp
 
 #ifdef __clang__
 #pragma clang diagnostic pop
diff --git a/tools/sdbusplus/templates/event.cpp.mako b/tools/sdbusplus/templates/event.cpp.mako
index 7613e85..1473f21 100644
--- a/tools/sdbusplus/templates/event.cpp.mako
+++ b/tools/sdbusplus/templates/event.cpp.mako
@@ -22,17 +22,68 @@
 {
     nlohmann::json j = { };
 % for m in event.metadata:
+    % if m.typeName == "object_path":
+    j["${m.SNAKE_CASE}"] = ${m.camelCase}.str;
+    % elif m.is_enum():
+    j["${m.SNAKE_CASE}"] = sdbusplus::message::convert_to_string(${m.camelCase});
+    % else:
     j["${m.SNAKE_CASE}"] = ${m.camelCase};
+    % endif
 % endfor
 
     // Add common source and pid info.
     nlohmann::json source_info = {};
-    source_info["FILE"] = source.file_name();
-    source_info["LINE"] = source.line();
-    source_info["COLUMN"] = source.column();
-    source_info["FUNCTION"] = source.function_name();
+    source_info["FILE"] = source_file;
+    source_info["FUNCTION"] = source_func;
+    source_info["LINE"] = source_line;
+    source_info["COLUMN"] = source_column;
     source_info["PID"] = pid;
     j["_SOURCE"] = source_info;
 
     return nlohmann::json{ { errName, std::move(j) } };
 }
+
+${event.CamelCase}::${event.CamelCase}(
+    const nlohmann::json& j, const std::source_location& s)
+{
+    const nlohmann::json& self = j.at(errName);
+
+% for m in event.metadata:
+    % if m.typeName == "object_path":
+    ${m.camelCase} = self.at("${m.SNAKE_CASE}").get<std::string>();
+    % elif m.is_enum():
+    ${m.camelCase} =
+        sdbusplus::message::convert_from_string<decltype(${m.camelCase})>(
+            self.at("${m.SNAKE_CASE}")
+        ).value();
+    % else:
+    ${m.camelCase} = self.at("${m.SNAKE_CASE}");
+    % endif
+% endfor
+
+    if (!self.contains("_SOURCE"))
+    {
+        source_file = s.file_name();
+        source_func = s.function_name();
+        source_line = s.line();
+        source_column = s.column();
+        pid = getpid();
+    }
+    else
+    {
+        source_file = self.at("FILE");
+        source_func = self.at("FUNCTION");
+        source_line = self.at("LINE");
+        source_column = self.at("COLUMN");
+        pid = self.at("PID");
+    }
+
+}
+
+namespace details
+{
+void register_${event.CamelCase}()
+{
+    sdbusplus::sdbuspp::register_event_helper<${event.CamelCase}>::hook();
+}
+}
diff --git a/tools/sdbusplus/templates/event.hpp.mako b/tools/sdbusplus/templates/event.hpp.mako
index 382f67c..8cf6e31 100644
--- a/tools/sdbusplus/templates/event.hpp.mako
+++ b/tools/sdbusplus/templates/event.hpp.mako
@@ -1,24 +1,29 @@
 struct ${event.CamelCase} final :
     public sdbusplus::exception::generated_event<${event.CamelCase}>
 {
+    ${event.CamelCase}(const nlohmann::json&, const std::source_location&);
 %if len(event.metadata) == 0:
     ${event.CamelCase}(
-        std::source_location source = std::source_location::current()) :
-        source(source), pid(getpid())
+        const std::source_location& source = std::source_location::current()) :
+        source_file(source.file_name()), source_func(source.function_name()),
+        source_line(source.line()), source_column(source.column()),
+        pid(getpid())
     {}
 %else:
     ${event.CamelCase}(
         ${", ".join([
             f"metadata_t<\"{m.SNAKE_CASE}\">, {m.cppTypeParam(events.name)} {m.camelCase}_"
             for m in event.metadata ])},
-        std::source_location source = std::source_location::current()) :
+        const std::source_location& source = std::source_location::current()) :
         ${", ".join([
             f"{m.camelCase}({m.camelCase}_)" for m in event.metadata ])},
-        source(source), pid(getpid())
+        source_file(source.file_name()), source_func(source.function_name()),
+        source_line(source.line()), source_column(source.column()),
+        pid(getpid())
     {}
 
 %for m in event.metadata:
-    const ${m.cppTypeParam(events.name)} ${m.camelCase};
+    ${m.cppTypeParam(events.name)} ${m.camelCase};
 %endfor
 
 %endif
@@ -26,7 +31,10 @@
     int set_error(SdBusInterface*, sd_bus_error*) const override;
     auto to_json() const -> nlohmann::json override;
 
-    std::source_location source;
+    std::string source_file;
+    std::string source_func;
+    size_t source_line;
+    size_t source_column;
     pid_t pid;
 
     static constexpr auto errName =
@@ -39,3 +47,12 @@
 
     static constexpr auto errErrno = ${event.errno};
 };
+
+namespace details
+{
+void register_${event.CamelCase}();
+[[gnu::constructor]] inline void force_register_${event.CamelCase}()
+{
+    register_${event.CamelCase}();
+}
+} // namespace details
diff --git a/tools/sdbusplus/templates/events.cpp.mako b/tools/sdbusplus/templates/events.cpp.mako
index 40105be..44290fb 100644
--- a/tools/sdbusplus/templates/events.cpp.mako
+++ b/tools/sdbusplus/templates/events.cpp.mako
@@ -1,6 +1,7 @@
-#include <${events.headerFile("event")}>
 #include <nlohmann/json.hpp>
-#
+#include <sdbusplus/sdbuspp_support/event.hpp>
+#include <${events.headerFile("event")}>
+#include <iostream>
 %if events.errors:
 
 namespace sdbusplus::error::${events.cppNamespacedClass()}