xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision e479ad5885a300fe6af862da819b25a0c74c9d9f)
1f4c99e70SEd Tanous #pragma once
2f4c99e70SEd Tanous #include "app.hpp"
3f4c99e70SEd Tanous #include "async_resp.hpp"
4f4c99e70SEd Tanous #include "error_messages.hpp"
5f4c99e70SEd Tanous #include "http_request.hpp"
6f4c99e70SEd Tanous #include "routing.hpp"
7f4c99e70SEd Tanous 
87cf436c9SEd Tanous #include <charconv>
9f4c99e70SEd Tanous #include <string>
10f4c99e70SEd Tanous #include <string_view>
117cf436c9SEd Tanous #include <utility>
12f4c99e70SEd Tanous #include <vector>
13f4c99e70SEd Tanous 
14f4c99e70SEd Tanous namespace redfish
15f4c99e70SEd Tanous {
16f4c99e70SEd Tanous namespace query_param
17f4c99e70SEd Tanous {
18f4c99e70SEd Tanous 
197cf436c9SEd Tanous enum class ExpandType : uint8_t
207cf436c9SEd Tanous {
217cf436c9SEd Tanous     None,
227cf436c9SEd Tanous     Links,
237cf436c9SEd Tanous     NotLinks,
247cf436c9SEd Tanous     Both,
257cf436c9SEd Tanous };
267cf436c9SEd Tanous 
27a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
28f4c99e70SEd Tanous struct Query
29f4c99e70SEd Tanous {
30a6b9125fSNan Zhou     // Only
31f4c99e70SEd Tanous     bool isOnly = false;
32a6b9125fSNan Zhou     // Expand
33a6b9125fSNan Zhou     uint8_t expandLevel = 0;
347cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
35c937d2bfSEd Tanous 
36c937d2bfSEd Tanous     // Skip
37c937d2bfSEd Tanous     size_t skip = 0;
38c937d2bfSEd Tanous 
39c937d2bfSEd Tanous     // Top
40c937d2bfSEd Tanous     size_t top = std::numeric_limits<size_t>::max();
41f4c99e70SEd Tanous };
42f4c99e70SEd Tanous 
43a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
44a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
45a6b9125fSNan Zhou // the processing.
46a6b9125fSNan Zhou struct QueryCapabilities
47a6b9125fSNan Zhou {
48a6b9125fSNan Zhou     bool canDelegateOnly = false;
49c937d2bfSEd Tanous     bool canDelegateTop = false;
50c937d2bfSEd Tanous     bool canDelegateSkip = false;
51a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
52a6b9125fSNan Zhou };
53a6b9125fSNan Zhou 
54a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
55a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
56a6b9125fSNan Zhou // function will take care of it.
57a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
58a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
59a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
60a6b9125fSNan Zhou {
61a6b9125fSNan Zhou     Query delegated;
62a6b9125fSNan Zhou     // delegate only
63a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
64a6b9125fSNan Zhou     {
65a6b9125fSNan Zhou         delegated.isOnly = true;
66a6b9125fSNan Zhou         query.isOnly = false;
67a6b9125fSNan Zhou     }
68a6b9125fSNan Zhou     // delegate expand as much as we can
69a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
70a6b9125fSNan Zhou     {
71a6b9125fSNan Zhou         delegated.expandType = query.expandType;
72a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
73a6b9125fSNan Zhou         {
74a6b9125fSNan Zhou             query.expandType = ExpandType::None;
75a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
76a6b9125fSNan Zhou             query.expandLevel = 0;
77a6b9125fSNan Zhou         }
78a6b9125fSNan Zhou         else
79a6b9125fSNan Zhou         {
80a6b9125fSNan Zhou             query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
81a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
82a6b9125fSNan Zhou         }
83a6b9125fSNan Zhou     }
84c937d2bfSEd Tanous 
85c937d2bfSEd Tanous     // delegate top
86c937d2bfSEd Tanous     if (queryCapabilities.canDelegateTop)
87c937d2bfSEd Tanous     {
88c937d2bfSEd Tanous         delegated.top = query.top;
89c937d2bfSEd Tanous         query.top = std::numeric_limits<size_t>::max();
90c937d2bfSEd Tanous     }
91c937d2bfSEd Tanous 
92c937d2bfSEd Tanous     // delegate skip
93c937d2bfSEd Tanous     if (queryCapabilities.canDelegateSkip)
94c937d2bfSEd Tanous     {
95c937d2bfSEd Tanous         delegated.skip = query.skip;
96c937d2bfSEd Tanous         query.skip = 0;
97c937d2bfSEd Tanous     }
98a6b9125fSNan Zhou     return delegated;
99a6b9125fSNan Zhou }
100a6b9125fSNan Zhou 
1017cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
1027cf436c9SEd Tanous {
1037cf436c9SEd Tanous     if (value.empty())
1047cf436c9SEd Tanous     {
1057cf436c9SEd Tanous         return false;
1067cf436c9SEd Tanous     }
1077cf436c9SEd Tanous     switch (value[0])
1087cf436c9SEd Tanous     {
1097cf436c9SEd Tanous         case '*':
1107cf436c9SEd Tanous             query.expandType = ExpandType::Both;
1117cf436c9SEd Tanous             break;
1127cf436c9SEd Tanous         case '.':
1137cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
1147cf436c9SEd Tanous             break;
1157cf436c9SEd Tanous         case '~':
1167cf436c9SEd Tanous             query.expandType = ExpandType::Links;
1177cf436c9SEd Tanous             break;
1187cf436c9SEd Tanous         default:
1197cf436c9SEd Tanous             return false;
1207cf436c9SEd Tanous 
1217cf436c9SEd Tanous             break;
1227cf436c9SEd Tanous     }
1237cf436c9SEd Tanous     value.remove_prefix(1);
1247cf436c9SEd Tanous     if (value.empty())
1257cf436c9SEd Tanous     {
1267cf436c9SEd Tanous         query.expandLevel = 1;
1277cf436c9SEd Tanous         return true;
1287cf436c9SEd Tanous     }
1297cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
1307cf436c9SEd Tanous     if (!value.starts_with(levels))
1317cf436c9SEd Tanous     {
1327cf436c9SEd Tanous         return false;
1337cf436c9SEd Tanous     }
1347cf436c9SEd Tanous     value.remove_prefix(levels.size());
1357cf436c9SEd Tanous 
1367cf436c9SEd Tanous     auto it = std::from_chars(value.data(), value.data() + value.size(),
1377cf436c9SEd Tanous                               query.expandLevel);
1387cf436c9SEd Tanous     if (it.ec != std::errc())
1397cf436c9SEd Tanous     {
1407cf436c9SEd Tanous         return false;
1417cf436c9SEd Tanous     }
1427cf436c9SEd Tanous     value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
1437cf436c9SEd Tanous     return value == ")";
1447cf436c9SEd Tanous }
1457cf436c9SEd Tanous 
146c937d2bfSEd Tanous enum class QueryError
147c937d2bfSEd Tanous {
148c937d2bfSEd Tanous     Ok,
149c937d2bfSEd Tanous     OutOfRange,
150c937d2bfSEd Tanous     ValueFormat,
151c937d2bfSEd Tanous };
152c937d2bfSEd Tanous 
153c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
154c937d2bfSEd Tanous {
155c937d2bfSEd Tanous     std::from_chars_result r =
156c937d2bfSEd Tanous         std::from_chars(value.data(), value.data() + value.size(), param);
157c937d2bfSEd Tanous 
158c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
159c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
160c937d2bfSEd Tanous     {
161c937d2bfSEd Tanous         return QueryError::OutOfRange;
162c937d2bfSEd Tanous     }
163c937d2bfSEd Tanous     // All other errors are value format
164c937d2bfSEd Tanous     if (r.ec != std::errc())
165c937d2bfSEd Tanous     {
166c937d2bfSEd Tanous         return QueryError::ValueFormat;
167c937d2bfSEd Tanous     }
168c937d2bfSEd Tanous     return QueryError::Ok;
169c937d2bfSEd Tanous }
170c937d2bfSEd Tanous 
171c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
172c937d2bfSEd Tanous {
173c937d2bfSEd Tanous     return getNumericParam(value, query.skip);
174c937d2bfSEd Tanous }
175c937d2bfSEd Tanous 
176c937d2bfSEd Tanous static constexpr size_t maxEntriesPerPage = 1000;
177c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
178c937d2bfSEd Tanous {
179c937d2bfSEd Tanous     QueryError ret = getNumericParam(value, query.top);
180c937d2bfSEd Tanous     if (ret != QueryError::Ok)
181c937d2bfSEd Tanous     {
182c937d2bfSEd Tanous         return ret;
183c937d2bfSEd Tanous     }
184c937d2bfSEd Tanous 
185c937d2bfSEd Tanous     // Range check for sanity.
186c937d2bfSEd Tanous     if (query.top > maxEntriesPerPage)
187c937d2bfSEd Tanous     {
188c937d2bfSEd Tanous         return QueryError::OutOfRange;
189c937d2bfSEd Tanous     }
190c937d2bfSEd Tanous 
191c937d2bfSEd Tanous     return QueryError::Ok;
192c937d2bfSEd Tanous }
193c937d2bfSEd Tanous 
194f4c99e70SEd Tanous inline std::optional<Query>
195f4c99e70SEd Tanous     parseParameters(const boost::urls::params_view& urlParams,
196f4c99e70SEd Tanous                     crow::Response& res)
197f4c99e70SEd Tanous {
198f4c99e70SEd Tanous     Query ret;
199f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
200f4c99e70SEd Tanous     {
201f4c99e70SEd Tanous         std::string_view key(it.key.data(), it.key.size());
202f4c99e70SEd Tanous         std::string_view value(it.value.data(), it.value.size());
203f4c99e70SEd Tanous         if (key == "only")
204f4c99e70SEd Tanous         {
205f4c99e70SEd Tanous             if (!it.value.empty())
206f4c99e70SEd Tanous             {
207f4c99e70SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
208f4c99e70SEd Tanous                 return std::nullopt;
209f4c99e70SEd Tanous             }
210f4c99e70SEd Tanous             ret.isOnly = true;
211f4c99e70SEd Tanous         }
2125e52870bSEd Tanous         else if (key == "$expand" && bmcwebInsecureEnableQueryParams)
2137cf436c9SEd Tanous         {
2147cf436c9SEd Tanous             if (!getExpandType(value, ret))
2157cf436c9SEd Tanous             {
2167cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
2177cf436c9SEd Tanous                 return std::nullopt;
218f4c99e70SEd Tanous             }
2197cf436c9SEd Tanous         }
220c937d2bfSEd Tanous         else if (key == "$top")
221c937d2bfSEd Tanous         {
222c937d2bfSEd Tanous             QueryError topRet = getTopParam(value, ret);
223c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
224c937d2bfSEd Tanous             {
225c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
226c937d2bfSEd Tanous                 return std::nullopt;
227c937d2bfSEd Tanous             }
228c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
229c937d2bfSEd Tanous             {
230c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
231c937d2bfSEd Tanous                     res, value, "$top",
232c937d2bfSEd Tanous                     "1-" + std::to_string(maxEntriesPerPage));
233c937d2bfSEd Tanous                 return std::nullopt;
234c937d2bfSEd Tanous             }
235c937d2bfSEd Tanous         }
236c937d2bfSEd Tanous         else if (key == "$skip")
237c937d2bfSEd Tanous         {
238c937d2bfSEd Tanous             QueryError topRet = getSkipParam(value, ret);
239c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
240c937d2bfSEd Tanous             {
241c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
242c937d2bfSEd Tanous                 return std::nullopt;
243c937d2bfSEd Tanous             }
244c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
245c937d2bfSEd Tanous             {
246c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
247c937d2bfSEd Tanous                     res, value, key,
248c937d2bfSEd Tanous                     "1-" + std::to_string(std::numeric_limits<size_t>::max()));
249c937d2bfSEd Tanous                 return std::nullopt;
250c937d2bfSEd Tanous             }
251c937d2bfSEd Tanous         }
2527cf436c9SEd Tanous         else
2537cf436c9SEd Tanous         {
2547cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
2557cf436c9SEd Tanous             if (key.starts_with("$"))
2567cf436c9SEd Tanous             {
2577cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
2587cf436c9SEd Tanous                 // status code for any unsupported query parameters that
2597cf436c9SEd Tanous                 // start with $ .
2607cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
2617cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
2627cf436c9SEd Tanous                 return std::nullopt;
2637cf436c9SEd Tanous             }
2647cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
2657cf436c9SEd Tanous             // not begin with $ ."
2667cf436c9SEd Tanous         }
2677cf436c9SEd Tanous     }
2687cf436c9SEd Tanous 
269f4c99e70SEd Tanous     return ret;
270f4c99e70SEd Tanous }
271f4c99e70SEd Tanous 
272f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
273f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
274f4c99e70SEd Tanous {
275f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing only query param";
276f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
277f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
278f4c99e70SEd Tanous     {
279f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
280f4c99e70SEd Tanous         completionHandler(res);
281f4c99e70SEd Tanous         return false;
282f4c99e70SEd Tanous     }
283f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
284f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
285f4c99e70SEd Tanous     {
286f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
287f4c99e70SEd Tanous                          << " element, returning full collection.";
288f4c99e70SEd Tanous         completionHandler(res);
289f4c99e70SEd Tanous         return false;
290f4c99e70SEd Tanous     }
291f4c99e70SEd Tanous 
292f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
293f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
294f4c99e70SEd Tanous     {
295f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "No found odata.id";
296f4c99e70SEd Tanous         messages::internalError(res);
297f4c99e70SEd Tanous         completionHandler(res);
298f4c99e70SEd Tanous         return false;
299f4c99e70SEd Tanous     }
300f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
301f4c99e70SEd Tanous     if (url == nullptr)
302f4c99e70SEd Tanous     {
303f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
304f4c99e70SEd Tanous         messages::internalError(res);
305f4c99e70SEd Tanous         completionHandler(res);
306f4c99e70SEd Tanous         return false;
307f4c99e70SEd Tanous     }
308f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
309f4c99e70SEd Tanous     // newReq.session = req.session;
310f4c99e70SEd Tanous     std::error_code ec;
311f4c99e70SEd Tanous     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
312f4c99e70SEd Tanous     if (ec)
313f4c99e70SEd Tanous     {
314f4c99e70SEd Tanous         messages::internalError(res);
315f4c99e70SEd Tanous         completionHandler(res);
316f4c99e70SEd Tanous         return false;
317f4c99e70SEd Tanous     }
318f4c99e70SEd Tanous 
319f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
320f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
321f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
322f4c99e70SEd Tanous     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
323f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
324f4c99e70SEd Tanous     return true;
325f4c99e70SEd Tanous }
326f4c99e70SEd Tanous 
3277cf436c9SEd Tanous struct ExpandNode
3287cf436c9SEd Tanous {
3297cf436c9SEd Tanous     nlohmann::json::json_pointer location;
3307cf436c9SEd Tanous     std::string uri;
3317cf436c9SEd Tanous 
3327cf436c9SEd Tanous     inline bool operator==(const ExpandNode& other) const
3337cf436c9SEd Tanous     {
3347cf436c9SEd Tanous         return location == other.location && uri == other.uri;
3357cf436c9SEd Tanous     }
3367cf436c9SEd Tanous };
3377cf436c9SEd Tanous 
3387cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
3397cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
3407cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
3417cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
3427cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
3437cf436c9SEd Tanous // with the keys from the jsonResponse object
3447cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
3457cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
3467cf436c9SEd Tanous     const nlohmann::json::json_pointer& p, bool inLinks,
3477cf436c9SEd Tanous     std::vector<ExpandNode>& out)
3487cf436c9SEd Tanous {
3497cf436c9SEd Tanous     // If no expand is needed, return early
3507cf436c9SEd Tanous     if (eType == ExpandType::None)
3517cf436c9SEd Tanous     {
3527cf436c9SEd Tanous         return;
3537cf436c9SEd Tanous     }
3547cf436c9SEd Tanous     nlohmann::json::array_t* array =
3557cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
3567cf436c9SEd Tanous     if (array != nullptr)
3577cf436c9SEd Tanous     {
3587cf436c9SEd Tanous         size_t index = 0;
3597cf436c9SEd Tanous         // For arrays, walk every element in the array
3607cf436c9SEd Tanous         for (auto& element : *array)
3617cf436c9SEd Tanous         {
3627cf436c9SEd Tanous             nlohmann::json::json_pointer newPtr = p / index;
3637cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
3647cf436c9SEd Tanous             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
3657cf436c9SEd Tanous                                               out);
3667cf436c9SEd Tanous             index++;
3677cf436c9SEd Tanous         }
3687cf436c9SEd Tanous     }
3697cf436c9SEd Tanous     nlohmann::json::object_t* obj =
3707cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
3717cf436c9SEd Tanous     if (obj == nullptr)
3727cf436c9SEd Tanous     {
3737cf436c9SEd Tanous         return;
3747cf436c9SEd Tanous     }
3757cf436c9SEd Tanous     // Navigation References only ever have a single element
3767cf436c9SEd Tanous     if (obj->size() == 1)
3777cf436c9SEd Tanous     {
3787cf436c9SEd Tanous         if (obj->begin()->first == "@odata.id")
3797cf436c9SEd Tanous         {
3807cf436c9SEd Tanous             const std::string* uri =
3817cf436c9SEd Tanous                 obj->begin()->second.get_ptr<const std::string*>();
3827cf436c9SEd Tanous             if (uri != nullptr)
3837cf436c9SEd Tanous             {
3847cf436c9SEd Tanous                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
3857cf436c9SEd Tanous                 out.push_back({p, *uri});
3867cf436c9SEd Tanous             }
3877cf436c9SEd Tanous         }
3887cf436c9SEd Tanous     }
3897cf436c9SEd Tanous     // Loop the object and look for links
3907cf436c9SEd Tanous     for (auto& element : *obj)
3917cf436c9SEd Tanous     {
392*e479ad58SNan Zhou         bool localInLinks = inLinks;
393*e479ad58SNan Zhou         if (!localInLinks)
3947cf436c9SEd Tanous         {
3957cf436c9SEd Tanous             // Check if this is a links node
396*e479ad58SNan Zhou             localInLinks = element.first == "Links";
3977cf436c9SEd Tanous         }
3987cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
3997cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
400*e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
4017cf436c9SEd Tanous         {
4027cf436c9SEd Tanous             continue;
4037cf436c9SEd Tanous         }
404*e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
4057cf436c9SEd Tanous         {
4067cf436c9SEd Tanous             continue;
4077cf436c9SEd Tanous         }
4087cf436c9SEd Tanous         nlohmann::json::json_pointer newPtr = p / element.first;
4097cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
4107cf436c9SEd Tanous 
4117cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
412*e479ad58SNan Zhou                                           localInLinks, out);
4137cf436c9SEd Tanous     }
4147cf436c9SEd Tanous }
4157cf436c9SEd Tanous 
4167cf436c9SEd Tanous inline std::vector<ExpandNode>
4177cf436c9SEd Tanous     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse,
4187cf436c9SEd Tanous                              const nlohmann::json::json_pointer& root)
4197cf436c9SEd Tanous {
4207cf436c9SEd Tanous     std::vector<ExpandNode> ret;
4217cf436c9SEd Tanous     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
4227cf436c9SEd Tanous     return ret;
4237cf436c9SEd Tanous }
4247cf436c9SEd Tanous 
4257cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
4267cf436c9SEd Tanous {
4277cf436c9SEd Tanous   public:
4287cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
4297cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
4307cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
4317cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
4327cf436c9SEd Tanous     MultiAsyncResp(crow::App& app,
4337cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
4347cf436c9SEd Tanous         app(app),
4357cf436c9SEd Tanous         finalRes(std::move(finalResIn))
4367cf436c9SEd Tanous     {}
4377cf436c9SEd Tanous 
4387cf436c9SEd Tanous     void addAwaitingResponse(
4397cf436c9SEd Tanous         Query query, std::shared_ptr<bmcweb::AsyncResp>& res,
4407cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
4417cf436c9SEd Tanous     {
4427cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
4437cf436c9SEd Tanous             onEndStatic, shared_from_this(), query, finalExpandLocation));
4447cf436c9SEd Tanous     }
4457cf436c9SEd Tanous 
4467cf436c9SEd Tanous     void onEnd(Query query, const nlohmann::json::json_pointer& locationToPlace,
4477cf436c9SEd Tanous                crow::Response& res)
4487cf436c9SEd Tanous     {
4497cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
4507cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
4517cf436c9SEd Tanous 
4527cf436c9SEd Tanous         if (query.expandLevel <= 0)
4537cf436c9SEd Tanous         {
4547cf436c9SEd Tanous             // Last level to expand, no need to go deeper
4557cf436c9SEd Tanous             return;
4567cf436c9SEd Tanous         }
4577cf436c9SEd Tanous         // Now decrease the depth by one to account for the tree node we
4587cf436c9SEd Tanous         // just resolved
4597cf436c9SEd Tanous         query.expandLevel--;
4607cf436c9SEd Tanous 
4617cf436c9SEd Tanous         std::vector<ExpandNode> nodes = findNavigationReferences(
4627cf436c9SEd Tanous             query.expandType, finalObj, locationToPlace);
4637cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
4647cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
4657cf436c9SEd Tanous         {
4667cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Expanding " << locationToPlace;
4677cf436c9SEd Tanous             std::error_code ec;
4687cf436c9SEd Tanous             crow::Request newReq({boost::beast::http::verb::get, node.uri, 11},
4697cf436c9SEd Tanous                                  ec);
4707cf436c9SEd Tanous             if (ec)
4717cf436c9SEd Tanous             {
4727cf436c9SEd Tanous                 messages::internalError(res);
4737cf436c9SEd Tanous                 return;
4747cf436c9SEd Tanous             }
4757cf436c9SEd Tanous 
4767cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
4777cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
4787cf436c9SEd Tanous                              << &asyncResp->res;
4797cf436c9SEd Tanous             addAwaitingResponse(query, asyncResp, node.location);
4807cf436c9SEd Tanous             app.handle(newReq, asyncResp);
4817cf436c9SEd Tanous         }
4827cf436c9SEd Tanous     }
4837cf436c9SEd Tanous 
4847cf436c9SEd Tanous   private:
4857cf436c9SEd Tanous     static void onEndStatic(const std::shared_ptr<MultiAsyncResp>& multi,
4867cf436c9SEd Tanous                             Query query,
4877cf436c9SEd Tanous                             const nlohmann::json::json_pointer& locationToPlace,
4887cf436c9SEd Tanous                             crow::Response& res)
4897cf436c9SEd Tanous     {
4907cf436c9SEd Tanous         multi->onEnd(query, locationToPlace, res);
4917cf436c9SEd Tanous     }
4927cf436c9SEd Tanous 
4937cf436c9SEd Tanous     crow::App& app;
4947cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
4957cf436c9SEd Tanous };
4967cf436c9SEd Tanous 
4977cf436c9SEd Tanous inline void
4987cf436c9SEd Tanous     processAllParams(crow::App& app, const Query query,
4997cf436c9SEd Tanous 
5007cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
5017cf436c9SEd Tanous                      crow::Response& intermediateResponse)
502f4c99e70SEd Tanous {
503f4c99e70SEd Tanous     if (!completionHandler)
504f4c99e70SEd Tanous     {
505f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
506f4c99e70SEd Tanous         return;
507f4c99e70SEd Tanous     }
508f4c99e70SEd Tanous 
509f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
510f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
511f4c99e70SEd Tanous     // params.
512f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
513f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
514f4c99e70SEd Tanous     {
515f4c99e70SEd Tanous         completionHandler(intermediateResponse);
516f4c99e70SEd Tanous         return;
517f4c99e70SEd Tanous     }
518f4c99e70SEd Tanous     if (query.isOnly)
519f4c99e70SEd Tanous     {
520f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
521f4c99e70SEd Tanous         return;
522f4c99e70SEd Tanous     }
5237cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
5247cf436c9SEd Tanous     {
5257cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
5267cf436c9SEd Tanous         // TODO(ed) this is a copy of the response object.  Admittedly,
5277cf436c9SEd Tanous         // we're inherently doing something inefficient, but we shouldn't
5287cf436c9SEd Tanous         // have to do a full copy
5297cf436c9SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
5307cf436c9SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
5317cf436c9SEd Tanous         asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
5327cf436c9SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
5337cf436c9SEd Tanous 
5347cf436c9SEd Tanous         // Start the chain by "ending" the root response
5357cf436c9SEd Tanous         multi->onEnd(query, nlohmann::json::json_pointer(""), asyncResp->res);
5367cf436c9SEd Tanous         return;
5377cf436c9SEd Tanous     }
538f4c99e70SEd Tanous     completionHandler(intermediateResponse);
539f4c99e70SEd Tanous }
540f4c99e70SEd Tanous 
541f4c99e70SEd Tanous } // namespace query_param
542f4c99e70SEd Tanous } // namespace redfish
543