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