sdbus++: events: create json for events

Create JSON for the events containing their metadata and leverage that
as the "message" portion of the `sd_bus_error`.  This will allow
unpacking the message on a client side back to the original exception.

Tested:
```
$ busctl --user call net.poettering.Calculator /net/poettering/calculator net.poettering.Calculator Divide xx 5 0
Call failed: {"net.poettering.Calculator.DivisionByZero":{"FOO":"unused"}}
```

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I4edd76d983267f28e51c4aa41902d45f5d6da793
diff --git a/.gitignore b/.gitignore
index c4fa618..b67be58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 # Meson build directories.
 /build*/
-/subprojects/*/
+/subprojects/*
+!/subprojects/*.wrap
 
 # Created by https://www.toptal.com/developers/gitignore/api/python
 # Edit at https://www.toptal.com/developers/gitignore?templates=python
diff --git a/include/sdbusplus/exception.hpp b/include/sdbusplus/exception.hpp
index 0ba477c..a8707f6 100644
--- a/include/sdbusplus/exception.hpp
+++ b/include/sdbusplus/exception.hpp
@@ -2,6 +2,7 @@
 
 #include <systemd/sd-bus.h>
 
+#include <nlohmann/json_fwd.hpp>
 #include <sdbusplus/sdbus.hpp>
 #include <sdbusplus/utility/consteval_string.hpp>
 
@@ -72,6 +73,9 @@
 
     template <utility::details::consteval_string_holder V>
     using metadata_t = utility::consteval_string<V>;
+
+  protected:
+    mutable std::string jsonString;
 };
 
 /** base exception class for all errors generated by sdbusplus itself. */
diff --git a/meson.build b/meson.build
index 52e330a..598ad64 100644
--- a/meson.build
+++ b/meson.build
@@ -12,6 +12,7 @@
 )
 
 libsystemd_pkg = dependency('libsystemd')
+nlohmann_json_dep = dependency('nlohmann_json', include_type: 'system')
 
 python = import('python')
 python_bin = python.find_installation('python3', modules:['inflection', 'yaml', 'mako'])
@@ -40,7 +41,10 @@
     'sdbusplus',
     libsdbusplus_src,
     include_directories: root_inc,
-    dependencies: libsystemd_pkg,
+    dependencies: [
+        libsystemd_pkg,
+        nlohmann_json_dep
+    ],
     version: meson.project_version(),
     install: true,
 )
@@ -60,7 +64,11 @@
 sdbusplus_dep = declare_dependency(
     include_directories: root_inc,
     link_with: libsdbusplus,
-    dependencies: [ libsystemd_pkg, boost_dep ],
+    dependencies: [
+        boost_dep,
+        libsystemd_pkg,
+        nlohmann_json_dep,
+    ],
 )
 
 subdir('tools')
diff --git a/subprojects/nlohmann_json.wrap b/subprojects/nlohmann_json.wrap
new file mode 100644
index 0000000..3745380
--- /dev/null
+++ b/subprojects/nlohmann_json.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+revision = HEAD
+url = https://github.com/nlohmann/json.git
+
+[provide]
+nlohmann_json = nlohmann_json_dep
diff --git a/tools/sdbusplus/templates/event.cpp.mako b/tools/sdbusplus/templates/event.cpp.mako
index e69de29..b956fa4 100644
--- a/tools/sdbusplus/templates/event.cpp.mako
+++ b/tools/sdbusplus/templates/event.cpp.mako
@@ -0,0 +1,28 @@
+int ${event.CamelCase}::set_error(sd_bus_error* e) const
+{
+    if (jsonString.empty())
+    {
+        jsonString = to_json().dump();
+    }
+
+    return sd_bus_error_set(e, errName, jsonString.c_str());
+}
+
+int ${event.CamelCase}::set_error(SdBusInterface* i, sd_bus_error* e) const
+{
+    if (jsonString.empty())
+    {
+        jsonString = to_json().dump();
+    }
+
+    return i->sd_bus_error_set(e, errName, jsonString.c_str());
+}
+
+auto ${event.CamelCase}::to_json() const -> nlohmann::json
+{
+    nlohmann::json j = { };
+% for m in event.metadata:
+    j["${m.SNAKE_CASE}"] = ${m.camelCase};
+% endfor
+    return nlohmann::json{ { errName, std::move(j) } };
+}
diff --git a/tools/sdbusplus/templates/event.hpp.mako b/tools/sdbusplus/templates/event.hpp.mako
index 24bc819..5425d0a 100644
--- a/tools/sdbusplus/templates/event.hpp.mako
+++ b/tools/sdbusplus/templates/event.hpp.mako
@@ -12,10 +12,14 @@
     {}
 
 %for m in event.metadata:
-    ${m.cppTypeParam(events.name)} ${m.camelCase};
+    const ${m.cppTypeParam(events.name)} ${m.camelCase};
 %endfor
 
 %endif
+    int set_error(sd_bus_error*) const override;
+    int set_error(SdBusInterface*, sd_bus_error*) const override;
+    auto to_json() const -> nlohmann::json;
+
     static constexpr auto errName =
         "${events.name}.${event.name}";
     static constexpr auto errDesc =
diff --git a/tools/sdbusplus/templates/events.cpp.mako b/tools/sdbusplus/templates/events.cpp.mako
index e69de29..737d5bd 100644
--- a/tools/sdbusplus/templates/events.cpp.mako
+++ b/tools/sdbusplus/templates/events.cpp.mako
@@ -0,0 +1,25 @@
+#include <${events.headerFile("event")}>
+#include <nlohmann/json.hpp>
+
+%if events.errors:
+
+namespace sdbusplus::error::${events.cppNamespacedClass()}
+{
+% for e in events.errors:
+
+${events.render(loader, "event.cpp.mako", events=events, event=e)}\
+% endfor
+
+} // namespace sdbusplus::error::${events.cppNamespacedClass()}
+%endif
+%if events.errors:
+
+namespace sdbusplus::event::${events.cppNamespacedClass()}
+{
+% for e in events.events:
+
+${events.render(loader, "event.cpp.mako", events=events, event=e)}\
+% endfor
+
+} // namespace sdbusplus::event::${events.cppNamespacedClass()}
+%endif