|  | #include "tlbmc/redfish/query_parameters.h" | 
|  |  | 
|  | #include <cstddef> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/status/status.h" | 
|  | #include "absl/status/statusor.h" | 
|  | #include "absl/strings/match.h" | 
|  | #include "absl/strings/numbers.h" | 
|  | #include "absl/strings/str_format.h" | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "absl/strings/strip.h" | 
|  | #include "boost/url/params_view.hpp" | 
|  | #include "nlohmann/json.hpp" | 
|  |  | 
|  | namespace milotic_tlbmc { | 
|  |  | 
|  | std::string ExpandTypeToUrlParam(ExpandType expand_type) { | 
|  | switch (expand_type) { | 
|  | case ExpandType::kNoLinks: | 
|  | return "."; | 
|  | case ExpandType::kLinksOnly: | 
|  | return "~"; | 
|  | case ExpandType::kAll: | 
|  | return "*"; | 
|  | default: | 
|  | return ""; | 
|  | } | 
|  | } | 
|  |  | 
|  | absl::StatusOr<QueryParameters> QueryParameters::Create( | 
|  | boost::urls::params_view request_query_parameters) { | 
|  | QueryParameters query_parameters; | 
|  | if (request_query_parameters.empty()) { | 
|  | return query_parameters; | 
|  | } | 
|  |  | 
|  | // Currently we only support one parameter | 
|  | if (request_query_parameters.size() > 1) { | 
|  | return absl::InvalidArgumentError( | 
|  | "Multiple query parameters are not supported."); | 
|  | } | 
|  |  | 
|  | for (const boost::urls::params_view::value_type it : | 
|  | request_query_parameters) { | 
|  | if (it.key != "$expand") { | 
|  | return absl::InvalidArgumentError("Unsupported query parameter."); | 
|  | } | 
|  |  | 
|  | // We have to parse the expand type and levels if applicable. | 
|  | // Examples: $expand=. or $expand=.($levels=2) | 
|  |  | 
|  | // We support the following | 
|  | // expand=. (Expand with no links) | 
|  | // expand=~ (Expand only links) | 
|  | // expand=* (Expand all) | 
|  | absl::string_view param_value = it.value; | 
|  | if (absl::StartsWith(param_value, ".")) { | 
|  | query_parameters.SetExpandType(ExpandType::kNoLinks); | 
|  | param_value = absl::StripPrefix(param_value, "."); | 
|  | } else if (absl::StartsWith(param_value, "~")) { | 
|  | query_parameters.SetExpandType(ExpandType::kLinksOnly); | 
|  | param_value = absl::StripPrefix(param_value, "~"); | 
|  | } else if (absl::StartsWith(param_value, "*")) { | 
|  | query_parameters.SetExpandType(ExpandType::kAll); | 
|  | param_value = absl::StripPrefix(param_value, "*"); | 
|  | } | 
|  |  | 
|  | // Expand level is defaulted to 1. | 
|  | query_parameters.SetExpandLevel(1); | 
|  |  | 
|  | if (param_value.empty()) { | 
|  | return query_parameters; | 
|  | } | 
|  |  | 
|  | // Get levels if exists | 
|  | if (!absl::StartsWith(param_value, "($levels=") || | 
|  | !absl::EndsWith(param_value, ")")) { | 
|  | return absl::InvalidArgumentError( | 
|  | absl::StrFormat("Invalid Expand Syntax. Got: %s", param_value)); | 
|  | } | 
|  | param_value = absl::StripPrefix(param_value, "($levels="); | 
|  | param_value = absl::StripSuffix(param_value, ")"); | 
|  | int expand_level = 0; | 
|  | if (!absl::SimpleAtoi(param_value, &expand_level)) { | 
|  | return absl::InvalidArgumentError(absl::StrFormat( | 
|  | "Expand Level was not a number. Got: %s", param_value)); | 
|  | } | 
|  | query_parameters.SetExpandLevel(expand_level); | 
|  | } | 
|  |  | 
|  | return query_parameters; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | void GetUnexpandedUrlsHelper(const nlohmann::json& resp_json, | 
|  | const nlohmann::json::json_pointer& cur_pointer, | 
|  | ExpandType expand_type, bool in_links, | 
|  | std::vector<ExpandNode>& unexpanded_urls) { | 
|  | if (resp_json.is_primitive()) { | 
|  | return; | 
|  | } | 
|  | if (resp_json.is_array()) { | 
|  | for (size_t i = 0; i < resp_json.size(); ++i) { | 
|  | GetUnexpandedUrlsHelper(resp_json[i], cur_pointer / i, expand_type, | 
|  | in_links, unexpanded_urls); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (resp_json.is_object()) { | 
|  | // Unexpanded URLs are objects with only an @odata.id | 
|  | if (resp_json.size() == 1 && resp_json.contains("@odata.id")) { | 
|  | unexpanded_urls.push_back(ExpandNode{.json_pointer = cur_pointer, | 
|  | .url = resp_json["@odata.id"]}); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Otherwise recurse on object values | 
|  | for (const auto& [key, value] : resp_json.items()) { | 
|  | bool local_in_links = in_links; | 
|  | if (!local_in_links) { | 
|  | // Check if this is a links node | 
|  | local_in_links = key == "Links"; | 
|  | } | 
|  | // Only traverse the parts of the tree the user asked for | 
|  | // Per section 7.3 of the redfish specification | 
|  | if (local_in_links && expand_type == ExpandType::kNoLinks) { | 
|  | continue; | 
|  | } | 
|  | if (!local_in_links && expand_type == ExpandType::kLinksOnly) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | GetUnexpandedUrlsHelper(value, cur_pointer / key, expand_type, | 
|  | local_in_links, unexpanded_urls); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | std::vector<ExpandNode> GetUnexpandedUrls(const nlohmann::json& resp, | 
|  | ExpandType expand_type) { | 
|  | std::vector<ExpandNode> unexpanded_urls; | 
|  | GetUnexpandedUrlsHelper(resp, nlohmann::json::json_pointer(""), expand_type, | 
|  | false, unexpanded_urls); | 
|  | return unexpanded_urls; | 
|  | } | 
|  |  | 
|  | }  // namespace milotic_tlbmc |