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 [[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> 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> 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> 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, res.jsonValue, key); 279 return false; 280 } 281 if (jsonValue.size() != value.size()) 282 { 283 messages::propertyValueTypeError(res, 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, 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> 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 494 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 495 std::span<PerUnpack> toUnpack); 496 497 inline bool readJsonHelperObject(nlohmann::json::object_t& obj, 498 crow::Response& res, 499 std::span<PerUnpack> toUnpack) 500 { 501 bool result = true; 502 for (auto& item : obj) 503 { 504 size_t unpackIndex = 0; 505 for (; unpackIndex < toUnpack.size(); unpackIndex++) 506 { 507 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 508 std::string_view key = unpackSpec.key; 509 size_t keysplitIndex = key.find('/'); 510 std::string_view leftover; 511 if (keysplitIndex != std::string_view::npos) 512 { 513 leftover = key.substr(keysplitIndex + 1); 514 key = key.substr(0, keysplitIndex); 515 } 516 517 if (key != item.first || unpackSpec.complete) 518 { 519 continue; 520 } 521 522 // Sublevel key 523 if (!leftover.empty()) 524 { 525 // Include the slash in the key so we can compare later 526 key = unpackSpec.key.substr(0, keysplitIndex + 1); 527 nlohmann::json j; 528 result = details::unpackValue<nlohmann::json>(item.second, key, 529 res, j) && 530 result; 531 if (!result) 532 { 533 return result; 534 } 535 536 std::vector<PerUnpack> nextLevel; 537 for (PerUnpack& p : toUnpack) 538 { 539 if (!p.key.starts_with(key)) 540 { 541 continue; 542 } 543 std::string_view thisLeftover = p.key.substr(key.size()); 544 nextLevel.push_back({thisLeftover, p.value, false}); 545 p.complete = true; 546 } 547 548 result = readJsonHelper(j, res, nextLevel) && result; 549 break; 550 } 551 552 result = 553 std::visit( 554 [&item, &unpackSpec, &res](auto& val) { 555 using ContainedT = 556 std::remove_pointer_t<std::decay_t<decltype(val)>>; 557 return details::unpackValue<ContainedT>( 558 item.second, unpackSpec.key, res, *val); 559 }, 560 unpackSpec.value) && 561 result; 562 563 unpackSpec.complete = true; 564 break; 565 } 566 567 if (unpackIndex == toUnpack.size()) 568 { 569 messages::propertyUnknown(res, item.first); 570 result = false; 571 } 572 } 573 574 for (PerUnpack& perUnpack : toUnpack) 575 { 576 if (!perUnpack.complete) 577 { 578 bool isOptional = std::visit( 579 [](auto& val) { 580 using ContainedType = 581 std::remove_pointer_t<std::decay_t<decltype(val)>>; 582 return details::IsOptional<ContainedType>::value; 583 }, 584 perUnpack.value); 585 if (isOptional) 586 { 587 continue; 588 } 589 messages::propertyMissing(res, perUnpack.key); 590 result = false; 591 } 592 } 593 return result; 594 } 595 596 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 597 std::span<PerUnpack> toUnpack) 598 { 599 nlohmann::json::object_t* obj = 600 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 601 if (obj == nullptr) 602 { 603 BMCWEB_LOG_DEBUG("Json value is not an object"); 604 messages::unrecognizedRequestBody(res); 605 return false; 606 } 607 return readJsonHelperObject(*obj, res, toUnpack); 608 } 609 610 inline void packVariant(std::span<PerUnpack> /*toPack*/) {} 611 612 template <typename FirstType, typename... UnpackTypes> 613 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 614 FirstType&& first, UnpackTypes&&... in) 615 { 616 if (toPack.empty()) 617 { 618 return; 619 } 620 toPack[0].key = key; 621 toPack[0].value = &first; 622 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 623 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 624 } 625 626 template <typename FirstType, typename... UnpackTypes> 627 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res, 628 std::string_view key, FirstType&& first, 629 UnpackTypes&&... in) 630 { 631 const std::size_t n = sizeof...(UnpackTypes) + 2; 632 std::array<PerUnpack, n / 2> toUnpack2; 633 packVariant(toUnpack2, key, std::forward<FirstType>(first), 634 std::forward<UnpackTypes&&>(in)...); 635 return readJsonHelperObject(jsonRequest, res, toUnpack2); 636 } 637 638 template <typename FirstType, typename... UnpackTypes> 639 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 640 std::string_view key, FirstType&& first, UnpackTypes&&... in) 641 { 642 nlohmann::json::object_t* obj = 643 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 644 if (obj == nullptr) 645 { 646 BMCWEB_LOG_DEBUG("Json value is not an object"); 647 messages::unrecognizedRequestBody(res); 648 return false; 649 } 650 return readJsonObject(*obj, res, key, std::forward<FirstType>(first), 651 std::forward<UnpackTypes&&>(in)...); 652 } 653 654 inline std::optional<nlohmann::json::object_t> readJsonPatchHelper( 655 const crow::Request& req, crow::Response& res) 656 { 657 nlohmann::json jsonRequest; 658 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 659 { 660 BMCWEB_LOG_DEBUG("Json value not readable"); 661 return std::nullopt; 662 } 663 nlohmann::json::object_t* object = 664 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 665 if (object == nullptr || object->empty()) 666 { 667 BMCWEB_LOG_DEBUG("Json value is empty"); 668 messages::emptyJSON(res); 669 return std::nullopt; 670 } 671 std::erase_if(*object, 672 [](const std::pair<std::string, nlohmann::json>& item) { 673 return item.first.starts_with("@odata."); 674 }); 675 if (object->empty()) 676 { 677 // If the update request only contains OData annotations, the service 678 // should return the HTTP 400 Bad Request status code with the 679 // NoOperation message from the Base Message Registry, ... 680 messages::noOperation(res); 681 return std::nullopt; 682 } 683 684 return {std::move(*object)}; 685 } 686 687 inline const nlohmann::json* findNestedKey(std::string_view key, 688 const nlohmann::json& value) 689 { 690 size_t keysplitIndex = key.find('/'); 691 std::string_view leftover; 692 nlohmann::json::const_iterator it; 693 if (keysplitIndex != std::string_view::npos) 694 { 695 const nlohmann::json::object_t* obj = 696 value.get_ptr<const nlohmann::json::object_t*>(); 697 if (obj == nullptr || obj->empty()) 698 { 699 BMCWEB_LOG_ERROR("Requested key wasn't an object"); 700 return nullptr; 701 } 702 703 leftover = key.substr(keysplitIndex + 1); 704 std::string_view keypart = key.substr(0, keysplitIndex); 705 it = value.find(keypart); 706 if (it == value.end()) 707 { 708 // Entry didn't have key 709 return nullptr; 710 } 711 return findNestedKey(leftover, it.value()); 712 } 713 714 it = value.find(key); 715 if (it == value.end()) 716 { 717 return nullptr; 718 } 719 return &*it; 720 } 721 722 template <typename... UnpackTypes> 723 bool readJsonPatch(const crow::Request& req, crow::Response& res, 724 std::string_view key, UnpackTypes&&... in) 725 { 726 std::optional<nlohmann::json::object_t> jsonRequest = 727 readJsonPatchHelper(req, res); 728 if (!jsonRequest) 729 { 730 return false; 731 } 732 if (jsonRequest->empty()) 733 { 734 messages::emptyJSON(res); 735 return false; 736 } 737 738 return readJsonObject(*jsonRequest, res, key, 739 std::forward<UnpackTypes&&>(in)...); 740 } 741 742 template <typename... UnpackTypes> 743 bool readJsonAction(const crow::Request& req, crow::Response& res, 744 const char* key, UnpackTypes&&... in) 745 { 746 nlohmann::json jsonRequest; 747 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 748 { 749 BMCWEB_LOG_DEBUG("Json value not readable"); 750 return false; 751 } 752 nlohmann::json::object_t* object = 753 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 754 if (object == nullptr) 755 { 756 BMCWEB_LOG_DEBUG("Json value is empty"); 757 messages::emptyJSON(res); 758 return false; 759 } 760 return readJsonObject(*object, res, key, 761 std::forward<UnpackTypes&&>(in)...); 762 } 763 764 // Determines if two json objects are less, based on the presence of the 765 // @odata.id key 766 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a, 767 const nlohmann::json& b) 768 { 769 using object_t = nlohmann::json::object_t; 770 const object_t* aObj = a.get_ptr<const object_t*>(); 771 const object_t* bObj = b.get_ptr<const object_t*>(); 772 773 if (aObj == nullptr) 774 { 775 if (bObj == nullptr) 776 { 777 return 0; 778 } 779 return -1; 780 } 781 if (bObj == nullptr) 782 { 783 return 1; 784 } 785 object_t::const_iterator aIt = aObj->find(key); 786 object_t::const_iterator bIt = bObj->find(key); 787 // If either object doesn't have the key, they get "sorted" to the 788 // beginning. 789 if (aIt == aObj->end()) 790 { 791 if (bIt == bObj->end()) 792 { 793 return 0; 794 } 795 return -1; 796 } 797 if (bIt == bObj->end()) 798 { 799 return 1; 800 } 801 const nlohmann::json::string_t* nameA = 802 aIt->second.get_ptr<const std::string*>(); 803 const nlohmann::json::string_t* nameB = 804 bIt->second.get_ptr<const std::string*>(); 805 // If either object doesn't have a string as the key, they get "sorted" to 806 // the beginning. 807 if (nameA == nullptr) 808 { 809 if (nameB == nullptr) 810 { 811 return 0; 812 } 813 return -1; 814 } 815 if (nameB == nullptr) 816 { 817 return 1; 818 } 819 if (key != "@odata.id") 820 { 821 return alphanumComp(*nameA, *nameB); 822 } 823 824 boost::system::result<boost::urls::url_view> aUrl = 825 boost::urls::parse_relative_ref(*nameA); 826 boost::system::result<boost::urls::url_view> bUrl = 827 boost::urls::parse_relative_ref(*nameB); 828 if (!aUrl) 829 { 830 if (!bUrl) 831 { 832 return 0; 833 } 834 return -1; 835 } 836 if (!bUrl) 837 { 838 return 1; 839 } 840 841 auto segmentsAIt = aUrl->segments().begin(); 842 auto segmentsBIt = bUrl->segments().begin(); 843 844 while (true) 845 { 846 if (segmentsAIt == aUrl->segments().end()) 847 { 848 if (segmentsBIt == bUrl->segments().end()) 849 { 850 return 0; 851 } 852 return -1; 853 } 854 if (segmentsBIt == bUrl->segments().end()) 855 { 856 return 1; 857 } 858 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 859 if (res != 0) 860 { 861 return res; 862 } 863 864 segmentsAIt++; 865 segmentsBIt++; 866 } 867 return 0; 868 }; 869 870 // kept for backward compatibility 871 inline int odataObjectCmp(const nlohmann::json& left, 872 const nlohmann::json& right) 873 { 874 return objectKeyCmp("@odata.id", left, right); 875 } 876 877 struct ODataObjectLess 878 { 879 std::string_view key; 880 881 explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {} 882 883 bool operator()(const nlohmann::json& left, 884 const nlohmann::json& right) const 885 { 886 return objectKeyCmp(key, left, right) < 0; 887 } 888 }; 889 890 // Sort the JSON array by |element[key]|. 891 // Elements without |key| or type of |element[key]| is not string are smaller 892 // those whose |element[key]| is string. 893 inline void sortJsonArrayByKey(nlohmann::json::array_t& array, 894 std::string_view key) 895 { 896 std::ranges::sort(array, ODataObjectLess(key)); 897 } 898 899 // Sort the JSON array by |element[key]|. 900 // Elements without |key| or type of |element[key]| is not string are smaller 901 // those whose |element[key]| is string. 902 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 903 { 904 std::ranges::sort(array, ODataObjectLess("@odata.id")); 905 } 906 907 // Returns the estimated size of the JSON value 908 // The implementation walks through every key and every value, accumulates the 909 // total size of keys and values. 910 // Ideally, we should use a custom allocator that nlohmann JSON supports. 911 912 // Assumption made: 913 // 1. number: 8 characters 914 // 2. boolean: 5 characters (False) 915 // 3. string: len(str) + 2 characters (quote) 916 // 4. bytes: len(bytes) characters 917 // 5. null: 4 characters (null) 918 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 919 920 } // namespace json_util 921 } // namespace redfish 922