xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 3648c8be1c03c8ebe099185bc651613862fc0027)
1f4c99e70SEd Tanous #pragma once
2d5c80ad9SNan Zhou #include "bmcweb_config.h"
3d5c80ad9SNan Zhou 
4f4c99e70SEd Tanous #include "app.hpp"
5f4c99e70SEd Tanous #include "async_resp.hpp"
6f4c99e70SEd Tanous #include "error_messages.hpp"
7f4c99e70SEd Tanous #include "http_request.hpp"
802cad96eSEd Tanous #include "http_response.hpp"
9d5c80ad9SNan Zhou #include "logging.hpp"
10f4c99e70SEd Tanous 
11d5c80ad9SNan Zhou #include <sys/types.h>
12d5c80ad9SNan Zhou 
13d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep
14d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp>
15d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp>
16d5c80ad9SNan Zhou #include <boost/url/params_view.hpp>
17d5c80ad9SNan Zhou #include <boost/url/string.hpp>
18d5c80ad9SNan Zhou #include <nlohmann/json.hpp>
19d5c80ad9SNan Zhou 
20d5c80ad9SNan Zhou #include <algorithm>
217cf436c9SEd Tanous #include <charconv>
22d5c80ad9SNan Zhou #include <cstdint>
23d5c80ad9SNan Zhou #include <functional>
24d5c80ad9SNan Zhou #include <limits>
25d5c80ad9SNan Zhou #include <map>
26d5c80ad9SNan Zhou #include <memory>
27d5c80ad9SNan Zhou #include <optional>
28f4c99e70SEd Tanous #include <string>
29f4c99e70SEd Tanous #include <string_view>
30d5c80ad9SNan Zhou #include <system_error>
317cf436c9SEd Tanous #include <utility>
32f4c99e70SEd Tanous #include <vector>
33f4c99e70SEd Tanous 
34d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
35d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp>
36d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
37d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h>
38d5c80ad9SNan Zhou 
39f4c99e70SEd Tanous namespace redfish
40f4c99e70SEd Tanous {
41f4c99e70SEd Tanous namespace query_param
42f4c99e70SEd Tanous {
43ce8ea743SJiaqing Zhao inline constexpr size_t maxEntriesPerPage = 1000;
44f4c99e70SEd Tanous 
457cf436c9SEd Tanous enum class ExpandType : uint8_t
467cf436c9SEd Tanous {
477cf436c9SEd Tanous     None,
487cf436c9SEd Tanous     Links,
497cf436c9SEd Tanous     NotLinks,
507cf436c9SEd Tanous     Both,
517cf436c9SEd Tanous };
527cf436c9SEd Tanous 
53a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
54f4c99e70SEd Tanous struct Query
55f4c99e70SEd Tanous {
56a6b9125fSNan Zhou     // Only
57f4c99e70SEd Tanous     bool isOnly = false;
58a6b9125fSNan Zhou     // Expand
59a6b9125fSNan Zhou     uint8_t expandLevel = 0;
607cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
61c937d2bfSEd Tanous 
62c937d2bfSEd Tanous     // Skip
63*3648c8beSEd Tanous     std::optional<size_t> skip = std::nullopt;
64c937d2bfSEd Tanous 
65c937d2bfSEd Tanous     // Top
66*3648c8beSEd Tanous     std::optional<size_t> top = std::nullopt;
67f4c99e70SEd Tanous };
68f4c99e70SEd Tanous 
69a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
70a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
71a6b9125fSNan Zhou // the processing.
72a6b9125fSNan Zhou struct QueryCapabilities
73a6b9125fSNan Zhou {
74a6b9125fSNan Zhou     bool canDelegateOnly = false;
75c937d2bfSEd Tanous     bool canDelegateTop = false;
76c937d2bfSEd Tanous     bool canDelegateSkip = false;
77a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
78a6b9125fSNan Zhou };
79a6b9125fSNan Zhou 
80a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
81a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
82a6b9125fSNan Zhou // function will take care of it.
83a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
84a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
85a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
86a6b9125fSNan Zhou {
87a6b9125fSNan Zhou     Query delegated;
88a6b9125fSNan Zhou     // delegate only
89a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
90a6b9125fSNan Zhou     {
91a6b9125fSNan Zhou         delegated.isOnly = true;
92a6b9125fSNan Zhou         query.isOnly = false;
93a6b9125fSNan Zhou     }
94a6b9125fSNan Zhou     // delegate expand as much as we can
95a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
96a6b9125fSNan Zhou     {
97a6b9125fSNan Zhou         delegated.expandType = query.expandType;
98a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
99a6b9125fSNan Zhou         {
100a6b9125fSNan Zhou             query.expandType = ExpandType::None;
101a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
102a6b9125fSNan Zhou             query.expandLevel = 0;
103a6b9125fSNan Zhou         }
104a6b9125fSNan Zhou         else
105a6b9125fSNan Zhou         {
106a6b9125fSNan Zhou             query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
107a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
108a6b9125fSNan Zhou         }
109a6b9125fSNan Zhou     }
110c937d2bfSEd Tanous 
111c937d2bfSEd Tanous     // delegate top
112*3648c8beSEd Tanous     if (query.top && queryCapabilities.canDelegateTop)
113c937d2bfSEd Tanous     {
114c937d2bfSEd Tanous         delegated.top = query.top;
115*3648c8beSEd Tanous         query.top = std::nullopt;
116c937d2bfSEd Tanous     }
117c937d2bfSEd Tanous 
118c937d2bfSEd Tanous     // delegate skip
119*3648c8beSEd Tanous     if (query.skip && queryCapabilities.canDelegateSkip)
120c937d2bfSEd Tanous     {
121c937d2bfSEd Tanous         delegated.skip = query.skip;
122c937d2bfSEd Tanous         query.skip = 0;
123c937d2bfSEd Tanous     }
124a6b9125fSNan Zhou     return delegated;
125a6b9125fSNan Zhou }
126a6b9125fSNan Zhou 
1277cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
1287cf436c9SEd Tanous {
1297cf436c9SEd Tanous     if (value.empty())
1307cf436c9SEd Tanous     {
1317cf436c9SEd Tanous         return false;
1327cf436c9SEd Tanous     }
1337cf436c9SEd Tanous     switch (value[0])
1347cf436c9SEd Tanous     {
1357cf436c9SEd Tanous         case '*':
1367cf436c9SEd Tanous             query.expandType = ExpandType::Both;
1377cf436c9SEd Tanous             break;
1387cf436c9SEd Tanous         case '.':
1397cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
1407cf436c9SEd Tanous             break;
1417cf436c9SEd Tanous         case '~':
1427cf436c9SEd Tanous             query.expandType = ExpandType::Links;
1437cf436c9SEd Tanous             break;
1447cf436c9SEd Tanous         default:
1457cf436c9SEd Tanous             return false;
1467cf436c9SEd Tanous 
1477cf436c9SEd Tanous             break;
1487cf436c9SEd Tanous     }
1497cf436c9SEd Tanous     value.remove_prefix(1);
1507cf436c9SEd Tanous     if (value.empty())
1517cf436c9SEd Tanous     {
1527cf436c9SEd Tanous         query.expandLevel = 1;
1537cf436c9SEd Tanous         return true;
1547cf436c9SEd Tanous     }
1557cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
1567cf436c9SEd Tanous     if (!value.starts_with(levels))
1577cf436c9SEd Tanous     {
1587cf436c9SEd Tanous         return false;
1597cf436c9SEd Tanous     }
1607cf436c9SEd Tanous     value.remove_prefix(levels.size());
1617cf436c9SEd Tanous 
1627cf436c9SEd Tanous     auto it = std::from_chars(value.data(), value.data() + value.size(),
1637cf436c9SEd Tanous                               query.expandLevel);
1647cf436c9SEd Tanous     if (it.ec != std::errc())
1657cf436c9SEd Tanous     {
1667cf436c9SEd Tanous         return false;
1677cf436c9SEd Tanous     }
1687cf436c9SEd Tanous     value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
1697cf436c9SEd Tanous     return value == ")";
1707cf436c9SEd Tanous }
1717cf436c9SEd Tanous 
172c937d2bfSEd Tanous enum class QueryError
173c937d2bfSEd Tanous {
174c937d2bfSEd Tanous     Ok,
175c937d2bfSEd Tanous     OutOfRange,
176c937d2bfSEd Tanous     ValueFormat,
177c937d2bfSEd Tanous };
178c937d2bfSEd Tanous 
179c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
180c937d2bfSEd Tanous {
181c937d2bfSEd Tanous     std::from_chars_result r =
182c937d2bfSEd Tanous         std::from_chars(value.data(), value.data() + value.size(), param);
183c937d2bfSEd Tanous 
184c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
185c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
186c937d2bfSEd Tanous     {
187c937d2bfSEd Tanous         return QueryError::OutOfRange;
188c937d2bfSEd Tanous     }
189c937d2bfSEd Tanous     // All other errors are value format
190c937d2bfSEd Tanous     if (r.ec != std::errc())
191c937d2bfSEd Tanous     {
192c937d2bfSEd Tanous         return QueryError::ValueFormat;
193c937d2bfSEd Tanous     }
194c937d2bfSEd Tanous     return QueryError::Ok;
195c937d2bfSEd Tanous }
196c937d2bfSEd Tanous 
197c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
198c937d2bfSEd Tanous {
199*3648c8beSEd Tanous     return getNumericParam(value, query.skip.emplace());
200c937d2bfSEd Tanous }
201c937d2bfSEd Tanous 
202c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
203c937d2bfSEd Tanous {
204*3648c8beSEd Tanous     QueryError ret = getNumericParam(value, query.top.emplace());
205c937d2bfSEd Tanous     if (ret != QueryError::Ok)
206c937d2bfSEd Tanous     {
207c937d2bfSEd Tanous         return ret;
208c937d2bfSEd Tanous     }
209c937d2bfSEd Tanous 
210c937d2bfSEd Tanous     // Range check for sanity.
211c937d2bfSEd Tanous     if (query.top > maxEntriesPerPage)
212c937d2bfSEd Tanous     {
213c937d2bfSEd Tanous         return QueryError::OutOfRange;
214c937d2bfSEd Tanous     }
215c937d2bfSEd Tanous 
216c937d2bfSEd Tanous     return QueryError::Ok;
217c937d2bfSEd Tanous }
218c937d2bfSEd Tanous 
219f4c99e70SEd Tanous inline std::optional<Query>
220f4c99e70SEd Tanous     parseParameters(const boost::urls::params_view& urlParams,
221f4c99e70SEd Tanous                     crow::Response& res)
222f4c99e70SEd Tanous {
223f4c99e70SEd Tanous     Query ret;
224f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
225f4c99e70SEd Tanous     {
226f4c99e70SEd Tanous         std::string_view key(it.key.data(), it.key.size());
227f4c99e70SEd Tanous         std::string_view value(it.value.data(), it.value.size());
228f4c99e70SEd Tanous         if (key == "only")
229f4c99e70SEd Tanous         {
230f4c99e70SEd Tanous             if (!it.value.empty())
231f4c99e70SEd Tanous             {
232f4c99e70SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
233f4c99e70SEd Tanous                 return std::nullopt;
234f4c99e70SEd Tanous             }
235f4c99e70SEd Tanous             ret.isOnly = true;
236f4c99e70SEd Tanous         }
2375e52870bSEd Tanous         else if (key == "$expand" && bmcwebInsecureEnableQueryParams)
2387cf436c9SEd Tanous         {
2397cf436c9SEd Tanous             if (!getExpandType(value, ret))
2407cf436c9SEd Tanous             {
2417cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
2427cf436c9SEd Tanous                 return std::nullopt;
243f4c99e70SEd Tanous             }
2447cf436c9SEd Tanous         }
245c937d2bfSEd Tanous         else if (key == "$top")
246c937d2bfSEd Tanous         {
247c937d2bfSEd Tanous             QueryError topRet = getTopParam(value, ret);
248c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
249c937d2bfSEd Tanous             {
250c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
251c937d2bfSEd Tanous                 return std::nullopt;
252c937d2bfSEd Tanous             }
253c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
254c937d2bfSEd Tanous             {
255c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
256c937d2bfSEd Tanous                     res, value, "$top",
257a926c53eSJiaqing Zhao                     "0-" + std::to_string(maxEntriesPerPage));
258c937d2bfSEd Tanous                 return std::nullopt;
259c937d2bfSEd Tanous             }
260c937d2bfSEd Tanous         }
261c937d2bfSEd Tanous         else if (key == "$skip")
262c937d2bfSEd Tanous         {
263c937d2bfSEd Tanous             QueryError topRet = getSkipParam(value, ret);
264c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
265c937d2bfSEd Tanous             {
266c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
267c937d2bfSEd Tanous                 return std::nullopt;
268c937d2bfSEd Tanous             }
269c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
270c937d2bfSEd Tanous             {
271c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
272c937d2bfSEd Tanous                     res, value, key,
273a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
274c937d2bfSEd Tanous                 return std::nullopt;
275c937d2bfSEd Tanous             }
276c937d2bfSEd Tanous         }
2777cf436c9SEd Tanous         else
2787cf436c9SEd Tanous         {
2797cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
2807cf436c9SEd Tanous             if (key.starts_with("$"))
2817cf436c9SEd Tanous             {
2827cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
2837cf436c9SEd Tanous                 // status code for any unsupported query parameters that
2847cf436c9SEd Tanous                 // start with $ .
2857cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
2867cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
2877cf436c9SEd Tanous                 return std::nullopt;
2887cf436c9SEd Tanous             }
2897cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
2907cf436c9SEd Tanous             // not begin with $ ."
2917cf436c9SEd Tanous         }
2927cf436c9SEd Tanous     }
2937cf436c9SEd Tanous 
294f4c99e70SEd Tanous     return ret;
295f4c99e70SEd Tanous }
296f4c99e70SEd Tanous 
297f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
298f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
299f4c99e70SEd Tanous {
300f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing only query param";
301f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
302f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
303f4c99e70SEd Tanous     {
304f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
305f4c99e70SEd Tanous         completionHandler(res);
306f4c99e70SEd Tanous         return false;
307f4c99e70SEd Tanous     }
308f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
309f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
310f4c99e70SEd Tanous     {
311f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
312f4c99e70SEd Tanous                          << " element, returning full collection.";
313f4c99e70SEd Tanous         completionHandler(res);
314f4c99e70SEd Tanous         return false;
315f4c99e70SEd Tanous     }
316f4c99e70SEd Tanous 
317f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
318f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
319f4c99e70SEd Tanous     {
320f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "No found odata.id";
321f4c99e70SEd Tanous         messages::internalError(res);
322f4c99e70SEd Tanous         completionHandler(res);
323f4c99e70SEd Tanous         return false;
324f4c99e70SEd Tanous     }
325f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
326f4c99e70SEd Tanous     if (url == nullptr)
327f4c99e70SEd Tanous     {
328f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
329f4c99e70SEd Tanous         messages::internalError(res);
330f4c99e70SEd Tanous         completionHandler(res);
331f4c99e70SEd Tanous         return false;
332f4c99e70SEd Tanous     }
333f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
334f4c99e70SEd Tanous     // newReq.session = req.session;
335f4c99e70SEd Tanous     std::error_code ec;
336f4c99e70SEd Tanous     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
337f4c99e70SEd Tanous     if (ec)
338f4c99e70SEd Tanous     {
339f4c99e70SEd Tanous         messages::internalError(res);
340f4c99e70SEd Tanous         completionHandler(res);
341f4c99e70SEd Tanous         return false;
342f4c99e70SEd Tanous     }
343f4c99e70SEd Tanous 
344f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
345f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
346f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
347f4c99e70SEd Tanous     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
348f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
349f4c99e70SEd Tanous     return true;
350f4c99e70SEd Tanous }
351f4c99e70SEd Tanous 
3527cf436c9SEd Tanous struct ExpandNode
3537cf436c9SEd Tanous {
3547cf436c9SEd Tanous     nlohmann::json::json_pointer location;
3557cf436c9SEd Tanous     std::string uri;
3567cf436c9SEd Tanous 
3577cf436c9SEd Tanous     inline bool operator==(const ExpandNode& other) const
3587cf436c9SEd Tanous     {
3597cf436c9SEd Tanous         return location == other.location && uri == other.uri;
3607cf436c9SEd Tanous     }
3617cf436c9SEd Tanous };
3627cf436c9SEd Tanous 
3637cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
3647cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
3657cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
3667cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
3677cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
3687cf436c9SEd Tanous // with the keys from the jsonResponse object
3697cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
3707cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
3717cf436c9SEd Tanous     const nlohmann::json::json_pointer& p, bool inLinks,
3727cf436c9SEd Tanous     std::vector<ExpandNode>& out)
3737cf436c9SEd Tanous {
3747cf436c9SEd Tanous     // If no expand is needed, return early
3757cf436c9SEd Tanous     if (eType == ExpandType::None)
3767cf436c9SEd Tanous     {
3777cf436c9SEd Tanous         return;
3787cf436c9SEd Tanous     }
3797cf436c9SEd Tanous     nlohmann::json::array_t* array =
3807cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
3817cf436c9SEd Tanous     if (array != nullptr)
3827cf436c9SEd Tanous     {
3837cf436c9SEd Tanous         size_t index = 0;
3847cf436c9SEd Tanous         // For arrays, walk every element in the array
3857cf436c9SEd Tanous         for (auto& element : *array)
3867cf436c9SEd Tanous         {
3877cf436c9SEd Tanous             nlohmann::json::json_pointer newPtr = p / index;
3887cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
3897cf436c9SEd Tanous             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
3907cf436c9SEd Tanous                                               out);
3917cf436c9SEd Tanous             index++;
3927cf436c9SEd Tanous         }
3937cf436c9SEd Tanous     }
3947cf436c9SEd Tanous     nlohmann::json::object_t* obj =
3957cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
3967cf436c9SEd Tanous     if (obj == nullptr)
3977cf436c9SEd Tanous     {
3987cf436c9SEd Tanous         return;
3997cf436c9SEd Tanous     }
4007cf436c9SEd Tanous     // Navigation References only ever have a single element
4017cf436c9SEd Tanous     if (obj->size() == 1)
4027cf436c9SEd Tanous     {
4037cf436c9SEd Tanous         if (obj->begin()->first == "@odata.id")
4047cf436c9SEd Tanous         {
4057cf436c9SEd Tanous             const std::string* uri =
4067cf436c9SEd Tanous                 obj->begin()->second.get_ptr<const std::string*>();
4077cf436c9SEd Tanous             if (uri != nullptr)
4087cf436c9SEd Tanous             {
4097cf436c9SEd Tanous                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
4107cf436c9SEd Tanous                 out.push_back({p, *uri});
4117cf436c9SEd Tanous             }
4127cf436c9SEd Tanous         }
4137cf436c9SEd Tanous     }
4147cf436c9SEd Tanous     // Loop the object and look for links
4157cf436c9SEd Tanous     for (auto& element : *obj)
4167cf436c9SEd Tanous     {
417e479ad58SNan Zhou         bool localInLinks = inLinks;
418e479ad58SNan Zhou         if (!localInLinks)
4197cf436c9SEd Tanous         {
4207cf436c9SEd Tanous             // Check if this is a links node
421e479ad58SNan Zhou             localInLinks = element.first == "Links";
4227cf436c9SEd Tanous         }
4237cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
4247cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
425e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
4267cf436c9SEd Tanous         {
4277cf436c9SEd Tanous             continue;
4287cf436c9SEd Tanous         }
429e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
4307cf436c9SEd Tanous         {
4317cf436c9SEd Tanous             continue;
4327cf436c9SEd Tanous         }
4337cf436c9SEd Tanous         nlohmann::json::json_pointer newPtr = p / element.first;
4347cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
4357cf436c9SEd Tanous 
4367cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
437e479ad58SNan Zhou                                           localInLinks, out);
4387cf436c9SEd Tanous     }
4397cf436c9SEd Tanous }
4407cf436c9SEd Tanous 
4417cf436c9SEd Tanous inline std::vector<ExpandNode>
44272c3ae33SNan Zhou     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
4437cf436c9SEd Tanous {
4447cf436c9SEd Tanous     std::vector<ExpandNode> ret;
44572c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
4467cf436c9SEd Tanous     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
4477cf436c9SEd Tanous     return ret;
4487cf436c9SEd Tanous }
4497cf436c9SEd Tanous 
45072c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
451b66cf2a2SNan Zhou // Returns std::nullopt on failures.
45272c3ae33SNan Zhou // This function shall handle $select when it is added.
45372c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand,
45472c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
455b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
45672c3ae33SNan Zhou {
45772c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
45872c3ae33SNan Zhou     if (query.expandLevel <= 1)
45972c3ae33SNan Zhou     {
460b66cf2a2SNan Zhou         return "";
46172c3ae33SNan Zhou     }
46272c3ae33SNan Zhou     std::string str = "?$expand=";
463b66cf2a2SNan Zhou     bool queryTypeExpected = false;
46472c3ae33SNan Zhou     switch (query.expandType)
46572c3ae33SNan Zhou     {
46672c3ae33SNan Zhou         case ExpandType::None:
467b66cf2a2SNan Zhou             return "";
46872c3ae33SNan Zhou         case ExpandType::Links:
469b66cf2a2SNan Zhou             queryTypeExpected = true;
47072c3ae33SNan Zhou             str += '~';
47172c3ae33SNan Zhou             break;
47272c3ae33SNan Zhou         case ExpandType::NotLinks:
473b66cf2a2SNan Zhou             queryTypeExpected = true;
47472c3ae33SNan Zhou             str += '.';
47572c3ae33SNan Zhou             break;
47672c3ae33SNan Zhou         case ExpandType::Both:
477b66cf2a2SNan Zhou             queryTypeExpected = true;
47872c3ae33SNan Zhou             str += '*';
47972c3ae33SNan Zhou             break;
480b66cf2a2SNan Zhou     }
481b66cf2a2SNan Zhou     if (!queryTypeExpected)
482b66cf2a2SNan Zhou     {
483b66cf2a2SNan Zhou         return std::nullopt;
48472c3ae33SNan Zhou     }
48572c3ae33SNan Zhou     str += "($levels=";
48672c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
48772c3ae33SNan Zhou     str += ')';
48872c3ae33SNan Zhou     return str;
48972c3ae33SNan Zhou }
49072c3ae33SNan Zhou 
4917cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
4927cf436c9SEd Tanous {
4937cf436c9SEd Tanous   public:
4947cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
4957cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
4967cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
4977cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
4988a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
4997cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
5008a592810SEd Tanous         app(appIn),
5017cf436c9SEd Tanous         finalRes(std::move(finalResIn))
5027cf436c9SEd Tanous     {}
5037cf436c9SEd Tanous 
5047cf436c9SEd Tanous     void addAwaitingResponse(
50502cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
5067cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
5077cf436c9SEd Tanous     {
5087cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
50972c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
5107cf436c9SEd Tanous     }
5117cf436c9SEd Tanous 
51272c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
5137cf436c9SEd Tanous                      crow::Response& res)
5147cf436c9SEd Tanous     {
5157cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
5167cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
5177cf436c9SEd Tanous     }
5187cf436c9SEd Tanous 
51972c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
52072c3ae33SNan Zhou     // for deeper levels.
52172c3ae33SNan Zhou     void startQuery(const Query& query)
52272c3ae33SNan Zhou     {
52372c3ae33SNan Zhou         std::vector<ExpandNode> nodes =
52472c3ae33SNan Zhou             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
5257cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
526b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
527b66cf2a2SNan Zhou         if (!queryStr)
528b66cf2a2SNan Zhou         {
529b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
530b66cf2a2SNan Zhou             return;
531b66cf2a2SNan Zhou         }
5327cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
5337cf436c9SEd Tanous         {
534b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
53572c3ae33SNan Zhou             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
5367cf436c9SEd Tanous             std::error_code ec;
53772c3ae33SNan Zhou             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
5387cf436c9SEd Tanous                                  ec);
5397cf436c9SEd Tanous             if (ec)
5407cf436c9SEd Tanous             {
54172c3ae33SNan Zhou                 messages::internalError(finalRes->res);
5427cf436c9SEd Tanous                 return;
5437cf436c9SEd Tanous             }
5447cf436c9SEd Tanous 
5457cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
5467cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
5477cf436c9SEd Tanous                              << &asyncResp->res;
54872c3ae33SNan Zhou 
54972c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
5507cf436c9SEd Tanous             app.handle(newReq, asyncResp);
5517cf436c9SEd Tanous         }
5527cf436c9SEd Tanous     }
5537cf436c9SEd Tanous 
5547cf436c9SEd Tanous   private:
55572c3ae33SNan Zhou     static void
55672c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
5577cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
5587cf436c9SEd Tanous                           crow::Response& res)
5597cf436c9SEd Tanous     {
56072c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
5617cf436c9SEd Tanous     }
5627cf436c9SEd Tanous 
5637cf436c9SEd Tanous     crow::App& app;
5647cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
5657cf436c9SEd Tanous };
5667cf436c9SEd Tanous 
5672a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
5682a68dc80SEd Tanous {
569*3648c8beSEd Tanous     if (!query.skip && !query.top)
570*3648c8beSEd Tanous     {
571*3648c8beSEd Tanous         // No work to do.
572*3648c8beSEd Tanous         return;
573*3648c8beSEd Tanous     }
5742a68dc80SEd Tanous     nlohmann::json::object_t* obj =
5752a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
5762a68dc80SEd Tanous     if (obj == nullptr)
5772a68dc80SEd Tanous     {
5782a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
5792a68dc80SEd Tanous         messages::internalError(res);
5802a68dc80SEd Tanous         return;
5812a68dc80SEd Tanous     }
5822a68dc80SEd Tanous 
5832a68dc80SEd Tanous     BMCWEB_LOG_DEBUG << "Handling top/skip";
5842a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
5852a68dc80SEd Tanous     if (members == obj->end())
5862a68dc80SEd Tanous     {
5872a68dc80SEd Tanous         // From the Redfish specification 7.3.1
5882a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
5892a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
5902a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
5912a68dc80SEd Tanous         // collections but are used on singular resources.
5922a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
5932a68dc80SEd Tanous         return;
5942a68dc80SEd Tanous     }
5952a68dc80SEd Tanous 
5962a68dc80SEd Tanous     nlohmann::json::array_t* arr =
5972a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
5982a68dc80SEd Tanous     if (arr == nullptr)
5992a68dc80SEd Tanous     {
6002a68dc80SEd Tanous         messages::internalError(res);
6012a68dc80SEd Tanous         return;
6022a68dc80SEd Tanous     }
6032a68dc80SEd Tanous 
604*3648c8beSEd Tanous     if (query.skip)
605*3648c8beSEd Tanous     {
606*3648c8beSEd Tanous         // Per section 7.3.1 of the Redfish specification, $skip is run before
607*3648c8beSEd Tanous         // $top Can only skip as many values as we have
608*3648c8beSEd Tanous         size_t skip = std::min(arr->size(), *query.skip);
6092a68dc80SEd Tanous         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
610*3648c8beSEd Tanous     }
611*3648c8beSEd Tanous     if (query.top)
612*3648c8beSEd Tanous     {
613*3648c8beSEd Tanous         size_t top = std::min(arr->size(), *query.top);
6142a68dc80SEd Tanous         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
6152a68dc80SEd Tanous     }
616*3648c8beSEd Tanous }
6172a68dc80SEd Tanous 
6187cf436c9SEd Tanous inline void
619593f6449SNan Zhou     processAllParams(crow::App& app, const Query& query,
6207cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
6217cf436c9SEd Tanous                      crow::Response& intermediateResponse)
622f4c99e70SEd Tanous {
623f4c99e70SEd Tanous     if (!completionHandler)
624f4c99e70SEd Tanous     {
625f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
626f4c99e70SEd Tanous         return;
627f4c99e70SEd Tanous     }
628f4c99e70SEd Tanous 
629f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
630f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
631f4c99e70SEd Tanous     // params.
632f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
633f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
634f4c99e70SEd Tanous     {
635f4c99e70SEd Tanous         completionHandler(intermediateResponse);
636f4c99e70SEd Tanous         return;
637f4c99e70SEd Tanous     }
638f4c99e70SEd Tanous     if (query.isOnly)
639f4c99e70SEd Tanous     {
640f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
641f4c99e70SEd Tanous         return;
642f4c99e70SEd Tanous     }
6432a68dc80SEd Tanous 
644*3648c8beSEd Tanous     if (query.top || query.skip)
6452a68dc80SEd Tanous     {
6462a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
6472a68dc80SEd Tanous     }
6482a68dc80SEd Tanous 
6497cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
6507cf436c9SEd Tanous     {
6517cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
6527cf436c9SEd Tanous         // TODO(ed) this is a copy of the response object.  Admittedly,
6537cf436c9SEd Tanous         // we're inherently doing something inefficient, but we shouldn't
6547cf436c9SEd Tanous         // have to do a full copy
6557cf436c9SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
6567cf436c9SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
6577cf436c9SEd Tanous         asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
6587cf436c9SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
6597cf436c9SEd Tanous 
66072c3ae33SNan Zhou         multi->startQuery(query);
6617cf436c9SEd Tanous         return;
6627cf436c9SEd Tanous     }
663f4c99e70SEd Tanous     completionHandler(intermediateResponse);
664f4c99e70SEd Tanous }
665f4c99e70SEd Tanous 
666f4c99e70SEd Tanous } // namespace query_param
667f4c99e70SEd Tanous } // namespace redfish
668