1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 4 #pragma once 5 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "human_sort.hpp" 10 #include "logging.hpp" 11 12 #include <boost/system/result.hpp> 13 #include <boost/url/parse.hpp> 14 #include <boost/url/url_view.hpp> 15 #include <nlohmann/json.hpp> 16 17 #include <algorithm> 18 #include <array> 19 #include <cmath> 20 #include <cstddef> 21 #include <cstdint> 22 #include <limits> 23 #include <map> 24 #include <optional> 25 #include <ranges> 26 #include <span> 27 #include <string> 28 #include <string_view> 29 #include <type_traits> 30 #include <utility> 31 #include <variant> 32 #include <vector> 33 34 // IWYU pragma: no_forward_declare crow::Request 35 36 namespace redfish 37 { 38 39 namespace json_util 40 { 41 42 /** 43 * @brief Processes request to extract JSON from its body. If it fails, adds 44 * MalformedJSON message to response and ends it. 45 * 46 * @param[io] res Response object 47 * @param[in] req Request object 48 * @param[out] reqJson JSON object extracted from request's body 49 * 50 * @return true if JSON is valid, false when JSON is invalid and response has 51 * been filled with message and ended. 52 */ 53 bool processJsonFromRequest(crow::Response& res, const crow::Request& req, 54 nlohmann::json& reqJson); 55 namespace details 56 { 57 58 template <typename Type> 59 struct IsOptional : std::false_type 60 {}; 61 62 template <typename Type> 63 struct IsOptional<std::optional<Type>> : std::true_type 64 {}; 65 66 template <typename Type> 67 struct IsVector : std::false_type 68 {}; 69 70 template <typename Type> 71 struct IsVector<std::vector<Type>> : std::true_type 72 {}; 73 74 template <typename Type> 75 struct IsStdArray : std::false_type 76 {}; 77 78 template <typename Type, std::size_t size> 79 struct IsStdArray<std::array<Type, size>> : std::true_type 80 {}; 81 82 template <typename Type> 83 struct IsVariant : std::false_type 84 {}; 85 86 template <typename... Types> 87 struct IsVariant<std::variant<Types...>> : std::true_type 88 {}; 89 90 enum class UnpackErrorCode 91 { 92 success, 93 invalidType, 94 outOfRange 95 }; 96 97 template <typename ToType, typename FromType> checkRange(const FromType & from,std::string_view key)98 bool checkRange(const FromType& from [[maybe_unused]], 99 std::string_view key [[maybe_unused]]) 100 { 101 if constexpr (std::is_floating_point_v<ToType>) 102 { 103 if (std::isnan(from)) 104 { 105 BMCWEB_LOG_DEBUG("Value for key {} was NAN", key); 106 return false; 107 } 108 } 109 if constexpr (std::numeric_limits<ToType>::max() < 110 std::numeric_limits<FromType>::max()) 111 { 112 if (from > std::numeric_limits<ToType>::max()) 113 { 114 BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key, 115 std::numeric_limits<FromType>::max()); 116 return false; 117 } 118 } 119 if constexpr (std::numeric_limits<ToType>::lowest() > 120 std::numeric_limits<FromType>::lowest()) 121 { 122 if (from < std::numeric_limits<ToType>::lowest()) 123 { 124 BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key, 125 std::numeric_limits<FromType>::lowest()); 126 return false; 127 } 128 } 129 130 return true; 131 } 132 133 template <typename Type> 134 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, 135 std::string_view key, Type& value); 136 137 template <std::size_t Index = 0, typename... Args> unpackValueVariant(nlohmann::json & j,std::string_view key,std::variant<Args...> & v)138 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key, 139 std::variant<Args...>& v) 140 { 141 if constexpr (Index < std::variant_size_v<std::variant<Args...>>) 142 { 143 std::variant_alternative_t<Index, std::variant<Args...>> type{}; 144 UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type); 145 if (unpack == UnpackErrorCode::success) 146 { 147 v = std::move(type); 148 return unpack; 149 } 150 151 return unpackValueVariant<Index + 1, Args...>(j, key, v); 152 } 153 return UnpackErrorCode::invalidType; 154 } 155 156 template <typename Type> unpackValueWithErrorCode(nlohmann::json & jsonValue,std::string_view key,Type & value)157 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, 158 std::string_view key, Type& value) 159 { 160 UnpackErrorCode ret = UnpackErrorCode::success; 161 162 if constexpr (std::is_floating_point_v<Type>) 163 { 164 double helper = 0; 165 double* jsonPtr = jsonValue.get_ptr<double*>(); 166 167 if (jsonPtr == nullptr) 168 { 169 int64_t* intPtr = jsonValue.get_ptr<int64_t*>(); 170 if (intPtr != nullptr) 171 { 172 helper = static_cast<double>(*intPtr); 173 jsonPtr = &helper; 174 } 175 } 176 if (jsonPtr == nullptr) 177 { 178 return UnpackErrorCode::invalidType; 179 } 180 if (!checkRange<Type>(*jsonPtr, key)) 181 { 182 return UnpackErrorCode::outOfRange; 183 } 184 value = static_cast<Type>(*jsonPtr); 185 } 186 187 else if constexpr (std::is_signed_v<Type>) 188 { 189 int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>(); 190 if (jsonPtr == nullptr) 191 { 192 return UnpackErrorCode::invalidType; 193 } 194 if (!checkRange<Type>(*jsonPtr, key)) 195 { 196 return UnpackErrorCode::outOfRange; 197 } 198 value = static_cast<Type>(*jsonPtr); 199 } 200 201 else if constexpr ((std::is_unsigned_v<Type>) && 202 (!std::is_same_v<bool, Type>)) 203 { 204 uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>(); 205 if (jsonPtr == nullptr) 206 { 207 return UnpackErrorCode::invalidType; 208 } 209 if (!checkRange<Type>(*jsonPtr, key)) 210 { 211 return UnpackErrorCode::outOfRange; 212 } 213 value = static_cast<Type>(*jsonPtr); 214 } 215 216 else if constexpr (std::is_same_v<nlohmann::json, Type>) 217 { 218 value = std::move(jsonValue); 219 } 220 else if constexpr (std::is_same_v<std::nullptr_t, Type>) 221 { 222 if (!jsonValue.is_null()) 223 { 224 return UnpackErrorCode::invalidType; 225 } 226 } 227 else if constexpr (IsVector<Type>::value) 228 { 229 nlohmann::json::object_t* obj = 230 jsonValue.get_ptr<nlohmann::json::object_t*>(); 231 if (obj == nullptr) 232 { 233 return UnpackErrorCode::invalidType; 234 } 235 236 for (const auto& val : *obj) 237 { 238 value.emplace_back(); 239 ret = unpackValueWithErrorCode<typename Type::value_type>( 240 val, key, value.back()) && 241 ret; 242 } 243 } 244 else 245 { 246 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 247 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 248 if (jsonPtr == nullptr) 249 { 250 BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key, 251 jsonValue.type_name()); 252 return UnpackErrorCode::invalidType; 253 } 254 value = std::move(*jsonPtr); 255 } 256 return ret; 257 } 258 259 template <typename Type> unpackValue(nlohmann::json & jsonValue,std::string_view key,crow::Response & res,Type & value)260 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, 261 crow::Response& res, Type& value) 262 { 263 bool ret = true; 264 265 if constexpr (IsOptional<Type>::value) 266 { 267 value.emplace(); 268 ret = unpackValue<typename Type::value_type>(jsonValue, key, res, 269 *value) && 270 ret; 271 } 272 else if constexpr (IsStdArray<Type>::value) 273 { 274 nlohmann::json::array_t* arr = 275 jsonValue.get_ptr<nlohmann::json::array_t*>(); 276 if (arr == nullptr) 277 { 278 messages::propertyValueTypeError(res, jsonValue, key); 279 return false; 280 } 281 if (jsonValue.size() != value.size()) 282 { 283 messages::propertyValueTypeError(res, jsonValue, key); 284 return false; 285 } 286 size_t index = 0; 287 for (auto& val : *arr) 288 { 289 ret = unpackValue<typename Type::value_type>(val, key, res, 290 value[index++]) && 291 ret; 292 } 293 } 294 else if constexpr (IsVector<Type>::value) 295 { 296 nlohmann::json::array_t* arr = 297 jsonValue.get_ptr<nlohmann::json::array_t*>(); 298 if (arr == nullptr) 299 { 300 messages::propertyValueTypeError(res, jsonValue, key); 301 return false; 302 } 303 304 for (auto& val : *arr) 305 { 306 value.emplace_back(); 307 ret = unpackValue<typename Type::value_type>(val, key, res, 308 value.back()) && 309 ret; 310 } 311 } 312 else if constexpr (IsVariant<Type>::value) 313 { 314 UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value); 315 if (ec != UnpackErrorCode::success) 316 { 317 if (ec == UnpackErrorCode::invalidType) 318 { 319 messages::propertyValueTypeError(res, jsonValue, key); 320 } 321 else if (ec == UnpackErrorCode::outOfRange) 322 { 323 messages::propertyValueOutOfRange(res, jsonValue, key); 324 } 325 return false; 326 } 327 } 328 else 329 { 330 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 331 if (ec != UnpackErrorCode::success) 332 { 333 if (ec == UnpackErrorCode::invalidType) 334 { 335 messages::propertyValueTypeError(res, jsonValue, key); 336 } 337 else if (ec == UnpackErrorCode::outOfRange) 338 { 339 messages::propertyValueOutOfRange(res, jsonValue, key); 340 } 341 return false; 342 } 343 } 344 345 return ret; 346 } 347 348 template <typename Type> unpackValue(nlohmann::json & jsonValue,std::string_view key,Type & value)349 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) 350 { 351 bool ret = true; 352 if constexpr (IsOptional<Type>::value) 353 { 354 value.emplace(); 355 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && 356 ret; 357 } 358 else if constexpr (IsStdArray<Type>::value) 359 { 360 nlohmann::json::array_t* arr = 361 jsonValue.get_ptr<nlohmann::json::array_t*>(); 362 if (arr == nullptr) 363 { 364 return false; 365 } 366 if (jsonValue.size() != value.size()) 367 { 368 return false; 369 } 370 size_t index = 0; 371 for (const auto& val : *arr) 372 { 373 ret = unpackValue<typename Type::value_type>(val, key, 374 value[index++]) && 375 ret; 376 } 377 } 378 else if constexpr (IsVector<Type>::value) 379 { 380 nlohmann::json::array_t* arr = 381 jsonValue.get_ptr<nlohmann::json::array_t*>(); 382 if (arr == nullptr) 383 { 384 return false; 385 } 386 387 for (const auto& val : *arr) 388 { 389 value.emplace_back(); 390 ret = unpackValue<typename Type::value_type>(val, key, 391 value.back()) && 392 ret; 393 } 394 } 395 else 396 { 397 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 398 if (ec != UnpackErrorCode::success) 399 { 400 return false; 401 } 402 } 403 404 return ret; 405 } 406 } // namespace details 407 408 // clang-format off 409 using UnpackVariant = std::variant< 410 uint8_t*, 411 uint16_t*, 412 int16_t*, 413 uint32_t*, 414 int32_t*, 415 uint64_t*, 416 int64_t*, 417 bool*, 418 double*, 419 std::string*, 420 nlohmann::json::object_t*, 421 std::variant<std::string, std::nullptr_t>*, 422 std::variant<uint8_t, std::nullptr_t>*, 423 std::variant<int16_t, std::nullptr_t>*, 424 std::variant<uint16_t, std::nullptr_t>*, 425 std::variant<int32_t, std::nullptr_t>*, 426 std::variant<uint32_t, std::nullptr_t>*, 427 std::variant<int64_t, std::nullptr_t>*, 428 std::variant<uint64_t, std::nullptr_t>*, 429 std::variant<double, std::nullptr_t>*, 430 std::variant<bool, std::nullptr_t>*, 431 std::vector<uint8_t>*, 432 std::vector<uint16_t>*, 433 std::vector<int16_t>*, 434 std::vector<uint32_t>*, 435 std::vector<int32_t>*, 436 std::vector<uint64_t>*, 437 std::vector<int64_t>*, 438 //std::vector<bool>*, 439 std::vector<double>*, 440 std::vector<std::string>*, 441 std::vector<nlohmann::json::object_t>*, 442 std::optional<uint8_t>*, 443 std::optional<uint16_t>*, 444 std::optional<int16_t>*, 445 std::optional<uint32_t>*, 446 std::optional<int32_t>*, 447 std::optional<uint64_t>*, 448 std::optional<int64_t>*, 449 std::optional<bool>*, 450 std::optional<double>*, 451 std::optional<std::string>*, 452 std::optional<nlohmann::json::object_t>*, 453 std::optional<std::vector<uint8_t>>*, 454 std::optional<std::vector<uint16_t>>*, 455 std::optional<std::vector<int16_t>>*, 456 std::optional<std::vector<uint32_t>>*, 457 std::optional<std::vector<int32_t>>*, 458 std::optional<std::vector<uint64_t>>*, 459 std::optional<std::vector<int64_t>>*, 460 //std::optional<std::vector<bool>>*, 461 std::optional<std::vector<double>>*, 462 std::optional<std::vector<std::string>>*, 463 std::optional<std::vector<nlohmann::json::object_t>>*, 464 std::optional<std::variant<std::string, std::nullptr_t>>*, 465 std::optional<std::variant<uint8_t, std::nullptr_t>>*, 466 std::optional<std::variant<int16_t, std::nullptr_t>>*, 467 std::optional<std::variant<uint16_t, std::nullptr_t>>*, 468 std::optional<std::variant<int32_t, std::nullptr_t>>*, 469 std::optional<std::variant<uint32_t, std::nullptr_t>>*, 470 std::optional<std::variant<int64_t, std::nullptr_t>>*, 471 std::optional<std::variant<uint64_t, std::nullptr_t>>*, 472 std::optional<std::variant<double, std::nullptr_t>>*, 473 std::optional<std::variant<bool, std::nullptr_t>>*, 474 std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*, 475 std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*, 476 477 // Note, these types are kept for historical completeness, but should not be used, 478 // As they do not provide object type safety. Instead, rely on nlohmann::json::object_t 479 // Will be removed Q2 2025 480 nlohmann::json*, 481 std::optional<std::vector<nlohmann::json>>*, 482 std::vector<nlohmann::json>*, 483 std::optional<nlohmann::json>* 484 >; 485 // clang-format on 486 487 struct PerUnpack 488 { 489 std::string_view key; 490 UnpackVariant value; 491 bool complete = false; 492 }; 493 readJsonHelperObject(nlohmann::json::object_t & obj,crow::Response & res,std::span<PerUnpack> toUnpack)494 inline bool readJsonHelperObject(nlohmann::json::object_t& obj, 495 crow::Response& res, 496 std::span<PerUnpack> toUnpack) 497 { 498 bool result = true; 499 for (auto& item : obj) 500 { 501 size_t unpackIndex = 0; 502 for (; unpackIndex < toUnpack.size(); unpackIndex++) 503 { 504 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 505 std::string_view key = unpackSpec.key; 506 size_t keysplitIndex = key.find('/'); 507 std::string_view leftover; 508 if (keysplitIndex != std::string_view::npos) 509 { 510 leftover = key.substr(keysplitIndex + 1); 511 key = key.substr(0, keysplitIndex); 512 } 513 514 if (key != item.first || unpackSpec.complete) 515 { 516 continue; 517 } 518 519 // Sublevel key 520 if (!leftover.empty()) 521 { 522 // Include the slash in the key so we can compare later 523 key = unpackSpec.key.substr(0, keysplitIndex + 1); 524 nlohmann::json::object_t j; 525 result = details::unpackValue<nlohmann::json::object_t>( 526 item.second, key, res, j) && 527 result; 528 if (!result) 529 { 530 return result; 531 } 532 533 std::vector<PerUnpack> nextLevel; 534 for (PerUnpack& p : toUnpack) 535 { 536 if (!p.key.starts_with(key)) 537 { 538 continue; 539 } 540 std::string_view thisLeftover = p.key.substr(key.size()); 541 nextLevel.push_back({thisLeftover, p.value, false}); 542 p.complete = true; 543 } 544 545 result = readJsonHelperObject(j, res, nextLevel) && result; 546 break; 547 } 548 549 result = 550 std::visit( 551 [&item, &unpackSpec, &res](auto& val) { 552 using ContainedT = 553 std::remove_pointer_t<std::decay_t<decltype(val)>>; 554 return details::unpackValue<ContainedT>( 555 item.second, unpackSpec.key, res, *val); 556 }, 557 unpackSpec.value) && 558 result; 559 560 unpackSpec.complete = true; 561 break; 562 } 563 564 if (unpackIndex == toUnpack.size()) 565 { 566 messages::propertyUnknown(res, item.first); 567 result = false; 568 } 569 } 570 571 for (PerUnpack& perUnpack : toUnpack) 572 { 573 if (!perUnpack.complete) 574 { 575 bool isOptional = std::visit( 576 [](auto& val) { 577 using ContainedType = 578 std::remove_pointer_t<std::decay_t<decltype(val)>>; 579 return details::IsOptional<ContainedType>::value; 580 }, 581 perUnpack.value); 582 if (isOptional) 583 { 584 continue; 585 } 586 messages::propertyMissing(res, perUnpack.key); 587 result = false; 588 } 589 } 590 return result; 591 } 592 packVariant(std::span<PerUnpack>)593 inline void packVariant(std::span<PerUnpack> /*toPack*/) {} 594 595 template <typename FirstType, typename... UnpackTypes> packVariant(std::span<PerUnpack> toPack,std::string_view key,FirstType && first,UnpackTypes &&...in)596 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 597 FirstType&& first, UnpackTypes&&... in) 598 { 599 if (toPack.empty()) 600 { 601 return; 602 } 603 toPack[0].key = key; 604 toPack[0].value = &first; 605 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 606 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 607 } 608 609 template <typename FirstType, typename... UnpackTypes> readJsonObject(nlohmann::json::object_t & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)610 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res, 611 std::string_view key, FirstType&& first, 612 UnpackTypes&&... in) 613 { 614 const std::size_t n = sizeof...(UnpackTypes) + 2; 615 std::array<PerUnpack, n / 2> toUnpack2; 616 packVariant(toUnpack2, key, std::forward<FirstType>(first), 617 std::forward<UnpackTypes&&>(in)...); 618 return readJsonHelperObject(jsonRequest, res, toUnpack2); 619 } 620 621 template <typename FirstType, typename... UnpackTypes> readJson(nlohmann::json & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)622 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 623 std::string_view key, FirstType&& first, UnpackTypes&&... in) 624 { 625 nlohmann::json::object_t* obj = 626 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 627 if (obj == nullptr) 628 { 629 BMCWEB_LOG_DEBUG("Json value is not an object"); 630 messages::unrecognizedRequestBody(res); 631 return false; 632 } 633 return readJsonObject(*obj, res, key, std::forward<FirstType>(first), 634 std::forward<UnpackTypes&&>(in)...); 635 } 636 readJsonPatchHelper(const crow::Request & req,crow::Response & res)637 inline std::optional<nlohmann::json::object_t> readJsonPatchHelper( 638 const crow::Request& req, crow::Response& res) 639 { 640 nlohmann::json jsonRequest; 641 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 642 { 643 BMCWEB_LOG_DEBUG("Json value not readable"); 644 return std::nullopt; 645 } 646 nlohmann::json::object_t* object = 647 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 648 if (object == nullptr || object->empty()) 649 { 650 BMCWEB_LOG_DEBUG("Json value is empty"); 651 messages::emptyJSON(res); 652 return std::nullopt; 653 } 654 std::erase_if(*object, 655 [](const std::pair<std::string, nlohmann::json>& item) { 656 return item.first.starts_with("@odata."); 657 }); 658 if (object->empty()) 659 { 660 // If the update request only contains OData annotations, the service 661 // should return the HTTP 400 Bad Request status code with the 662 // NoOperation message from the Base Message Registry, ... 663 messages::noOperation(res); 664 return std::nullopt; 665 } 666 667 return {std::move(*object)}; 668 } 669 findNestedKey(std::string_view key,const nlohmann::json & value)670 inline const nlohmann::json* findNestedKey(std::string_view key, 671 const nlohmann::json& value) 672 { 673 size_t keysplitIndex = key.find('/'); 674 std::string_view leftover; 675 nlohmann::json::const_iterator it; 676 if (keysplitIndex != std::string_view::npos) 677 { 678 const nlohmann::json::object_t* obj = 679 value.get_ptr<const nlohmann::json::object_t*>(); 680 if (obj == nullptr || obj->empty()) 681 { 682 BMCWEB_LOG_ERROR("Requested key wasn't an object"); 683 return nullptr; 684 } 685 686 leftover = key.substr(keysplitIndex + 1); 687 std::string_view keypart = key.substr(0, keysplitIndex); 688 it = value.find(keypart); 689 if (it == value.end()) 690 { 691 // Entry didn't have key 692 return nullptr; 693 } 694 return findNestedKey(leftover, it.value()); 695 } 696 697 it = value.find(key); 698 if (it == value.end()) 699 { 700 return nullptr; 701 } 702 return &*it; 703 } 704 705 template <typename... UnpackTypes> readJsonPatch(const crow::Request & req,crow::Response & res,std::string_view key,UnpackTypes &&...in)706 bool readJsonPatch(const crow::Request& req, crow::Response& res, 707 std::string_view key, UnpackTypes&&... in) 708 { 709 std::optional<nlohmann::json::object_t> jsonRequest = 710 readJsonPatchHelper(req, res); 711 if (!jsonRequest) 712 { 713 return false; 714 } 715 if (jsonRequest->empty()) 716 { 717 messages::emptyJSON(res); 718 return false; 719 } 720 721 return readJsonObject(*jsonRequest, res, key, 722 std::forward<UnpackTypes&&>(in)...); 723 } 724 725 template <typename... UnpackTypes> readJsonAction(const crow::Request & req,crow::Response & res,const char * key,UnpackTypes &&...in)726 bool readJsonAction(const crow::Request& req, crow::Response& res, 727 const char* key, UnpackTypes&&... in) 728 { 729 nlohmann::json jsonRequest; 730 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 731 { 732 BMCWEB_LOG_DEBUG("Json value not readable"); 733 return false; 734 } 735 nlohmann::json::object_t* object = 736 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 737 if (object == nullptr) 738 { 739 BMCWEB_LOG_DEBUG("Json value is empty"); 740 messages::emptyJSON(res); 741 return false; 742 } 743 return readJsonObject(*object, res, key, 744 std::forward<UnpackTypes&&>(in)...); 745 } 746 747 // Determines if two json objects are less, based on the presence of the 748 // @odata.id key objectKeyCmp(std::string_view key,const nlohmann::json & a,const nlohmann::json & b)749 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a, 750 const nlohmann::json& b) 751 { 752 using object_t = nlohmann::json::object_t; 753 const object_t* aObj = a.get_ptr<const object_t*>(); 754 const object_t* bObj = b.get_ptr<const object_t*>(); 755 756 if (aObj == nullptr) 757 { 758 if (bObj == nullptr) 759 { 760 return 0; 761 } 762 return -1; 763 } 764 if (bObj == nullptr) 765 { 766 return 1; 767 } 768 object_t::const_iterator aIt = aObj->find(key); 769 object_t::const_iterator bIt = bObj->find(key); 770 // If either object doesn't have the key, they get "sorted" to the 771 // beginning. 772 if (aIt == aObj->end()) 773 { 774 if (bIt == bObj->end()) 775 { 776 return 0; 777 } 778 return -1; 779 } 780 if (bIt == bObj->end()) 781 { 782 return 1; 783 } 784 const nlohmann::json::string_t* nameA = 785 aIt->second.get_ptr<const std::string*>(); 786 const nlohmann::json::string_t* nameB = 787 bIt->second.get_ptr<const std::string*>(); 788 // If either object doesn't have a string as the key, they get "sorted" to 789 // the beginning. 790 if (nameA == nullptr) 791 { 792 if (nameB == nullptr) 793 { 794 return 0; 795 } 796 return -1; 797 } 798 if (nameB == nullptr) 799 { 800 return 1; 801 } 802 if (key != "@odata.id") 803 { 804 return alphanumComp(*nameA, *nameB); 805 } 806 807 boost::system::result<boost::urls::url_view> aUrl = 808 boost::urls::parse_relative_ref(*nameA); 809 boost::system::result<boost::urls::url_view> bUrl = 810 boost::urls::parse_relative_ref(*nameB); 811 if (!aUrl) 812 { 813 if (!bUrl) 814 { 815 return 0; 816 } 817 return -1; 818 } 819 if (!bUrl) 820 { 821 return 1; 822 } 823 824 auto segmentsAIt = aUrl->segments().begin(); 825 auto segmentsBIt = bUrl->segments().begin(); 826 827 while (true) 828 { 829 if (segmentsAIt == aUrl->segments().end()) 830 { 831 if (segmentsBIt == bUrl->segments().end()) 832 { 833 return 0; 834 } 835 return -1; 836 } 837 if (segmentsBIt == bUrl->segments().end()) 838 { 839 return 1; 840 } 841 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 842 if (res != 0) 843 { 844 return res; 845 } 846 847 segmentsAIt++; 848 segmentsBIt++; 849 } 850 return 0; 851 }; 852 853 // kept for backward compatibility odataObjectCmp(const nlohmann::json & left,const nlohmann::json & right)854 inline int odataObjectCmp(const nlohmann::json& left, 855 const nlohmann::json& right) 856 { 857 return objectKeyCmp("@odata.id", left, right); 858 } 859 860 struct ODataObjectLess 861 { 862 std::string_view key; 863 ODataObjectLessredfish::json_util::ODataObjectLess864 explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {} 865 operator ()redfish::json_util::ODataObjectLess866 bool operator()(const nlohmann::json& left, 867 const nlohmann::json& right) const 868 { 869 return objectKeyCmp(key, left, right) < 0; 870 } 871 }; 872 873 // Sort the JSON array by |element[key]|. 874 // Elements without |key| or type of |element[key]| is not string are smaller 875 // those whose |element[key]| is string. sortJsonArrayByKey(nlohmann::json::array_t & array,std::string_view key)876 inline void sortJsonArrayByKey(nlohmann::json::array_t& array, 877 std::string_view key) 878 { 879 std::ranges::sort(array, ODataObjectLess(key)); 880 } 881 882 // Sort the JSON array by |element[key]|. 883 // Elements without |key| or type of |element[key]| is not string are smaller 884 // those whose |element[key]| is string. sortJsonArrayByOData(nlohmann::json::array_t & array)885 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 886 { 887 std::ranges::sort(array, ODataObjectLess("@odata.id")); 888 } 889 890 // Returns the estimated size of the JSON value 891 // The implementation walks through every key and every value, accumulates the 892 // total size of keys and values. 893 // Ideally, we should use a custom allocator that nlohmann JSON supports. 894 895 // Assumption made: 896 // 1. number: 8 characters 897 // 2. boolean: 5 characters (False) 898 // 3. string: len(str) + 2 characters (quote) 899 // 4. bytes: len(bytes) characters 900 // 5. null: 4 characters (null) 901 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 902 903 } // namespace json_util 904 } // namespace redfish 905