xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 593f6449a6afa130a28c4cc9a55fb857e019917e)
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     {
392e479ad58SNan Zhou         bool localInLinks = inLinks;
393e479ad58SNan Zhou         if (!localInLinks)
3947cf436c9SEd Tanous         {
3957cf436c9SEd Tanous             // Check if this is a links node
396e479ad58SNan 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
400e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
4017cf436c9SEd Tanous         {
4027cf436c9SEd Tanous             continue;
4037cf436c9SEd Tanous         }
404e479ad58SNan 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,
412e479ad58SNan Zhou                                           localInLinks, out);
4137cf436c9SEd Tanous     }
4147cf436c9SEd Tanous }
4157cf436c9SEd Tanous 
4167cf436c9SEd Tanous inline std::vector<ExpandNode>
41772c3ae33SNan Zhou     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
4187cf436c9SEd Tanous {
4197cf436c9SEd Tanous     std::vector<ExpandNode> ret;
42072c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
4217cf436c9SEd Tanous     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
4227cf436c9SEd Tanous     return ret;
4237cf436c9SEd Tanous }
4247cf436c9SEd Tanous 
42572c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
42672c3ae33SNan Zhou // This function shall handle $select when it is added.
42772c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand,
42872c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
42972c3ae33SNan Zhou inline std::string formatQueryForExpand(const Query& query)
43072c3ae33SNan Zhou {
43172c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
43272c3ae33SNan Zhou     if (query.expandLevel <= 1)
43372c3ae33SNan Zhou     {
43472c3ae33SNan Zhou         return {};
43572c3ae33SNan Zhou     }
43672c3ae33SNan Zhou     std::string str = "?$expand=";
43772c3ae33SNan Zhou     switch (query.expandType)
43872c3ae33SNan Zhou     {
43972c3ae33SNan Zhou         case ExpandType::None:
44072c3ae33SNan Zhou             return {};
44172c3ae33SNan Zhou         case ExpandType::Links:
44272c3ae33SNan Zhou             str += '~';
44372c3ae33SNan Zhou             break;
44472c3ae33SNan Zhou         case ExpandType::NotLinks:
44572c3ae33SNan Zhou             str += '.';
44672c3ae33SNan Zhou             break;
44772c3ae33SNan Zhou         case ExpandType::Both:
44872c3ae33SNan Zhou             str += '*';
44972c3ae33SNan Zhou             break;
45072c3ae33SNan Zhou         default:
45172c3ae33SNan Zhou             return {};
45272c3ae33SNan Zhou     }
45372c3ae33SNan Zhou     str += "($levels=";
45472c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
45572c3ae33SNan Zhou     str += ')';
45672c3ae33SNan Zhou     return str;
45772c3ae33SNan Zhou }
45872c3ae33SNan Zhou 
4597cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
4607cf436c9SEd Tanous {
4617cf436c9SEd Tanous   public:
4627cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
4637cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
4647cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
4657cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
4667cf436c9SEd Tanous     MultiAsyncResp(crow::App& app,
4677cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
4687cf436c9SEd Tanous         app(app),
4697cf436c9SEd Tanous         finalRes(std::move(finalResIn))
4707cf436c9SEd Tanous     {}
4717cf436c9SEd Tanous 
4727cf436c9SEd Tanous     void addAwaitingResponse(
47372c3ae33SNan Zhou         std::shared_ptr<bmcweb::AsyncResp>& res,
4747cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
4757cf436c9SEd Tanous     {
4767cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
47772c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
4787cf436c9SEd Tanous     }
4797cf436c9SEd Tanous 
48072c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
4817cf436c9SEd Tanous                      crow::Response& res)
4827cf436c9SEd Tanous     {
4837cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
4847cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
4857cf436c9SEd Tanous     }
4867cf436c9SEd Tanous 
48772c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
48872c3ae33SNan Zhou     // for deeper levels.
48972c3ae33SNan Zhou     void startQuery(const Query& query)
49072c3ae33SNan Zhou     {
49172c3ae33SNan Zhou         std::vector<ExpandNode> nodes =
49272c3ae33SNan Zhou             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
4937cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
49472c3ae33SNan Zhou         const std::string queryStr = formatQueryForExpand(query);
4957cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
4967cf436c9SEd Tanous         {
49772c3ae33SNan Zhou             const std::string subQuery = node.uri + queryStr;
49872c3ae33SNan Zhou             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
4997cf436c9SEd Tanous             std::error_code ec;
50072c3ae33SNan Zhou             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
5017cf436c9SEd Tanous                                  ec);
5027cf436c9SEd Tanous             if (ec)
5037cf436c9SEd Tanous             {
50472c3ae33SNan Zhou                 messages::internalError(finalRes->res);
5057cf436c9SEd Tanous                 return;
5067cf436c9SEd Tanous             }
5077cf436c9SEd Tanous 
5087cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
5097cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
5107cf436c9SEd Tanous                              << &asyncResp->res;
51172c3ae33SNan Zhou 
51272c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
5137cf436c9SEd Tanous             app.handle(newReq, asyncResp);
5147cf436c9SEd Tanous         }
5157cf436c9SEd Tanous     }
5167cf436c9SEd Tanous 
5177cf436c9SEd Tanous   private:
51872c3ae33SNan Zhou     static void
51972c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
5207cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
5217cf436c9SEd Tanous                           crow::Response& res)
5227cf436c9SEd Tanous     {
52372c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
5247cf436c9SEd Tanous     }
5257cf436c9SEd Tanous 
5267cf436c9SEd Tanous     crow::App& app;
5277cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
5287cf436c9SEd Tanous };
5297cf436c9SEd Tanous 
5302a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
5312a68dc80SEd Tanous {
5322a68dc80SEd Tanous     nlohmann::json::object_t* obj =
5332a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
5342a68dc80SEd Tanous     if (obj == nullptr)
5352a68dc80SEd Tanous     {
5362a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
5372a68dc80SEd Tanous         messages::internalError(res);
5382a68dc80SEd Tanous         return;
5392a68dc80SEd Tanous     }
5402a68dc80SEd Tanous 
5412a68dc80SEd Tanous     BMCWEB_LOG_DEBUG << "Handling top/skip";
5422a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
5432a68dc80SEd Tanous     if (members == obj->end())
5442a68dc80SEd Tanous     {
5452a68dc80SEd Tanous         // From the Redfish specification 7.3.1
5462a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
5472a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
5482a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
5492a68dc80SEd Tanous         // collections but are used on singular resources.
5502a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
5512a68dc80SEd Tanous         return;
5522a68dc80SEd Tanous     }
5532a68dc80SEd Tanous 
5542a68dc80SEd Tanous     nlohmann::json::array_t* arr =
5552a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
5562a68dc80SEd Tanous     if (arr == nullptr)
5572a68dc80SEd Tanous     {
5582a68dc80SEd Tanous         messages::internalError(res);
5592a68dc80SEd Tanous         return;
5602a68dc80SEd Tanous     }
5612a68dc80SEd Tanous 
5622a68dc80SEd Tanous     // Per section 7.3.1 of the Redfish specification, $skip is run before $top
5632a68dc80SEd Tanous     // Can only skip as many values as we have
5642a68dc80SEd Tanous     size_t skip = std::min(arr->size(), query.skip);
5652a68dc80SEd Tanous     arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
5662a68dc80SEd Tanous 
5672a68dc80SEd Tanous     size_t top = std::min(arr->size(), query.top);
5682a68dc80SEd Tanous     arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
5692a68dc80SEd Tanous }
5702a68dc80SEd Tanous 
5717cf436c9SEd Tanous inline void
572*593f6449SNan Zhou     processAllParams(crow::App& app, const Query& query,
5737cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
5747cf436c9SEd Tanous                      crow::Response& intermediateResponse)
575f4c99e70SEd Tanous {
576f4c99e70SEd Tanous     if (!completionHandler)
577f4c99e70SEd Tanous     {
578f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
579f4c99e70SEd Tanous         return;
580f4c99e70SEd Tanous     }
581f4c99e70SEd Tanous 
582f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
583f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
584f4c99e70SEd Tanous     // params.
585f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
586f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
587f4c99e70SEd Tanous     {
588f4c99e70SEd Tanous         completionHandler(intermediateResponse);
589f4c99e70SEd Tanous         return;
590f4c99e70SEd Tanous     }
591f4c99e70SEd Tanous     if (query.isOnly)
592f4c99e70SEd Tanous     {
593f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
594f4c99e70SEd Tanous         return;
595f4c99e70SEd Tanous     }
5962a68dc80SEd Tanous 
5972a68dc80SEd Tanous     if (query.top != std::numeric_limits<size_t>::max() || query.skip != 0)
5982a68dc80SEd Tanous     {
5992a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
6002a68dc80SEd Tanous     }
6012a68dc80SEd Tanous 
6027cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
6037cf436c9SEd Tanous     {
6047cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
6057cf436c9SEd Tanous         // TODO(ed) this is a copy of the response object.  Admittedly,
6067cf436c9SEd Tanous         // we're inherently doing something inefficient, but we shouldn't
6077cf436c9SEd Tanous         // have to do a full copy
6087cf436c9SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
6097cf436c9SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
6107cf436c9SEd Tanous         asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
6117cf436c9SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
6127cf436c9SEd Tanous 
61372c3ae33SNan Zhou         multi->startQuery(query);
6147cf436c9SEd Tanous         return;
6157cf436c9SEd Tanous     }
616f4c99e70SEd Tanous     completionHandler(intermediateResponse);
617f4c99e70SEd Tanous }
618f4c99e70SEd Tanous 
619f4c99e70SEd Tanous } // namespace query_param
620f4c99e70SEd Tanous } // namespace redfish
621