plugin: implement addRedfishHandler

This commit implements the first Redfish Plugin macro:
  REDFISH_HANDLER_ADD

This plugin macro gives plugin user ability to add a platform specific
route. The plugin takes URL, Verb, Redfish Privileges, and the callback.
A few refacotring has been implemented (e.g., string_view instead of
string reference) to make this happen.

A unit test is added which demostrates how the plugin adds a new route.
The test also ensures duplicate routes throws.

Tested: unit test.

Change-Id: Id15d79a681f74367ce5108c67d9da146eac9fef8
Signed-off-by: Nan Zhou <nanzhou@google.com>
diff --git a/http/app.hpp b/http/app.hpp
index 85104fc..bd30007 100644
--- a/http/app.hpp
+++ b/http/app.hpp
@@ -12,6 +12,7 @@
 #include <boost/beast/ssl/ssl_stream.hpp>
 #endif
 
+#include <array>
 #include <chrono>
 #include <cstdint>
 #include <functional>
@@ -118,6 +119,13 @@
         return router.getRoutes(parent);
     }
 
+    template <uint64_t Tag, typename Func, size_t N>
+    void addHandler(std::string_view url, boost::beast::http::verb verb,
+                    const std::array<redfish::Privileges, N>& p, Func&& func)
+    {
+        router.newRuleTagged<Tag>(url).methods(verb).privileges(p)(func);
+    }
+
 #ifdef BMCWEB_ENABLE_SSL
     App& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx)
     {
diff --git a/http/routing.hpp b/http/routing.hpp
index 3c9d555..c60721c 100644
--- a/http/routing.hpp
+++ b/http/routing.hpp
@@ -41,7 +41,7 @@
 class BaseRule
 {
   public:
-    explicit BaseRule(const std::string& thisRule) : rule(thisRule)
+    explicit BaseRule(std::string_view thisRule) : rule(thisRule)
     {}
 
     virtual ~BaseRule() = default;
@@ -596,7 +596,7 @@
   public:
     using self_t = TaggedRule<Args...>;
 
-    explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn)
+    explicit TaggedRule(std::string_view ruleIn) : BaseRule(ruleIn)
     {}
 
     void validate() override
@@ -806,7 +806,7 @@
 
     template <uint64_t N>
     typename black_magic::Arguments<N>::type::template rebind<TaggedRule>&
-        newRuleTagged(const std::string& rule)
+        newRuleTagged(std::string_view rule)
     {
         using RuleT = typename black_magic::Arguments<N>::type::template rebind<
             TaggedRule>;
diff --git a/meson.build b/meson.build
index 29e1740..0245754 100644
--- a/meson.build
+++ b/meson.build
@@ -432,6 +432,7 @@
   'test/redfish-core/lib/thermal_subsystem_test.cpp',
   'test/redfish-core/lib/power_subsystem_test.cpp',
   # 'test/redfish-core/lib/chassis_test.cpp',
+  'plugins/macros_test.cpp',
 )
 
 if(get_option('tests').enabled())
diff --git a/plugins/macros.hpp b/plugins/macros.hpp
new file mode 100644
index 0000000..0d90605
--- /dev/null
+++ b/plugins/macros.hpp
@@ -0,0 +1,26 @@
+#include "app_singleton.hpp"
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "privileges.hpp"
+#include "utility.hpp"
+
+#include <boost/beast/http/verb.hpp>
+
+#include <array>
+#include <string_view>
+
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define REDFISH_HANDLER_ADD(url, verb, privileges, func)                       \
+    plugins::addRedfishHandler<crow::black_magic::getParameterTag(url)>(       \
+        url, verb, privileges, func);
+
+namespace plugins
+{
+template <uint64_t tag, typename RedfishHandler, size_t N>
+void addRedfishHandler(std::string_view url, boost::beast::http::verb verb,
+                       const std::array<redfish::Privileges, N>& p,
+                       RedfishHandler&& func)
+{
+    crow::globalBmcWebApp->addHandler<tag>(url, verb, p, func);
+}
+} // namespace plugins
\ No newline at end of file
diff --git a/plugins/macros_test.cpp b/plugins/macros_test.cpp
new file mode 100644
index 0000000..ca15030
--- /dev/null
+++ b/plugins/macros_test.cpp
@@ -0,0 +1,75 @@
+#include "app.hpp"
+#include "app_singleton.hpp"
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "macros.hpp"
+#include "privileges.hpp"
+#include "registries/privilege_registry.hpp"
+#include "utility.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/beast/http/verb.hpp>
+
+#include <memory>
+#include <string_view>
+
+#include <gtest/gtest.h>
+
+namespace crow
+{
+namespace
+{
+
+class PluginMacrosTest : public testing::Test
+{
+  public:
+    PluginMacrosTest()
+    {
+        io = std::make_shared<boost::asio::io_context>();
+        app = std::make_unique<crow::App>(io);
+        crow::globalBmcWebApp = app.get();
+    }
+
+  private:
+    std::shared_ptr<boost::asio::io_context> io;
+    std::unique_ptr<crow::App> app;
+};
+
+TEST_F(PluginMacrosTest, AddHandlerCanBeInvoked)
+{
+    bool called = false;
+    auto callback = [&called](const Request&,
+                              const std::shared_ptr<bmcweb::AsyncResp>&) {
+        called = true;
+    };
+
+    REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
+                        redfish::privileges::getChassis, std::move(callback));
+
+    crow::globalBmcWebApp->validate();
+
+    constexpr std::string_view url = "/hello/";
+    std::error_code ec;
+    Request req{{boost::beast::http::verb::get, url, 11}, ec};
+
+    auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+    crow::globalBmcWebApp->handle(req, asyncResp);
+
+    EXPECT_TRUE(called);
+}
+
+TEST_F(PluginMacrosTest, DuplicateHandlerThrows)
+{
+    auto callback =
+        [](const Request&, const std::shared_ptr<bmcweb::AsyncResp>&) {};
+
+    REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
+                        redfish::privileges::getChassis, std::move(callback));
+    REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
+                        redfish::privileges::getChassis, std::move(callback));
+
+    EXPECT_ANY_THROW(crow::globalBmcWebApp->validate());
+}
+
+} // namespace
+} // namespace crow
\ No newline at end of file