xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision ce8ea743055f1b82c60790db40aa3295e03bdf9c)
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"
602cad96eSEd Tanous #include "http_response.hpp"
7f4c99e70SEd Tanous #include "routing.hpp"
8f4c99e70SEd Tanous 
97cf436c9SEd Tanous #include <charconv>
10f4c99e70SEd Tanous #include <string>
11f4c99e70SEd Tanous #include <string_view>
127cf436c9SEd Tanous #include <utility>
13f4c99e70SEd Tanous #include <vector>
14f4c99e70SEd Tanous 
15f4c99e70SEd Tanous namespace redfish
16f4c99e70SEd Tanous {
17f4c99e70SEd Tanous namespace query_param
18f4c99e70SEd Tanous {
19*ce8ea743SJiaqing Zhao inline constexpr size_t maxEntriesPerPage = 1000;
20f4c99e70SEd Tanous 
217cf436c9SEd Tanous enum class ExpandType : uint8_t
227cf436c9SEd Tanous {
237cf436c9SEd Tanous     None,
247cf436c9SEd Tanous     Links,
257cf436c9SEd Tanous     NotLinks,
267cf436c9SEd Tanous     Both,
277cf436c9SEd Tanous };
287cf436c9SEd Tanous 
29a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
30f4c99e70SEd Tanous struct Query
31f4c99e70SEd Tanous {
32a6b9125fSNan Zhou     // Only
33f4c99e70SEd Tanous     bool isOnly = false;
34a6b9125fSNan Zhou     // Expand
35a6b9125fSNan Zhou     uint8_t expandLevel = 0;
367cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
37c937d2bfSEd Tanous 
38c937d2bfSEd Tanous     // Skip
39c937d2bfSEd Tanous     size_t skip = 0;
40c937d2bfSEd Tanous 
41c937d2bfSEd Tanous     // Top
42*ce8ea743SJiaqing Zhao     size_t top = maxEntriesPerPage;
43f4c99e70SEd Tanous };
44f4c99e70SEd Tanous 
45a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
46a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
47a6b9125fSNan Zhou // the processing.
48a6b9125fSNan Zhou struct QueryCapabilities
49a6b9125fSNan Zhou {
50a6b9125fSNan Zhou     bool canDelegateOnly = false;
51c937d2bfSEd Tanous     bool canDelegateTop = false;
52c937d2bfSEd Tanous     bool canDelegateSkip = false;
53a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
54a6b9125fSNan Zhou };
55a6b9125fSNan Zhou 
56a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
57a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
58a6b9125fSNan Zhou // function will take care of it.
59a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
60a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
61a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
62a6b9125fSNan Zhou {
63a6b9125fSNan Zhou     Query delegated;
64a6b9125fSNan Zhou     // delegate only
65a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
66a6b9125fSNan Zhou     {
67a6b9125fSNan Zhou         delegated.isOnly = true;
68a6b9125fSNan Zhou         query.isOnly = false;
69a6b9125fSNan Zhou     }
70a6b9125fSNan Zhou     // delegate expand as much as we can
71a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
72a6b9125fSNan Zhou     {
73a6b9125fSNan Zhou         delegated.expandType = query.expandType;
74a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
75a6b9125fSNan Zhou         {
76a6b9125fSNan Zhou             query.expandType = ExpandType::None;
77a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
78a6b9125fSNan Zhou             query.expandLevel = 0;
79a6b9125fSNan Zhou         }
80a6b9125fSNan Zhou         else
81a6b9125fSNan Zhou         {
82a6b9125fSNan Zhou             query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
83a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
84a6b9125fSNan Zhou         }
85a6b9125fSNan Zhou     }
86c937d2bfSEd Tanous 
87c937d2bfSEd Tanous     // delegate top
88c937d2bfSEd Tanous     if (queryCapabilities.canDelegateTop)
89c937d2bfSEd Tanous     {
90c937d2bfSEd Tanous         delegated.top = query.top;
91*ce8ea743SJiaqing Zhao         query.top = maxEntriesPerPage;
92c937d2bfSEd Tanous     }
93c937d2bfSEd Tanous 
94c937d2bfSEd Tanous     // delegate skip
95c937d2bfSEd Tanous     if (queryCapabilities.canDelegateSkip)
96c937d2bfSEd Tanous     {
97c937d2bfSEd Tanous         delegated.skip = query.skip;
98c937d2bfSEd Tanous         query.skip = 0;
99c937d2bfSEd Tanous     }
100a6b9125fSNan Zhou     return delegated;
101a6b9125fSNan Zhou }
102a6b9125fSNan Zhou 
1037cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
1047cf436c9SEd Tanous {
1057cf436c9SEd Tanous     if (value.empty())
1067cf436c9SEd Tanous     {
1077cf436c9SEd Tanous         return false;
1087cf436c9SEd Tanous     }
1097cf436c9SEd Tanous     switch (value[0])
1107cf436c9SEd Tanous     {
1117cf436c9SEd Tanous         case '*':
1127cf436c9SEd Tanous             query.expandType = ExpandType::Both;
1137cf436c9SEd Tanous             break;
1147cf436c9SEd Tanous         case '.':
1157cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
1167cf436c9SEd Tanous             break;
1177cf436c9SEd Tanous         case '~':
1187cf436c9SEd Tanous             query.expandType = ExpandType::Links;
1197cf436c9SEd Tanous             break;
1207cf436c9SEd Tanous         default:
1217cf436c9SEd Tanous             return false;
1227cf436c9SEd Tanous 
1237cf436c9SEd Tanous             break;
1247cf436c9SEd Tanous     }
1257cf436c9SEd Tanous     value.remove_prefix(1);
1267cf436c9SEd Tanous     if (value.empty())
1277cf436c9SEd Tanous     {
1287cf436c9SEd Tanous         query.expandLevel = 1;
1297cf436c9SEd Tanous         return true;
1307cf436c9SEd Tanous     }
1317cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
1327cf436c9SEd Tanous     if (!value.starts_with(levels))
1337cf436c9SEd Tanous     {
1347cf436c9SEd Tanous         return false;
1357cf436c9SEd Tanous     }
1367cf436c9SEd Tanous     value.remove_prefix(levels.size());
1377cf436c9SEd Tanous 
1387cf436c9SEd Tanous     auto it = std::from_chars(value.data(), value.data() + value.size(),
1397cf436c9SEd Tanous                               query.expandLevel);
1407cf436c9SEd Tanous     if (it.ec != std::errc())
1417cf436c9SEd Tanous     {
1427cf436c9SEd Tanous         return false;
1437cf436c9SEd Tanous     }
1447cf436c9SEd Tanous     value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
1457cf436c9SEd Tanous     return value == ")";
1467cf436c9SEd Tanous }
1477cf436c9SEd Tanous 
148c937d2bfSEd Tanous enum class QueryError
149c937d2bfSEd Tanous {
150c937d2bfSEd Tanous     Ok,
151c937d2bfSEd Tanous     OutOfRange,
152c937d2bfSEd Tanous     ValueFormat,
153c937d2bfSEd Tanous };
154c937d2bfSEd Tanous 
155c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
156c937d2bfSEd Tanous {
157c937d2bfSEd Tanous     std::from_chars_result r =
158c937d2bfSEd Tanous         std::from_chars(value.data(), value.data() + value.size(), param);
159c937d2bfSEd Tanous 
160c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
161c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
162c937d2bfSEd Tanous     {
163c937d2bfSEd Tanous         return QueryError::OutOfRange;
164c937d2bfSEd Tanous     }
165c937d2bfSEd Tanous     // All other errors are value format
166c937d2bfSEd Tanous     if (r.ec != std::errc())
167c937d2bfSEd Tanous     {
168c937d2bfSEd Tanous         return QueryError::ValueFormat;
169c937d2bfSEd Tanous     }
170c937d2bfSEd Tanous     return QueryError::Ok;
171c937d2bfSEd Tanous }
172c937d2bfSEd Tanous 
173c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
174c937d2bfSEd Tanous {
175c937d2bfSEd Tanous     return getNumericParam(value, query.skip);
176c937d2bfSEd Tanous }
177c937d2bfSEd Tanous 
178c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
179c937d2bfSEd Tanous {
180c937d2bfSEd Tanous     QueryError ret = getNumericParam(value, query.top);
181c937d2bfSEd Tanous     if (ret != QueryError::Ok)
182c937d2bfSEd Tanous     {
183c937d2bfSEd Tanous         return ret;
184c937d2bfSEd Tanous     }
185c937d2bfSEd Tanous 
186c937d2bfSEd Tanous     // Range check for sanity.
187c937d2bfSEd Tanous     if (query.top > maxEntriesPerPage)
188c937d2bfSEd Tanous     {
189c937d2bfSEd Tanous         return QueryError::OutOfRange;
190c937d2bfSEd Tanous     }
191c937d2bfSEd Tanous 
192c937d2bfSEd Tanous     return QueryError::Ok;
193c937d2bfSEd Tanous }
194c937d2bfSEd Tanous 
195f4c99e70SEd Tanous inline std::optional<Query>
196f4c99e70SEd Tanous     parseParameters(const boost::urls::params_view& urlParams,
197f4c99e70SEd Tanous                     crow::Response& res)
198f4c99e70SEd Tanous {
199f4c99e70SEd Tanous     Query ret;
200f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
201f4c99e70SEd Tanous     {
202f4c99e70SEd Tanous         std::string_view key(it.key.data(), it.key.size());
203f4c99e70SEd Tanous         std::string_view value(it.value.data(), it.value.size());
204f4c99e70SEd Tanous         if (key == "only")
205f4c99e70SEd Tanous         {
206f4c99e70SEd Tanous             if (!it.value.empty())
207f4c99e70SEd Tanous             {
208f4c99e70SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
209f4c99e70SEd Tanous                 return std::nullopt;
210f4c99e70SEd Tanous             }
211f4c99e70SEd Tanous             ret.isOnly = true;
212f4c99e70SEd Tanous         }
2135e52870bSEd Tanous         else if (key == "$expand" && bmcwebInsecureEnableQueryParams)
2147cf436c9SEd Tanous         {
2157cf436c9SEd Tanous             if (!getExpandType(value, ret))
2167cf436c9SEd Tanous             {
2177cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
2187cf436c9SEd Tanous                 return std::nullopt;
219f4c99e70SEd Tanous             }
2207cf436c9SEd Tanous         }
221c937d2bfSEd Tanous         else if (key == "$top")
222c937d2bfSEd Tanous         {
223c937d2bfSEd Tanous             QueryError topRet = getTopParam(value, ret);
224c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
225c937d2bfSEd Tanous             {
226c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
227c937d2bfSEd Tanous                 return std::nullopt;
228c937d2bfSEd Tanous             }
229c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
230c937d2bfSEd Tanous             {
231c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
232c937d2bfSEd Tanous                     res, value, "$top",
233a926c53eSJiaqing Zhao                     "0-" + std::to_string(maxEntriesPerPage));
234c937d2bfSEd Tanous                 return std::nullopt;
235c937d2bfSEd Tanous             }
236c937d2bfSEd Tanous         }
237c937d2bfSEd Tanous         else if (key == "$skip")
238c937d2bfSEd Tanous         {
239c937d2bfSEd Tanous             QueryError topRet = getSkipParam(value, ret);
240c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
241c937d2bfSEd Tanous             {
242c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
243c937d2bfSEd Tanous                 return std::nullopt;
244c937d2bfSEd Tanous             }
245c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
246c937d2bfSEd Tanous             {
247c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
248c937d2bfSEd Tanous                     res, value, key,
249a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
250c937d2bfSEd Tanous                 return std::nullopt;
251c937d2bfSEd Tanous             }
252c937d2bfSEd Tanous         }
2537cf436c9SEd Tanous         else
2547cf436c9SEd Tanous         {
2557cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
2567cf436c9SEd Tanous             if (key.starts_with("$"))
2577cf436c9SEd Tanous             {
2587cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
2597cf436c9SEd Tanous                 // status code for any unsupported query parameters that
2607cf436c9SEd Tanous                 // start with $ .
2617cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
2627cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
2637cf436c9SEd Tanous                 return std::nullopt;
2647cf436c9SEd Tanous             }
2657cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
2667cf436c9SEd Tanous             // not begin with $ ."
2677cf436c9SEd Tanous         }
2687cf436c9SEd Tanous     }
2697cf436c9SEd Tanous 
270f4c99e70SEd Tanous     return ret;
271f4c99e70SEd Tanous }
272f4c99e70SEd Tanous 
273f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
274f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
275f4c99e70SEd Tanous {
276f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing only query param";
277f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
278f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
279f4c99e70SEd Tanous     {
280f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
281f4c99e70SEd Tanous         completionHandler(res);
282f4c99e70SEd Tanous         return false;
283f4c99e70SEd Tanous     }
284f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
285f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
286f4c99e70SEd Tanous     {
287f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
288f4c99e70SEd Tanous                          << " element, returning full collection.";
289f4c99e70SEd Tanous         completionHandler(res);
290f4c99e70SEd Tanous         return false;
291f4c99e70SEd Tanous     }
292f4c99e70SEd Tanous 
293f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
294f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
295f4c99e70SEd Tanous     {
296f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "No found odata.id";
297f4c99e70SEd Tanous         messages::internalError(res);
298f4c99e70SEd Tanous         completionHandler(res);
299f4c99e70SEd Tanous         return false;
300f4c99e70SEd Tanous     }
301f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
302f4c99e70SEd Tanous     if (url == nullptr)
303f4c99e70SEd Tanous     {
304f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
305f4c99e70SEd Tanous         messages::internalError(res);
306f4c99e70SEd Tanous         completionHandler(res);
307f4c99e70SEd Tanous         return false;
308f4c99e70SEd Tanous     }
309f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
310f4c99e70SEd Tanous     // newReq.session = req.session;
311f4c99e70SEd Tanous     std::error_code ec;
312f4c99e70SEd Tanous     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
313f4c99e70SEd Tanous     if (ec)
314f4c99e70SEd Tanous     {
315f4c99e70SEd Tanous         messages::internalError(res);
316f4c99e70SEd Tanous         completionHandler(res);
317f4c99e70SEd Tanous         return false;
318f4c99e70SEd Tanous     }
319f4c99e70SEd Tanous 
320f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
321f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
322f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
323f4c99e70SEd Tanous     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
324f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
325f4c99e70SEd Tanous     return true;
326f4c99e70SEd Tanous }
327f4c99e70SEd Tanous 
3287cf436c9SEd Tanous struct ExpandNode
3297cf436c9SEd Tanous {
3307cf436c9SEd Tanous     nlohmann::json::json_pointer location;
3317cf436c9SEd Tanous     std::string uri;
3327cf436c9SEd Tanous 
3337cf436c9SEd Tanous     inline bool operator==(const ExpandNode& other) const
3347cf436c9SEd Tanous     {
3357cf436c9SEd Tanous         return location == other.location && uri == other.uri;
3367cf436c9SEd Tanous     }
3377cf436c9SEd Tanous };
3387cf436c9SEd Tanous 
3397cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
3407cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
3417cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
3427cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
3437cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
3447cf436c9SEd Tanous // with the keys from the jsonResponse object
3457cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
3467cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
3477cf436c9SEd Tanous     const nlohmann::json::json_pointer& p, bool inLinks,
3487cf436c9SEd Tanous     std::vector<ExpandNode>& out)
3497cf436c9SEd Tanous {
3507cf436c9SEd Tanous     // If no expand is needed, return early
3517cf436c9SEd Tanous     if (eType == ExpandType::None)
3527cf436c9SEd Tanous     {
3537cf436c9SEd Tanous         return;
3547cf436c9SEd Tanous     }
3557cf436c9SEd Tanous     nlohmann::json::array_t* array =
3567cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
3577cf436c9SEd Tanous     if (array != nullptr)
3587cf436c9SEd Tanous     {
3597cf436c9SEd Tanous         size_t index = 0;
3607cf436c9SEd Tanous         // For arrays, walk every element in the array
3617cf436c9SEd Tanous         for (auto& element : *array)
3627cf436c9SEd Tanous         {
3637cf436c9SEd Tanous             nlohmann::json::json_pointer newPtr = p / index;
3647cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
3657cf436c9SEd Tanous             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
3667cf436c9SEd Tanous                                               out);
3677cf436c9SEd Tanous             index++;
3687cf436c9SEd Tanous         }
3697cf436c9SEd Tanous     }
3707cf436c9SEd Tanous     nlohmann::json::object_t* obj =
3717cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
3727cf436c9SEd Tanous     if (obj == nullptr)
3737cf436c9SEd Tanous     {
3747cf436c9SEd Tanous         return;
3757cf436c9SEd Tanous     }
3767cf436c9SEd Tanous     // Navigation References only ever have a single element
3777cf436c9SEd Tanous     if (obj->size() == 1)
3787cf436c9SEd Tanous     {
3797cf436c9SEd Tanous         if (obj->begin()->first == "@odata.id")
3807cf436c9SEd Tanous         {
3817cf436c9SEd Tanous             const std::string* uri =
3827cf436c9SEd Tanous                 obj->begin()->second.get_ptr<const std::string*>();
3837cf436c9SEd Tanous             if (uri != nullptr)
3847cf436c9SEd Tanous             {
3857cf436c9SEd Tanous                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
3867cf436c9SEd Tanous                 out.push_back({p, *uri});
3877cf436c9SEd Tanous             }
3887cf436c9SEd Tanous         }
3897cf436c9SEd Tanous     }
3907cf436c9SEd Tanous     // Loop the object and look for links
3917cf436c9SEd Tanous     for (auto& element : *obj)
3927cf436c9SEd Tanous     {
393e479ad58SNan Zhou         bool localInLinks = inLinks;
394e479ad58SNan Zhou         if (!localInLinks)
3957cf436c9SEd Tanous         {
3967cf436c9SEd Tanous             // Check if this is a links node
397e479ad58SNan Zhou             localInLinks = element.first == "Links";
3987cf436c9SEd Tanous         }
3997cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
4007cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
401e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
4027cf436c9SEd Tanous         {
4037cf436c9SEd Tanous             continue;
4047cf436c9SEd Tanous         }
405e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
4067cf436c9SEd Tanous         {
4077cf436c9SEd Tanous             continue;
4087cf436c9SEd Tanous         }
4097cf436c9SEd Tanous         nlohmann::json::json_pointer newPtr = p / element.first;
4107cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
4117cf436c9SEd Tanous 
4127cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
413e479ad58SNan Zhou                                           localInLinks, out);
4147cf436c9SEd Tanous     }
4157cf436c9SEd Tanous }
4167cf436c9SEd Tanous 
4177cf436c9SEd Tanous inline std::vector<ExpandNode>
41872c3ae33SNan Zhou     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
4197cf436c9SEd Tanous {
4207cf436c9SEd Tanous     std::vector<ExpandNode> ret;
42172c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
4227cf436c9SEd Tanous     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
4237cf436c9SEd Tanous     return ret;
4247cf436c9SEd Tanous }
4257cf436c9SEd Tanous 
42672c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
427b66cf2a2SNan Zhou // Returns std::nullopt on failures.
42872c3ae33SNan Zhou // This function shall handle $select when it is added.
42972c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand,
43072c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
431b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
43272c3ae33SNan Zhou {
43372c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
43472c3ae33SNan Zhou     if (query.expandLevel <= 1)
43572c3ae33SNan Zhou     {
436b66cf2a2SNan Zhou         return "";
43772c3ae33SNan Zhou     }
43872c3ae33SNan Zhou     std::string str = "?$expand=";
439b66cf2a2SNan Zhou     bool queryTypeExpected = false;
44072c3ae33SNan Zhou     switch (query.expandType)
44172c3ae33SNan Zhou     {
44272c3ae33SNan Zhou         case ExpandType::None:
443b66cf2a2SNan Zhou             return "";
44472c3ae33SNan Zhou         case ExpandType::Links:
445b66cf2a2SNan Zhou             queryTypeExpected = true;
44672c3ae33SNan Zhou             str += '~';
44772c3ae33SNan Zhou             break;
44872c3ae33SNan Zhou         case ExpandType::NotLinks:
449b66cf2a2SNan Zhou             queryTypeExpected = true;
45072c3ae33SNan Zhou             str += '.';
45172c3ae33SNan Zhou             break;
45272c3ae33SNan Zhou         case ExpandType::Both:
453b66cf2a2SNan Zhou             queryTypeExpected = true;
45472c3ae33SNan Zhou             str += '*';
45572c3ae33SNan Zhou             break;
456b66cf2a2SNan Zhou     }
457b66cf2a2SNan Zhou     if (!queryTypeExpected)
458b66cf2a2SNan Zhou     {
459b66cf2a2SNan Zhou         return std::nullopt;
46072c3ae33SNan Zhou     }
46172c3ae33SNan Zhou     str += "($levels=";
46272c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
46372c3ae33SNan Zhou     str += ')';
46472c3ae33SNan Zhou     return str;
46572c3ae33SNan Zhou }
46672c3ae33SNan Zhou 
4677cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
4687cf436c9SEd Tanous {
4697cf436c9SEd Tanous   public:
4707cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
4717cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
4727cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
4737cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
4748a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
4757cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
4768a592810SEd Tanous         app(appIn),
4777cf436c9SEd Tanous         finalRes(std::move(finalResIn))
4787cf436c9SEd Tanous     {}
4797cf436c9SEd Tanous 
4807cf436c9SEd Tanous     void addAwaitingResponse(
48102cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
4827cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
4837cf436c9SEd Tanous     {
4847cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
48572c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
4867cf436c9SEd Tanous     }
4877cf436c9SEd Tanous 
48872c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
4897cf436c9SEd Tanous                      crow::Response& res)
4907cf436c9SEd Tanous     {
4917cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
4927cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
4937cf436c9SEd Tanous     }
4947cf436c9SEd Tanous 
49572c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
49672c3ae33SNan Zhou     // for deeper levels.
49772c3ae33SNan Zhou     void startQuery(const Query& query)
49872c3ae33SNan Zhou     {
49972c3ae33SNan Zhou         std::vector<ExpandNode> nodes =
50072c3ae33SNan Zhou             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
5017cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
502b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
503b66cf2a2SNan Zhou         if (!queryStr)
504b66cf2a2SNan Zhou         {
505b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
506b66cf2a2SNan Zhou             return;
507b66cf2a2SNan Zhou         }
5087cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
5097cf436c9SEd Tanous         {
510b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
51172c3ae33SNan Zhou             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
5127cf436c9SEd Tanous             std::error_code ec;
51372c3ae33SNan Zhou             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
5147cf436c9SEd Tanous                                  ec);
5157cf436c9SEd Tanous             if (ec)
5167cf436c9SEd Tanous             {
51772c3ae33SNan Zhou                 messages::internalError(finalRes->res);
5187cf436c9SEd Tanous                 return;
5197cf436c9SEd Tanous             }
5207cf436c9SEd Tanous 
5217cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
5227cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
5237cf436c9SEd Tanous                              << &asyncResp->res;
52472c3ae33SNan Zhou 
52572c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
5267cf436c9SEd Tanous             app.handle(newReq, asyncResp);
5277cf436c9SEd Tanous         }
5287cf436c9SEd Tanous     }
5297cf436c9SEd Tanous 
5307cf436c9SEd Tanous   private:
53172c3ae33SNan Zhou     static void
53272c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
5337cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
5347cf436c9SEd Tanous                           crow::Response& res)
5357cf436c9SEd Tanous     {
53672c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
5377cf436c9SEd Tanous     }
5387cf436c9SEd Tanous 
5397cf436c9SEd Tanous     crow::App& app;
5407cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
5417cf436c9SEd Tanous };
5427cf436c9SEd Tanous 
5432a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
5442a68dc80SEd Tanous {
5452a68dc80SEd Tanous     nlohmann::json::object_t* obj =
5462a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
5472a68dc80SEd Tanous     if (obj == nullptr)
5482a68dc80SEd Tanous     {
5492a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
5502a68dc80SEd Tanous         messages::internalError(res);
5512a68dc80SEd Tanous         return;
5522a68dc80SEd Tanous     }
5532a68dc80SEd Tanous 
5542a68dc80SEd Tanous     BMCWEB_LOG_DEBUG << "Handling top/skip";
5552a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
5562a68dc80SEd Tanous     if (members == obj->end())
5572a68dc80SEd Tanous     {
5582a68dc80SEd Tanous         // From the Redfish specification 7.3.1
5592a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
5602a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
5612a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
5622a68dc80SEd Tanous         // collections but are used on singular resources.
5632a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
5642a68dc80SEd Tanous         return;
5652a68dc80SEd Tanous     }
5662a68dc80SEd Tanous 
5672a68dc80SEd Tanous     nlohmann::json::array_t* arr =
5682a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
5692a68dc80SEd Tanous     if (arr == nullptr)
5702a68dc80SEd Tanous     {
5712a68dc80SEd Tanous         messages::internalError(res);
5722a68dc80SEd Tanous         return;
5732a68dc80SEd Tanous     }
5742a68dc80SEd Tanous 
5752a68dc80SEd Tanous     // Per section 7.3.1 of the Redfish specification, $skip is run before $top
5762a68dc80SEd Tanous     // Can only skip as many values as we have
5772a68dc80SEd Tanous     size_t skip = std::min(arr->size(), query.skip);
5782a68dc80SEd Tanous     arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
5792a68dc80SEd Tanous 
5802a68dc80SEd Tanous     size_t top = std::min(arr->size(), query.top);
5812a68dc80SEd Tanous     arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
5822a68dc80SEd Tanous }
5832a68dc80SEd Tanous 
5847cf436c9SEd Tanous inline void
585593f6449SNan Zhou     processAllParams(crow::App& app, const Query& query,
5867cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
5877cf436c9SEd Tanous                      crow::Response& intermediateResponse)
588f4c99e70SEd Tanous {
589f4c99e70SEd Tanous     if (!completionHandler)
590f4c99e70SEd Tanous     {
591f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
592f4c99e70SEd Tanous         return;
593f4c99e70SEd Tanous     }
594f4c99e70SEd Tanous 
595f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
596f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
597f4c99e70SEd Tanous     // params.
598f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
599f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
600f4c99e70SEd Tanous     {
601f4c99e70SEd Tanous         completionHandler(intermediateResponse);
602f4c99e70SEd Tanous         return;
603f4c99e70SEd Tanous     }
604f4c99e70SEd Tanous     if (query.isOnly)
605f4c99e70SEd Tanous     {
606f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
607f4c99e70SEd Tanous         return;
608f4c99e70SEd Tanous     }
6092a68dc80SEd Tanous 
610*ce8ea743SJiaqing Zhao     if (query.top <= maxEntriesPerPage || query.skip != 0)
6112a68dc80SEd Tanous     {
6122a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
6132a68dc80SEd Tanous     }
6142a68dc80SEd Tanous 
6157cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
6167cf436c9SEd Tanous     {
6177cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
6187cf436c9SEd Tanous         // TODO(ed) this is a copy of the response object.  Admittedly,
6197cf436c9SEd Tanous         // we're inherently doing something inefficient, but we shouldn't
6207cf436c9SEd Tanous         // have to do a full copy
6217cf436c9SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
6227cf436c9SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
6237cf436c9SEd Tanous         asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
6247cf436c9SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
6257cf436c9SEd Tanous 
62672c3ae33SNan Zhou         multi->startQuery(query);
6277cf436c9SEd Tanous         return;
6287cf436c9SEd Tanous     }
629f4c99e70SEd Tanous     completionHandler(intermediateResponse);
630f4c99e70SEd Tanous }
631f4c99e70SEd Tanous 
632f4c99e70SEd Tanous } // namespace query_param
633f4c99e70SEd Tanous } // namespace redfish
634