Implement Expand
Section 7.3 of the Redfish specification lays out a feature called
"expand" that allows users to expand portions of the Redfish tree
automatically on the server side. This commit implements them to the
specification.
To accomplish this, a new class, MultiAsyncResp is created, that allows
RAII objects to handle lifetime properly. When an expand query is
generated, a MultiAsyncResp object is instantiated, which allows "new"
requests to attach themselves to the multi object, and keep the request
alive until they all complete. This also allows requests to be created,
while requests are in flight, which is required for queries above
depth=1.
Negatives:
Similar to the previous $only commit, this requires that all nodes
redfish nodes now capture App by reference. This is common, but does
interfere with some of our other patterns, and attempts to improve the
syntactic sugar for this proved unworkable.
This commit only adds the above to service root and Computer systems, in
hopes that we find a better syntax before this merges.
Left to future patches in series:
Merging the error json structures in responses.
The Redfish spec isn't very clear on how errors propagate for expanded
queries, and in a conforming we shouldn't ever hit them, but
nonetheless, I suspect the behavior we have is sub-optimal (attaching an
error node to every place in the tree that had an issue) and we should
attempt to do better in the future.
Tested (on previous patch):
curl --insecure --user root:0penBmc https://localhost:18080/redfish/v1\?\$expand\=.\(\$levels\=255\)
Returns the full tree
Setting $levels=1 query returns only a depth of 1 tree being returned.
Unit tests passing
Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I874aabfaa9df5dbf832a80ec62ae65369284791d
diff --git a/redfish-core/include/query.hpp b/redfish-core/include/query.hpp
index 44f07ba..b06278b 100644
--- a/redfish-core/include/query.hpp
+++ b/redfish-core/include/query.hpp
@@ -23,13 +23,19 @@
return false;
}
+ // If this isn't a get, no need to do anything with parameters
+ if (req.method() != boost::beast::http::verb::get)
+ {
+ return true;
+ }
+
std::function<void(crow::Response&)> handler =
res.releaseCompleteRequestHandler();
res.setCompleteRequestHandler(
[&app, handler(std::move(handler)),
query{*queryOpt}](crow::Response& res) mutable {
- processAllParams(app, query, res, handler);
+ processAllParams(app, query, handler, res);
});
return true;
}
diff --git a/redfish-core/include/utils/query_param.hpp b/redfish-core/include/utils/query_param.hpp
index d4ff81f..7aef2f0 100644
--- a/redfish-core/include/utils/query_param.hpp
+++ b/redfish-core/include/utils/query_param.hpp
@@ -5,8 +5,10 @@
#include "http_request.hpp"
#include "routing.hpp"
+#include <charconv>
#include <string>
#include <string_view>
+#include <utility>
#include <vector>
namespace redfish
@@ -14,11 +16,66 @@
namespace query_param
{
+enum class ExpandType : uint8_t
+{
+ None,
+ Links,
+ NotLinks,
+ Both,
+};
+
struct Query
{
bool isOnly = false;
+ uint8_t expandLevel = 1;
+ ExpandType expandType = ExpandType::None;
};
+inline bool getExpandType(std::string_view value, Query& query)
+{
+ if (value.empty())
+ {
+ return false;
+ }
+ switch (value[0])
+ {
+ case '*':
+ query.expandType = ExpandType::Both;
+ break;
+ case '.':
+ query.expandType = ExpandType::NotLinks;
+ break;
+ case '~':
+ query.expandType = ExpandType::Links;
+ break;
+ default:
+ return false;
+
+ break;
+ }
+ value.remove_prefix(1);
+ if (value.empty())
+ {
+ query.expandLevel = 1;
+ return true;
+ }
+ constexpr std::string_view levels = "($levels=";
+ if (!value.starts_with(levels))
+ {
+ return false;
+ }
+ value.remove_prefix(levels.size());
+
+ auto it = std::from_chars(value.data(), value.data() + value.size(),
+ query.expandLevel);
+ if (it.ec != std::errc())
+ {
+ return false;
+ }
+ value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
+ return value == ")";
+}
+
inline std::optional<Query>
parseParameters(const boost::urls::params_view& urlParams,
crow::Response& res)
@@ -37,7 +94,31 @@
}
ret.isOnly = true;
}
+ else if (key == "$expand")
+ {
+ if (!getExpandType(value, ret))
+ {
+ messages::queryParameterValueFormatError(res, value, key);
+ return std::nullopt;
+ }
+ }
+ else
+ {
+ // Intentionally ignore other errors Redfish spec, 7.3.1
+ if (key.starts_with("$"))
+ {
+ // Services shall return... The HTTP 501 Not Implemented
+ // status code for any unsupported query parameters that
+ // start with $ .
+ messages::queryParameterValueFormatError(res, value, key);
+ res.result(boost::beast::http::status::not_implemented);
+ return std::nullopt;
+ }
+ // "Shall ignore unknown or unsupported query parameters that do
+ // not begin with $ ."
+ }
}
+
return ret;
}
@@ -96,9 +177,180 @@
return true;
}
-void processAllParams(crow::App& app, Query query,
- crow::Response& intermediateResponse,
- std::function<void(crow::Response&)>& completionHandler)
+struct ExpandNode
+{
+ nlohmann::json::json_pointer location;
+ std::string uri;
+
+ inline bool operator==(const ExpandNode& other) const
+ {
+ return location == other.location && uri == other.uri;
+ }
+};
+
+// Walks a json object looking for Redfish NavigationReference entries that
+// might need resolved. It recursively walks the jsonResponse object, looking
+// for links at every level, and returns a list (out) of locations within the
+// tree that need to be expanded. The current json pointer location p is passed
+// in to reference the current node that's being expanded, so it can be combined
+// with the keys from the jsonResponse object
+inline void findNavigationReferencesRecursive(
+ ExpandType eType, nlohmann::json& jsonResponse,
+ const nlohmann::json::json_pointer& p, bool inLinks,
+ std::vector<ExpandNode>& out)
+{
+ // If no expand is needed, return early
+ if (eType == ExpandType::None)
+ {
+ return;
+ }
+ nlohmann::json::array_t* array =
+ jsonResponse.get_ptr<nlohmann::json::array_t*>();
+ if (array != nullptr)
+ {
+ size_t index = 0;
+ // For arrays, walk every element in the array
+ for (auto& element : *array)
+ {
+ nlohmann::json::json_pointer newPtr = p / index;
+ BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
+ findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
+ out);
+ index++;
+ }
+ }
+ nlohmann::json::object_t* obj =
+ jsonResponse.get_ptr<nlohmann::json::object_t*>();
+ if (obj == nullptr)
+ {
+ return;
+ }
+ // Navigation References only ever have a single element
+ if (obj->size() == 1)
+ {
+ if (obj->begin()->first == "@odata.id")
+ {
+ const std::string* uri =
+ obj->begin()->second.get_ptr<const std::string*>();
+ if (uri != nullptr)
+ {
+ BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
+ out.push_back({p, *uri});
+ }
+ }
+ }
+ // Loop the object and look for links
+ for (auto& element : *obj)
+ {
+ if (!inLinks)
+ {
+ // Check if this is a links node
+ inLinks = element.first == "Links";
+ }
+ // Only traverse the parts of the tree the user asked for
+ // Per section 7.3 of the redfish specification
+ if (inLinks && eType == ExpandType::NotLinks)
+ {
+ continue;
+ }
+ if (!inLinks && eType == ExpandType::Links)
+ {
+ continue;
+ }
+ nlohmann::json::json_pointer newPtr = p / element.first;
+ BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
+
+ findNavigationReferencesRecursive(eType, element.second, newPtr,
+ inLinks, out);
+ }
+}
+
+inline std::vector<ExpandNode>
+ findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse,
+ const nlohmann::json::json_pointer& root)
+{
+ std::vector<ExpandNode> ret;
+ findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
+ return ret;
+}
+
+class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
+{
+ public:
+ // This object takes a single asyncResp object as the "final" one, then
+ // allows callers to attach sub-responses within the json tree that need
+ // to be executed and filled into their appropriate locations. This
+ // class manages the final "merge" of the json resources.
+ MultiAsyncResp(crow::App& app,
+ std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
+ app(app),
+ finalRes(std::move(finalResIn))
+ {}
+
+ void addAwaitingResponse(
+ Query query, std::shared_ptr<bmcweb::AsyncResp>& res,
+ const nlohmann::json::json_pointer& finalExpandLocation)
+ {
+ res->res.setCompleteRequestHandler(std::bind_front(
+ onEndStatic, shared_from_this(), query, finalExpandLocation));
+ }
+
+ void onEnd(Query query, const nlohmann::json::json_pointer& locationToPlace,
+ crow::Response& res)
+ {
+ nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
+ finalObj = std::move(res.jsonValue);
+
+ if (query.expandLevel <= 0)
+ {
+ // Last level to expand, no need to go deeper
+ return;
+ }
+ // Now decrease the depth by one to account for the tree node we
+ // just resolved
+ query.expandLevel--;
+
+ std::vector<ExpandNode> nodes = findNavigationReferences(
+ query.expandType, finalObj, locationToPlace);
+ BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
+ for (const ExpandNode& node : nodes)
+ {
+ BMCWEB_LOG_DEBUG << "Expanding " << locationToPlace;
+ std::error_code ec;
+ crow::Request newReq({boost::beast::http::verb::get, node.uri, 11},
+ ec);
+ if (ec)
+ {
+ messages::internalError(res);
+ return;
+ }
+
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+ BMCWEB_LOG_DEBUG << "setting completion handler on "
+ << &asyncResp->res;
+ addAwaitingResponse(query, asyncResp, node.location);
+ app.handle(newReq, asyncResp);
+ }
+ }
+
+ private:
+ static void onEndStatic(const std::shared_ptr<MultiAsyncResp>& multi,
+ Query query,
+ const nlohmann::json::json_pointer& locationToPlace,
+ crow::Response& res)
+ {
+ multi->onEnd(query, locationToPlace, res);
+ }
+
+ crow::App& app;
+ std::shared_ptr<bmcweb::AsyncResp> finalRes;
+};
+
+inline void
+ processAllParams(crow::App& app, const Query query,
+
+ std::function<void(crow::Response&)>& completionHandler,
+ crow::Response& intermediateResponse)
{
if (!completionHandler)
{
@@ -120,6 +372,21 @@
processOnly(app, intermediateResponse, completionHandler);
return;
}
+ if (query.expandType != ExpandType::None)
+ {
+ BMCWEB_LOG_DEBUG << "Executing expand query";
+ // TODO(ed) this is a copy of the response object. Admittedly,
+ // we're inherently doing something inefficient, but we shouldn't
+ // have to do a full copy
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+ asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
+ asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
+ auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
+
+ // Start the chain by "ending" the root response
+ multi->onEnd(query, nlohmann::json::json_pointer(""), asyncResp->res);
+ return;
+ }
completionHandler(intermediateResponse);
}
diff --git a/redfish-core/include/utils/query_param_test.cpp b/redfish-core/include/utils/query_param_test.cpp
new file mode 100644
index 0000000..69ed673
--- /dev/null
+++ b/redfish-core/include/utils/query_param_test.cpp
@@ -0,0 +1,190 @@
+#include "query_param.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <iostream>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+TEST(QueryParams, ParseParametersOnly)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?only");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ using redfish::query_param::parseParameters;
+ using redfish::query_param::Query;
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+ EXPECT_TRUE(query->isOnly);
+}
+
+TEST(QueryParams, ParseParametersExpand)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$expand=*");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ using redfish::query_param::parseParameters;
+ using redfish::query_param::Query;
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+ EXPECT_TRUE(query->expandType == redfish::query_param::ExpandType::Both);
+}
+
+TEST(QueryParams, ParseParametersUnexpectedGetsIgnored)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?unexpected_param");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ using redfish::query_param::parseParameters;
+ using redfish::query_param::Query;
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+}
+
+TEST(QueryParams, ParseParametersUnexpectedDollarGetsError)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$unexpected_param");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ using redfish::query_param::parseParameters;
+ using redfish::query_param::Query;
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query == std::nullopt);
+ EXPECT_EQ(res.result(), boost::beast::http::status::not_implemented);
+}
+
+TEST(QueryParams, GetExpandType)
+{
+ redfish::query_param::Query query{};
+
+ EXPECT_FALSE(getExpandType("", query));
+ EXPECT_FALSE(getExpandType(".(", query));
+ EXPECT_FALSE(getExpandType(".()", query));
+ EXPECT_FALSE(getExpandType(".($levels=1", query));
+
+ EXPECT_TRUE(getExpandType("*", query));
+ EXPECT_EQ(query.expandType, redfish::query_param::ExpandType::Both);
+ EXPECT_TRUE(getExpandType(".", query));
+ EXPECT_EQ(query.expandType, redfish::query_param::ExpandType::NotLinks);
+ EXPECT_TRUE(getExpandType("~", query));
+ EXPECT_EQ(query.expandType, redfish::query_param::ExpandType::Links);
+
+ // Per redfish specification, level defaults to 1
+ EXPECT_TRUE(getExpandType(".", query));
+ EXPECT_EQ(query.expandLevel, 1);
+
+ EXPECT_TRUE(getExpandType(".($levels=42)", query));
+ EXPECT_EQ(query.expandLevel, 42);
+
+ // Overflow
+ EXPECT_FALSE(getExpandType(".($levels=256)", query));
+
+ // Negative
+ EXPECT_FALSE(getExpandType(".($levels=-1)", query));
+
+ // No number
+ EXPECT_FALSE(getExpandType(".($levels=a)", query));
+}
+
+namespace redfish::query_param
+{
+// NOLINTNEXTLINE(readability-identifier-naming)
+void PrintTo(const ExpandNode& value, ::std::ostream* os)
+{
+ *os << "ExpandNode: " << value.location << " " << value.uri;
+}
+}; // namespace redfish::query_param
+
+TEST(QueryParams, FindNavigationReferencesNonLink)
+{
+ using nlohmann::json;
+ using redfish::query_param::ExpandType;
+ using redfish::query_param::findNavigationReferences;
+ using ::testing::UnorderedElementsAre;
+ json singleTreeNode = R"({"Foo" : {"@odata.id": "/foobar"}})"_json;
+
+ // Parsing as the root should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleTreeNode,
+ json::json_pointer("")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/Foo"), "/foobar"}));
+ // Parsing at a depth should net one entry at depth
+ EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleTreeNode,
+ json::json_pointer("/baz")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/baz/Foo"), "/foobar"}));
+
+ // Parsing in Non-hyperlinks mode should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, singleTreeNode,
+ json::json_pointer("")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/Foo"), "/foobar"}));
+
+ // Parsing non-hyperlinks at depth should net one entry at depth
+ EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, singleTreeNode,
+ json::json_pointer("/baz")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/baz/Foo"), "/foobar"}));
+
+ // Searching for not types should return empty set
+ EXPECT_TRUE(findNavigationReferences(ExpandType::None, singleTreeNode,
+ json::json_pointer(""))
+ .empty());
+
+ // Searching for hyperlinks only should return empty set
+ EXPECT_TRUE(findNavigationReferences(ExpandType::Links, singleTreeNode,
+ json::json_pointer(""))
+ .empty());
+}
+
+TEST(QueryParams, FindNavigationReferencesLink)
+{
+ using nlohmann::json;
+ using redfish::query_param::ExpandType;
+ using redfish::query_param::findNavigationReferences;
+ using ::testing::UnorderedElementsAre;
+ json singleLinkNode =
+ R"({"Links" : {"Sessions": {"@odata.id": "/foobar"}}})"_json;
+
+ // Parsing as the root should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleLinkNode,
+ json::json_pointer("")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/Links/Sessions"), "/foobar"}));
+ // Parsing at a depth should net one entry at depth
+ EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleLinkNode,
+ json::json_pointer("/baz")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/baz/Links/Sessions"), "/foobar"}));
+
+ // Parsing in hyperlinks mode should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::Links, singleLinkNode,
+ json::json_pointer("")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/Links/Sessions"), "/foobar"}));
+
+ // Parsing hyperlinks at depth should net one entry at depth
+ EXPECT_THAT(findNavigationReferences(ExpandType::Links, singleLinkNode,
+ json::json_pointer("/baz")),
+ UnorderedElementsAre(redfish::query_param::ExpandNode{
+ json::json_pointer("/baz/Links/Sessions"), "/foobar"}));
+
+ // Searching for not types should return empty set
+ EXPECT_TRUE(findNavigationReferences(ExpandType::None, singleLinkNode,
+ json::json_pointer(""))
+ .empty());
+
+ // Searching for non-hyperlinks only should return empty set
+ EXPECT_TRUE(findNavigationReferences(ExpandType::NotLinks, singleLinkNode,
+ json::json_pointer(""))
+ .empty());
+}
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index 696d6fd..d7214ee 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -22,6 +22,7 @@
#include <http_request.hpp>
#include <nlohmann/json.hpp>
#include <persistent_data.hpp>
+#include <query.hpp>
#include <registries/privilege_registry.hpp>
#include <utils/systemd_utils.hpp>
@@ -29,10 +30,8 @@
{
inline void
- handleServiceRootGet(const crow::Request& /*req*/,
- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+ handleServiceRootGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
-
std::string uuid = persistent_data::getConfig().systemUuid;
asyncResp->res.jsonValue["@odata.type"] =
"#ServiceRoot.v1_11_0.ServiceRoot";
@@ -73,10 +72,18 @@
nlohmann::json& protocolFeatures =
asyncResp->res.jsonValue["ProtocolFeaturesSupported"];
protocolFeatures["ExcerptQuery"] = false;
- protocolFeatures["ExpandQuery"]["ExpandAll"] = false;
- protocolFeatures["ExpandQuery"]["Levels"] = false;
- protocolFeatures["ExpandQuery"]["Links"] = false;
- protocolFeatures["ExpandQuery"]["NoLinks"] = false;
+
+ protocolFeatures["ExpandQuery"]["ExpandAll"] =
+ bmcwebInsecureEnableQueryParams;
+ // This is the maximum level defined in ServiceRoot.v1_13_0.json
+ if (bmcwebInsecureEnableQueryParams)
+ {
+ protocolFeatures["ExpandQuery"]["MaxLevels"] = 6;
+ }
+ protocolFeatures["ExpandQuery"]["Levels"] = bmcwebInsecureEnableQueryParams;
+ protocolFeatures["ExpandQuery"]["Links"] = bmcwebInsecureEnableQueryParams;
+ protocolFeatures["ExpandQuery"]["NoLinks"] =
+ bmcwebInsecureEnableQueryParams;
protocolFeatures["FilterQuery"] = false;
protocolFeatures["OnlyMemberQuery"] = bmcwebInsecureEnableQueryParams;
protocolFeatures["SelectQuery"] = false;
@@ -88,7 +95,15 @@
{
BMCWEB_ROUTE(app, "/redfish/v1/")
.privileges(redfish::privileges::getServiceRoot)
- .methods(boost::beast::http::verb::get)(handleServiceRootGet);
+ .methods(boost::beast::http::verb::get)(
+ [&app](const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
+ {
+ return;
+ }
+ handleServiceRootGet(asyncResp);
+ });
}
} // namespace redfish
diff --git a/redfish-core/lib/ut/service_root_test.cpp b/redfish-core/lib/ut/service_root_test.cpp
index 4c05751..1a7b249 100644
--- a/redfish-core/lib/ut/service_root_test.cpp
+++ b/redfish-core/lib/ut/service_root_test.cpp
@@ -1,3 +1,5 @@
+#include "bmcweb_config.h"
+
#include "http_request.hpp"
#include "include/async_resp.hpp"
#include "nlohmann/json.hpp"
@@ -67,11 +69,24 @@
EXPECT_EQ(json["ProtocolFeaturesSupported"].size(), 6);
EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExcerptQuery"]);
- EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExpandQuery"]["ExpandAll"]);
- EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Levels"]);
- EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Links"]);
- EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExpandQuery"]["NoLinks"]);
- EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 4);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["ExpandAll"],
+ bmcwebInsecureEnableQueryParams);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Levels"],
+ bmcwebInsecureEnableQueryParams);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Links"],
+ bmcwebInsecureEnableQueryParams);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["NoLinks"],
+ bmcwebInsecureEnableQueryParams);
+ if (bmcwebInsecureEnableQueryParams)
+ {
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 5);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["MaxLevels"],
+ 6);
+ }
+ else
+ {
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 4);
+ }
EXPECT_FALSE(json["ProtocolFeaturesSupported"]["FilterQuery"]);
EXPECT_EQ(json["ProtocolFeaturesSupported"]["OnlyMemberQuery"],
bmcwebInsecureEnableQueryParams);
@@ -87,10 +102,9 @@
TEST(ServiceRootTest, ServiceRootConstructor)
{
std::error_code ec;
- crow::Request req({}, ec);
auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
shareAsyncResp->res.setCompleteRequestHandler(assertServiceRootGet);
- redfish::handleServiceRootGet(req, shareAsyncResp);
+ redfish::handleServiceRootGet(shareAsyncResp);
}
diff --git a/redfish-core/src/query_param.cpp b/redfish-core/src/query_param.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/redfish-core/src/query_param.cpp