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