query parameter: add a way to delegate certain parameter

The generic query parameter handlers might not be performant, e.g.,
Expand in the sensor collections. This change adds a way to delegate
query parameter processsing to redfish-core codes:

1. introduced a separate struct in the setUpRedfishRoute function, with
which redfish-core codes can easily set delegation for each parameter;
for example, the children patch of this PR will implement an efficient
handler for sensor collection Expand, top, and skip.
2. introduced a separate Redfish route for delegation; this routes takes
the struct described above and changes the query object so that query
parameters are delegated.
3. in order to avoid copying Query objects and run delegation check
twice, the |setUpRedfishRouteWithDelegation| function sets |delegated|
so that callers can directly use it to determinte if delegation is
needed, and what delegated Queries are

Tested:
1. added unit tests
2. the default redfish route is still working correctly

Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Change-Id: I77597ad7e8b40ac179d86dc9be1a35767cc61284
diff --git a/redfish-core/include/query.hpp b/redfish-core/include/query.hpp
index 7db370d..4dd03c6 100644
--- a/redfish-core/include/query.hpp
+++ b/redfish-core/include/query.hpp
@@ -7,9 +7,14 @@
 namespace redfish
 {
 
-[[nodiscard]] inline bool setUpRedfishRoute(crow::App& app,
-                                            const crow::Request& req,
-                                            crow::Response& res)
+// Sets up the Redfish Route and delegates some of the query parameter
+// processing. |queryCapabilities| stores which query parameters will be
+// handled by redfish-core/lib codes, then default query parameter handler won't
+// process these parameters.
+[[nodiscard]] inline bool setUpRedfishRouteWithDelegation(
+    crow::App& app, const crow::Request& req, crow::Response& res,
+    query_param::Query& delegated,
+    const query_param::QueryCapabilities& queryCapabilities)
 {
     BMCWEB_LOG_DEBUG << "setup redfish route";
 
@@ -42,9 +47,9 @@
         return true;
     }
 
+    delegated = query_param::delegate(queryCapabilities, *queryOpt);
     std::function<void(crow::Response&)> handler =
         res.releaseCompleteRequestHandler();
-
     res.setCompleteRequestHandler(
         [&app, handler(std::move(handler)),
          query{*queryOpt}](crow::Response& res) mutable {
@@ -52,4 +57,15 @@
         });
     return true;
 }
+
+// Sets up the Redfish Route. All parameters are handled by the default handler.
+[[nodiscard]] inline bool setUpRedfishRoute(crow::App& app,
+                                            const crow::Request& req,
+                                            crow::Response& res)
+{
+    // This route |delegated| is never used
+    query_param::Query delegated;
+    return setUpRedfishRouteWithDelegation(app, req, res, delegated,
+                                           query_param::QueryCapabilities{});
+}
 } // namespace redfish
diff --git a/redfish-core/include/utils/query_param.hpp b/redfish-core/include/utils/query_param.hpp
index 7aef2f0..60e572f 100644
--- a/redfish-core/include/utils/query_param.hpp
+++ b/redfish-core/include/utils/query_param.hpp
@@ -24,13 +24,58 @@
     Both,
 };
 
+// The struct stores the parsed query parameters of the default Redfish route.
 struct Query
 {
+    // Only
     bool isOnly = false;
-    uint8_t expandLevel = 1;
+    // Expand
+    uint8_t expandLevel = 0;
     ExpandType expandType = ExpandType::None;
 };
 
+// The struct defines how resource handlers in redfish-core/lib/ can handle
+// query parameters themselves, so that the default Redfish route will delegate
+// the processing.
+struct QueryCapabilities
+{
+    bool canDelegateOnly = false;
+    uint8_t canDelegateExpandLevel = 0;
+};
+
+// Delegates query parameters according to the given |queryCapabilities|
+// This function doesn't check query parameter conflicts since the parse
+// function will take care of it.
+// Returns a delegated query object which can be used by individual resource
+// handlers so that handlers don't need to query again.
+inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
+{
+    Query delegated;
+    // delegate only
+    if (query.isOnly && queryCapabilities.canDelegateOnly)
+    {
+        delegated.isOnly = true;
+        query.isOnly = false;
+    }
+    // delegate expand as much as we can
+    if (query.expandType != ExpandType::None)
+    {
+        delegated.expandType = query.expandType;
+        if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
+        {
+            query.expandType = ExpandType::None;
+            delegated.expandLevel = query.expandLevel;
+            query.expandLevel = 0;
+        }
+        else
+        {
+            query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
+            delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
+        }
+    }
+    return delegated;
+}
+
 inline bool getExpandType(std::string_view value, Query& query)
 {
     if (value.empty())
diff --git a/redfish-core/include/utils/query_param_test.cpp b/redfish-core/include/utils/query_param_test.cpp
index 69ed673..7a2da71 100644
--- a/redfish-core/include/utils/query_param_test.cpp
+++ b/redfish-core/include/utils/query_param_test.cpp
@@ -7,6 +7,67 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
+namespace redfish::query_param
+{
+namespace
+{
+
+TEST(Delegate, OnlyPositive)
+{
+    Query query{
+        .isOnly = true,
+    };
+    QueryCapabilities capabilities{
+        .canDelegateOnly = true,
+    };
+    Query delegated = delegate(capabilities, query);
+    EXPECT_TRUE(delegated.isOnly);
+    EXPECT_FALSE(query.isOnly);
+}
+
+TEST(Delegate, ExpandPositive)
+{
+    Query query{
+        .isOnly = false,
+        .expandLevel = 5,
+        .expandType = ExpandType::Both,
+    };
+    QueryCapabilities capabilities{
+        .canDelegateExpandLevel = 3,
+    };
+    Query delegated = delegate(capabilities, query);
+    EXPECT_FALSE(delegated.isOnly);
+    EXPECT_EQ(delegated.expandLevel, capabilities.canDelegateExpandLevel);
+    EXPECT_EQ(delegated.expandType, ExpandType::Both);
+    EXPECT_EQ(query.expandLevel, 2);
+}
+
+TEST(Delegate, OnlyNegative)
+{
+    Query query{
+        .isOnly = true,
+    };
+    QueryCapabilities capabilities{
+        .canDelegateOnly = false,
+    };
+    Query delegated = delegate(capabilities, query);
+    EXPECT_FALSE(delegated.isOnly);
+    EXPECT_EQ(query.isOnly, true);
+}
+
+TEST(Delegate, ExpandNegative)
+{
+    Query query{
+        .isOnly = false,
+        .expandType = ExpandType::None,
+    };
+    Query delegated = delegate(QueryCapabilities{}, query);
+    EXPECT_EQ(delegated.expandType, ExpandType::None);
+}
+
+} // namespace
+} // namespace redfish::query_param
+
 TEST(QueryParams, ParseParametersOnly)
 {
     auto ret = boost::urls::parse_relative_ref("/redfish/v1?only");