sdbus++: async: server: generate get-property fn

Add binding generation for get-property functions.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I60d44b25d718373acfd581faf158f45f5b5430e9
diff --git a/example/calculator-aserver.cpp b/example/calculator-aserver.cpp
index 220cda1..536ee25 100644
--- a/example/calculator-aserver.cpp
+++ b/example/calculator-aserver.cpp
@@ -13,6 +13,16 @@
         ctx.spawn(startup());
     }
 
+    auto get_property(owner_t) const
+    {
+        return "asdf";
+    }
+
+    auto get_property(last_result_t) const
+    {
+        return 42;
+    }
+
   private:
     auto startup() -> sdbusplus::async::task<>
     {
diff --git a/include/sdbusplus/async/server.hpp b/include/sdbusplus/async/server.hpp
index b7dfbdc..cfc4d78 100644
--- a/include/sdbusplus/async/server.hpp
+++ b/include/sdbusplus/async/server.hpp
@@ -50,6 +50,32 @@
         return static_cast<T*>(this)->ctx;
     }
 };
+
+/* Determine if a type has a get_property call. */
+template <typename Tag, typename Instance>
+concept has_get_property_nomsg =
+    requires(const Instance& i) { i.get_property(Tag{}); };
+
+/* Determine if a type has a get property call that requries a msg. */
+template <typename Tag, typename Instance>
+concept has_get_property_msg = requires(
+    const Instance& i, sdbusplus::message_t& m) { i.get_property(Tag{}, m); };
+
+/* Determine if a type has any get_property call. */
+template <typename Tag, typename Instance>
+concept has_get_property = has_get_property_nomsg<Tag, Instance> ||
+                           has_get_property_msg<Tag, Instance>;
+
+/* Determine if a type is missing the 'const' on get-property calls. */
+template <typename Tag, typename Instance>
+concept has_get_property_missing_const =
+    !has_get_property<Tag, Instance> &&
+    (
+        requires(Instance& i) { i.get_property(Tag{}); } ||
+        requires(Instance& i, sdbusplus::message_t& m) {
+            i.get_property(Tag{}, m);
+        });
+
 } // namespace server::details
 
 } // namespace sdbusplus::async
diff --git a/tools/meson.build b/tools/meson.build
index 8e1e631..dfc1f03 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -24,6 +24,12 @@
   'sdbusplus/templates/method.prototype.hpp.mako',
   'sdbusplus/templates/method.server.vtable.cpp.mako',
   'sdbusplus/templates/property.md.mako',
+  'sdbusplus/templates/property.aserver.callback.hpp.mako',
+  'sdbusplus/templates/property.aserver.get.hpp.mako',
+  'sdbusplus/templates/property.aserver.tag.hpp.mako',
+  'sdbusplus/templates/property.aserver.typeid.hpp.mako',
+  'sdbusplus/templates/property.aserver.value.hpp.mako',
+  'sdbusplus/templates/property.aserver.vtable.hpp.mako',
   'sdbusplus/templates/property.client.hpp.mako',
   'sdbusplus/templates/property.server.cpp.mako',
   'sdbusplus/templates/property.server.vtable.cpp.mako',
diff --git a/tools/sdbusplus/templates/interface.aserver.hpp.mako b/tools/sdbusplus/templates/interface.aserver.hpp.mako
index 1ec817a..f6e419b 100644
--- a/tools/sdbusplus/templates/interface.aserver.hpp.mako
+++ b/tools/sdbusplus/templates/interface.aserver.hpp.mako
@@ -1,6 +1,7 @@
 #pragma once
 #include <sdbusplus/async/server.hpp>
 #include <sdbusplus/server/interface.hpp>
+#include <sdbusplus/server/transaction.hpp>
 
 #include <type_traits>
 
@@ -38,10 +39,12 @@
 namespace details
 {
 
+namespace server_details = sdbusplus::async::server::details;
+
 template <typename Instance, typename Server>
 class ${interface.classname} :
     public sdbusplus::common::${interface.cppNamespacedClass()},
-    protected sdbusplus::async::server::details::server_context_friend
+    protected server_details::server_context_friend
 {
   public:
     explicit ${interface.classname}(const char* path) :
@@ -65,24 +68,46 @@
         _${interface.joinedName("_", "interface")}.emit_removed();
     }
 
+    /* Property access tags. */
+% for p in interface.properties:
+${p.render(loader, "property.aserver.tag.hpp.mako", property=p, interface=interface)}\
+% endfor
+
+% for p in interface.properties:
+${p.render(loader, "property.aserver.get.hpp.mako", property=p, interface=interface)}
+% endfor
+
   private:
     /** @return the async context */
     sdbusplus::async::context& _context()
     {
-        return sdbusplus::async::server::details::server_context_friend::
-            context<Server>();
+        return server_details::server_context_friend::context<Server>();
     }
 
     sdbusplus::server::interface_t
         _${interface.joinedName("_", "interface")};
 
+% for p in interface.properties:
+${p.render(loader, "property.aserver.value.hpp.mako", property=p, interface=interface)}\
+% endfor
+
+% for p in interface.properties:
+${p.render(loader, "property.aserver.typeid.hpp.mako", property=p, interface=interface)}\
+% endfor
 % for s in interface.signals:
 ${s.render(loader, "signal.aserver.typeid.hpp.mako", signal=s, interface=interface)}\
 % endfor
 
+% for p in interface.properties:
+${p.render(loader, "property.aserver.callback.hpp.mako", property=p, interface=interface)}
+% endfor
+
     static constexpr sdbusplus::vtable_t _vtable[] = {
         vtable::start(),
 
+% for p in interface.properties:
+${p.render(loader, "property.aserver.vtable.hpp.mako", property=p, interface=interface)}\
+% endfor
 % for s in interface.signals:
 ${s.render(loader, "signal.aserver.vtable.hpp.mako", signal=s, interface=interface)}\
 % endfor
diff --git a/tools/sdbusplus/templates/property.aserver.callback.hpp.mako b/tools/sdbusplus/templates/property.aserver.callback.hpp.mako
new file mode 100644
index 0000000..aa43b98
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.callback.hpp.mako
@@ -0,0 +1,62 @@
+<%
+p_name = property.snake_case
+p_tag = property.snake_case + "_t"
+p_type = property.cppTypeParam(interface.name)
+i_name = interface.classname
+%>\
+    static int _callback_get_${p_name}(
+        sd_bus*, const char*, const char*, const char*,
+        sd_bus_message* reply, void* context,
+        sd_bus_error* error [[maybe_unused]])
+    {
+        auto self = static_cast<${i_name}*>(context);
+
+        try
+        {
+            auto m = sdbusplus::message_t{reply};
+
+            // Set up the transaction.
+            server::transaction::set_id(m);
+
+            // Get property value and add to message.
+            if constexpr (server_details::has_get_property_msg<${p_tag},
+                                                               Instance>)
+            {
+                auto v = self->${p_name}(m);
+                static_assert(std::is_convertible_v<decltype(v), ${p_type}>,
+                              "Property doesn't convert to '${p_type}'.");
+                m.append<${p_type}>(std::move(v));
+            }
+            else
+            {
+                auto v = self->${p_name}();
+                static_assert(std::is_convertible_v<decltype(v), ${p_type}>,
+                              "Property doesn't convert to '${p_type}'.");
+                m.append<${p_type}>(std::move(v));
+            }
+        }
+        % for e in property.errors:
+        catch (const ${interface.errorNamespacedClass(e)}& e)
+        {
+            return sd_bus_error_set(error, e.name(), e.description());
+        }
+        % endfor
+        catch (const std::exception&)
+        {
+            self->_context().get_bus().set_current_exception(
+                std::current_exception());
+            return -EINVAL;
+        }
+
+        return 1;
+    }
+
+% if 'const' not in property.flags and 'readonly' not in property.flags:
+    static int _callback_set_${p_name}(
+        sd_bus*, const char*, const char*, const char*,
+        sd_bus_message* value[[maybe_unused]], void* context [[maybe_unused]],
+        sd_bus_error* error [[maybe_unused]])
+    {
+        return -EINVAL;
+    }
+% endif
diff --git a/tools/sdbusplus/templates/property.aserver.get.hpp.mako b/tools/sdbusplus/templates/property.aserver.get.hpp.mako
new file mode 100644
index 0000000..00bb69f
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.get.hpp.mako
@@ -0,0 +1,23 @@
+<%
+p_name = property.snake_case;
+p_tag = property.snake_case + "_t"
+%>\
+    auto ${p_name}() const
+        requires server_details::has_get_property_nomsg<${p_tag}, Instance>
+    {
+        return static_cast<const Instance*>(this)->get_property(${p_tag}{});
+    }
+    auto ${p_name}(sdbusplus::message_t& m) const
+        requires server_details::has_get_property_msg<${p_tag}, Instance>
+    {
+        return static_cast<const Instance*>(this)->get_property(${p_tag}{}, m);
+    }
+    auto ${p_name}() const noexcept
+        requires (!server_details::has_get_property<${p_tag}, Instance>)
+    {
+        static_assert(
+            !server_details::has_get_property_missing_const<${p_tag},
+                                                            Instance>,
+            "Missing const on get_property(${p_tag})?");
+        return _${p_name};
+    }
diff --git a/tools/sdbusplus/templates/property.aserver.tag.hpp.mako b/tools/sdbusplus/templates/property.aserver.tag.hpp.mako
new file mode 100644
index 0000000..6ab59db
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.tag.hpp.mako
@@ -0,0 +1,10 @@
+<%
+p_tag = property.snake_case + "_t"
+p_type = property.cppTypeParam(interface.name)
+%>\
+    struct ${p_tag}
+    {
+        using value_type = ${p_type};
+        ${p_tag}() = default;
+        explicit ${p_tag}(value_type) {}
+    };
diff --git a/tools/sdbusplus/templates/property.aserver.typeid.hpp.mako b/tools/sdbusplus/templates/property.aserver.typeid.hpp.mako
new file mode 100644
index 0000000..19663e9
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.typeid.hpp.mako
@@ -0,0 +1,3 @@
+    static constexpr auto _property_typeid_${property.snake_case} =
+        utility::tuple_to_array(message::types::type_id<\
+${property.cppTypeParam(interface.name)}>());
diff --git a/tools/sdbusplus/templates/property.aserver.value.hpp.mako b/tools/sdbusplus/templates/property.aserver.value.hpp.mako
new file mode 100644
index 0000000..452bab3
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.value.hpp.mako
@@ -0,0 +1,17 @@
+<%
+p_name = property.snake_case
+p_type = property.cppTypeParam(interface.name)
+
+def p_value():
+    if property.defaultValue == None:
+        return "{}"
+
+    value = str(property.defaultValue)
+    enum_prefix = ""
+    if property.is_enum():
+        enum_prefix = f"{p_type}::"
+
+    return f" = {enum_prefix}{value}"
+
+%>\
+    ${p_type} _${p_name}${p_value()};
diff --git a/tools/sdbusplus/templates/property.aserver.vtable.hpp.mako b/tools/sdbusplus/templates/property.aserver.vtable.hpp.mako
new file mode 100644
index 0000000..3618049
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.vtable.hpp.mako
@@ -0,0 +1,12 @@
+        vtable::property("${property.name}",
+                         _property_typeid_${property.snake_case}.data(),
+                         _callback_get_${property.snake_case},
+% if 'const' not in property.flags and 'readonly' not in property.flags:
+                         _callback_set_${property.snake_case},
+% endif
+% if not property.cpp_flags:
+                         vtable::property_::emits_change\
+% else:
+                         ${property.cpp_flags}\
+% endif
+),