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