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