sdbus++: events: create event classes

Generate an equivalent amount of exception class as the `errors.yaml`
supported and convert the examples to use it.

Tested:

```
‣ Type=error  Endian=l  Flags=1  Version=1 Cookie=9  ReplyCookie=2  Timestamp="Tue 2024-09-17 18:21:35.649674 UTC"
  Sender=:1.2489  Destination=:1.2495
  ErrorName=net.poettering.Calculator.DivisionByZero  ErrorMessage="Attempted to divide by zero."
  UniqueName=:1.2489
  MESSAGE "s" {
          STRING "Attempted to divide by zero.";
  };
```

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I4b2671838639d1e8be1ccdf655b699a98b9c2116
diff --git a/example/calculator-aserver.cpp b/example/calculator-aserver.cpp
index 33a582a..7a5051e 100644
--- a/example/calculator-aserver.cpp
+++ b/example/calculator-aserver.cpp
@@ -21,7 +21,7 @@
     auto method_call(divide_t, auto x,
                      auto y) -> sdbusplus::async::task<divide_t::return_type>
     {
-        using sdbusplus::error::net::poettering::calculator::DivisionByZero;
+        using sdbusplus::error::net::poettering::Calculator::DivisionByZero;
         if (y == 0)
         {
             status(State::Error);
diff --git a/example/calculator-server.cpp b/example/calculator-server.cpp
index 7ee4218..2d0efca 100644
--- a/example/calculator-server.cpp
+++ b/example/calculator-server.cpp
@@ -1,5 +1,4 @@
 #include <net/poettering/Calculator/client.hpp>
-#include <net/poettering/Calculator/error.hpp>
 #include <net/poettering/Calculator/server.hpp>
 #include <sdbusplus/server.hpp>
 
@@ -29,7 +28,7 @@
      */
     int64_t divide(int64_t x, int64_t y) override
     {
-        using sdbusplus::error::net::poettering::calculator::DivisionByZero;
+        using sdbusplus::error::net::poettering::Calculator::DivisionByZero;
         if (y == 0)
         {
             status(State::Error);
diff --git a/example/yaml/net/poettering/Calculator.interface.yaml b/example/yaml/net/poettering/Calculator.interface.yaml
index f130e59..affc2d4 100644
--- a/example/yaml/net/poettering/Calculator.interface.yaml
+++ b/example/yaml/net/poettering/Calculator.interface.yaml
@@ -40,7 +40,7 @@
             description: >
                 The result of (x/y).
       errors:
-          - self.Error.DivisionByZero
+          - self.DivisionByZero
     - name: Clear
       flags:
           - unprivileged
@@ -65,7 +65,7 @@
       description: >
           The name of the owner of the Calculator.
       errors:
-          - self.Error.PermissionDenied
+          - self.PermissionDenied
 signals:
     - name: Cleared
       description: >
diff --git a/include/sdbusplus/exception.hpp b/include/sdbusplus/exception.hpp
index e7e31ea..a9ddab0 100644
--- a/include/sdbusplus/exception.hpp
+++ b/include/sdbusplus/exception.hpp
@@ -41,6 +41,32 @@
     int get_errno() const noexcept override;
 };
 
+/** base exception for all new errors and events created by the sdbus++
+ * generator */
+template <typename Event>
+struct generated_event : public generated_exception
+{
+    const char* name() const noexcept override
+    {
+        return Event::errName;
+    }
+
+    const char* description() const noexcept override
+    {
+        return Event::errDesc;
+    }
+
+    const char* what() const noexcept override
+    {
+        return Event::errWhat;
+    }
+
+    int get_errno() const noexcept override
+    {
+        return Event::errErrno;
+    }
+};
+
 /** base exception class for all errors generated by sdbusplus itself. */
 struct internal_exception : public exception
 {};
diff --git a/tools/sdbusplus/event.py b/tools/sdbusplus/event.py
index dd343cf..36d4673 100644
--- a/tools/sdbusplus/event.py
+++ b/tools/sdbusplus/event.py
@@ -24,8 +24,9 @@
 
 class EventElement(NamedElement):
     def __init__(self, **kwargs):
+        self.is_error = kwargs.pop("is_error", False)
         self.deprecated = kwargs.pop("deprecated", None)
-        self.errno = kwargs.pop("errno", None)
+        self.errno = kwargs.pop("errno", "EIO")
         self.languages = {
             key: EventLanguage(**kwargs.pop(key, {})) for key in ["en"]
         }
@@ -39,6 +40,25 @@
 
         super(EventElement, self).__init__(**kwargs)
 
+    def __getattribute__(self, name):
+        lam = {"description": lambda: self.__description()}.get(name)
+
+        if lam:
+            return lam()
+        try:
+            return super(EventElement, self).__getattribute__(name)
+        except Exception:
+            raise AttributeError(
+                "Attribute '%s' not found in %s.EventElement"
+                % (name, self.__module__)
+            )
+
+    def __description(self):
+        en = self.languages["en"]
+        if en.description:
+            return en.description
+        return en.message
+
     @staticmethod
     def syslog_severity(severity: str) -> str:
         return {
@@ -81,7 +101,9 @@
 
     def __init__(self, **kwargs):
         self.version = kwargs.pop("version")
-        self.errors = [EventElement(**n) for n in kwargs.pop("errors", [])]
+        self.errors = [
+            EventElement(**n, is_error=True) for n in kwargs.pop("errors", [])
+        ]
         self.events = [EventElement(**n) for n in kwargs.pop("events", [])]
 
         super(Event, self).__init__(**kwargs)
diff --git a/tools/sdbusplus/interface.py b/tools/sdbusplus/interface.py
index d801814..eade1de 100644
--- a/tools/sdbusplus/interface.py
+++ b/tools/sdbusplus/interface.py
@@ -44,14 +44,22 @@
         includes = []
         for e in inc_list:
             e = e.replace("self.", self.name + ".")
-            n = "/".join(
-                e.split(".")[:-2],  # ignore the Error.Name
-            )
-            includes.append(f"{n}/error.hpp")
+            if ".Error." in e:
+                n = "/".join(
+                    e.split(".")[:-2],  # ignore the Error.Name at the end
+                )
+                includes.append(f"{n}/error.hpp")
+            else:
+                n = "/".join(
+                    e.split(".")[:-1],  # ignore the .Name at the end
+                )
+                includes.append(f"{n}/event.hpp")
         return sorted(set(includes))
 
     def errorNamespacedClass(self, error):
         error = error.replace("self.", self.name + ".")
+        if ".Error" not in error:
+            error = "error." + error
         return "sdbusplus::" + "::".join(error.split("."))
 
     def enum_includes(self, inc_list):
diff --git a/tools/sdbusplus/templates/event.hpp.mako b/tools/sdbusplus/templates/event.hpp.mako
index e69de29..78a31d9 100644
--- a/tools/sdbusplus/templates/event.hpp.mako
+++ b/tools/sdbusplus/templates/event.hpp.mako
@@ -0,0 +1,12 @@
+struct ${event.CamelCase} final :
+    public sdbusplus::exception::generated_event<${event.CamelCase}>
+{
+    static constexpr auto errName =
+        "${events.name}.${event.name}";
+    static constexpr auto errDesc =
+        "${event.description}";
+    static constexpr auto errWhat =
+        "${events.name}.${event.name}: ${event.description}";
+
+    static constexpr auto errErrno = ${event.errno};
+};
diff --git a/tools/sdbusplus/templates/event.md.mako b/tools/sdbusplus/templates/event.md.mako
index 012d833..de6a69b 100644
--- a/tools/sdbusplus/templates/event.md.mako
+++ b/tools/sdbusplus/templates/event.md.mako
@@ -8,7 +8,7 @@
 ${event.languages["en"].message}
 % endif
 
-% if event.errno:
+% if event.is_error:
 - severity: `${event.severity}`
 - errno: `${event.errno}`
 % endif
diff --git a/tools/sdbusplus/templates/events.hpp.mako b/tools/sdbusplus/templates/events.hpp.mako
index e69de29..434cc0b 100644
--- a/tools/sdbusplus/templates/events.hpp.mako
+++ b/tools/sdbusplus/templates/events.hpp.mako
@@ -0,0 +1,30 @@
+/* Events for ${events.name}
+ * Version: ${events.version}
+ */
+#pragma once
+#include <sdbusplus/exception.hpp>
+
+#include <cerrno>
+
+%if events.errors:
+
+namespace sdbusplus::error::${events.cppNamespacedClass()}
+{
+% for e in events.errors:
+
+${events.render(loader, "event.hpp.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.hpp.mako", events=events, event=e)}\
+% endfor
+
+} // namespace sdbusplus::event::${events.cppNamespacedClass()}
+%endif