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>
checkRange(const FromType & from,std::string_view key)109 bool checkRange(const FromType& from [[maybe_unused]],
110 std::string_view key [[maybe_unused]])
111 {
112 if constexpr (std::is_floating_point_v<ToType>)
113 {
114 if (std::isnan(from))
115 {
116 BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
117 return false;
118 }
119 }
120 if constexpr (std::numeric_limits<ToType>::max() <
121 std::numeric_limits<FromType>::max())
122 {
123 if (from > std::numeric_limits<ToType>::max())
124 {
125 BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key,
126 std::numeric_limits<FromType>::max());
127 return false;
128 }
129 }
130 if constexpr (std::numeric_limits<ToType>::lowest() >
131 std::numeric_limits<FromType>::lowest())
132 {
133 if (from < std::numeric_limits<ToType>::lowest())
134 {
135 BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key,
136 std::numeric_limits<FromType>::lowest());
137 return false;
138 }
139 }
140
141 return true;
142 }
143
144 template <typename Type>
145 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
146 std::string_view key, Type& value);
147
148 template <std::size_t Index = 0, typename... Args>
unpackValueVariant(nlohmann::json & j,std::string_view key,std::variant<Args...> & v)149 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key,
150 std::variant<Args...>& v)
151 {
152 if constexpr (Index < std::variant_size_v<std::variant<Args...>>)
153 {
154 std::variant_alternative_t<Index, std::variant<Args...>> type{};
155 UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type);
156 if (unpack == UnpackErrorCode::success)
157 {
158 v = std::move(type);
159 return unpack;
160 }
161
162 return unpackValueVariant<Index + 1, Args...>(j, key, v);
163 }
164 return UnpackErrorCode::invalidType;
165 }
166
167 template <typename Type>
unpackValueWithErrorCode(nlohmann::json & jsonValue,std::string_view key,Type & value)168 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
169 std::string_view key, Type& value)
170 {
171 UnpackErrorCode ret = UnpackErrorCode::success;
172
173 if constexpr (std::is_floating_point_v<Type>)
174 {
175 double helper = 0;
176 double* jsonPtr = jsonValue.get_ptr<double*>();
177
178 if (jsonPtr == nullptr)
179 {
180 int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
181 if (intPtr != nullptr)
182 {
183 helper = static_cast<double>(*intPtr);
184 jsonPtr = &helper;
185 }
186 }
187 if (jsonPtr == nullptr)
188 {
189 return UnpackErrorCode::invalidType;
190 }
191 if (!checkRange<Type>(*jsonPtr, key))
192 {
193 return UnpackErrorCode::outOfRange;
194 }
195 value = static_cast<Type>(*jsonPtr);
196 }
197
198 else if constexpr (std::is_signed_v<Type>)
199 {
200 int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
201 if (jsonPtr == nullptr)
202 {
203 return UnpackErrorCode::invalidType;
204 }
205 if (!checkRange<Type>(*jsonPtr, key))
206 {
207 return UnpackErrorCode::outOfRange;
208 }
209 value = static_cast<Type>(*jsonPtr);
210 }
211
212 else if constexpr ((std::is_unsigned_v<Type>)&&(
213 !std::is_same_v<bool, Type>))
214 {
215 uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
216 if (jsonPtr == nullptr)
217 {
218 return UnpackErrorCode::invalidType;
219 }
220 if (!checkRange<Type>(*jsonPtr, key))
221 {
222 return UnpackErrorCode::outOfRange;
223 }
224 value = static_cast<Type>(*jsonPtr);
225 }
226
227 else if constexpr (std::is_same_v<nlohmann::json, Type>)
228 {
229 value = std::move(jsonValue);
230 }
231 else if constexpr (std::is_same_v<std::nullptr_t, Type>)
232 {
233 if (!jsonValue.is_null())
234 {
235 return UnpackErrorCode::invalidType;
236 }
237 }
238 else if constexpr (IsVector<Type>::value)
239 {
240 nlohmann::json::object_t* obj =
241 jsonValue.get_ptr<nlohmann::json::object_t*>();
242 if (obj == nullptr)
243 {
244 return UnpackErrorCode::invalidType;
245 }
246
247 for (const auto& val : *obj)
248 {
249 value.emplace_back();
250 ret = unpackValueWithErrorCode<typename Type::value_type>(
251 val, key, value.back()) &&
252 ret;
253 }
254 }
255 else
256 {
257 using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
258 JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
259 if (jsonPtr == nullptr)
260 {
261 BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
262 jsonValue.type_name());
263 return UnpackErrorCode::invalidType;
264 }
265 value = std::move(*jsonPtr);
266 }
267 return ret;
268 }
269
270 template <typename Type>
unpackValue(nlohmann::json & jsonValue,std::string_view key,crow::Response & res,Type & value)271 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
272 crow::Response& res, Type& value)
273 {
274 bool ret = true;
275
276 if constexpr (IsOptional<Type>::value)
277 {
278 value.emplace();
279 ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
280 *value) &&
281 ret;
282 }
283 else if constexpr (IsStdArray<Type>::value)
284 {
285 nlohmann::json::array_t* arr =
286 jsonValue.get_ptr<nlohmann::json::array_t*>();
287 if (arr == nullptr)
288 {
289 messages::propertyValueTypeError(res, res.jsonValue, key);
290 return false;
291 }
292 if (jsonValue.size() != value.size())
293 {
294 messages::propertyValueTypeError(res, res.jsonValue, key);
295 return false;
296 }
297 size_t index = 0;
298 for (auto& val : *arr)
299 {
300 ret = unpackValue<typename Type::value_type>(val, key, res,
301 value[index++]) &&
302 ret;
303 }
304 }
305 else if constexpr (IsVector<Type>::value)
306 {
307 nlohmann::json::array_t* arr =
308 jsonValue.get_ptr<nlohmann::json::array_t*>();
309 if (arr == nullptr)
310 {
311 messages::propertyValueTypeError(res, res.jsonValue, key);
312 return false;
313 }
314
315 for (auto& val : *arr)
316 {
317 value.emplace_back();
318 ret = unpackValue<typename Type::value_type>(val, key, res,
319 value.back()) &&
320 ret;
321 }
322 }
323 else if constexpr (IsVariant<Type>::value)
324 {
325 UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
326 if (ec != UnpackErrorCode::success)
327 {
328 if (ec == UnpackErrorCode::invalidType)
329 {
330 messages::propertyValueTypeError(res, jsonValue, key);
331 }
332 else if (ec == UnpackErrorCode::outOfRange)
333 {
334 messages::propertyValueNotInList(res, jsonValue, key);
335 }
336 return false;
337 }
338 }
339 else
340 {
341 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
342 if (ec != UnpackErrorCode::success)
343 {
344 if (ec == UnpackErrorCode::invalidType)
345 {
346 messages::propertyValueTypeError(res, jsonValue, key);
347 }
348 else if (ec == UnpackErrorCode::outOfRange)
349 {
350 messages::propertyValueNotInList(res, jsonValue, key);
351 }
352 return false;
353 }
354 }
355
356 return ret;
357 }
358
359 template <typename Type>
unpackValue(nlohmann::json & jsonValue,std::string_view key,Type & value)360 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
361 {
362 bool ret = true;
363 if constexpr (IsOptional<Type>::value)
364 {
365 value.emplace();
366 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
367 ret;
368 }
369 else if constexpr (IsStdArray<Type>::value)
370 {
371 nlohmann::json::array_t* arr =
372 jsonValue.get_ptr<nlohmann::json::array_t*>();
373 if (arr == nullptr)
374 {
375 return false;
376 }
377 if (jsonValue.size() != value.size())
378 {
379 return false;
380 }
381 size_t index = 0;
382 for (const auto& val : *arr)
383 {
384 ret = unpackValue<typename Type::value_type>(val, key,
385 value[index++]) &&
386 ret;
387 }
388 }
389 else if constexpr (IsVector<Type>::value)
390 {
391 nlohmann::json::array_t* arr =
392 jsonValue.get_ptr<nlohmann::json::array_t*>();
393 if (arr == nullptr)
394 {
395 return false;
396 }
397
398 for (const auto& val : *arr)
399 {
400 value.emplace_back();
401 ret = unpackValue<typename Type::value_type>(val, key,
402 value.back()) &&
403 ret;
404 }
405 }
406 else
407 {
408 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
409 if (ec != UnpackErrorCode::success)
410 {
411 return false;
412 }
413 }
414
415 return ret;
416 }
417 } // namespace details
418
419 // clang-format off
420 using UnpackVariant = std::variant<
421 uint8_t*,
422 uint16_t*,
423 int16_t*,
424 uint32_t*,
425 int32_t*,
426 uint64_t*,
427 int64_t*,
428 bool*,
429 double*,
430 std::string*,
431 nlohmann::json::object_t*,
432 std::variant<std::string, std::nullptr_t>*,
433 std::variant<uint8_t, std::nullptr_t>*,
434 std::variant<int16_t, std::nullptr_t>*,
435 std::variant<uint16_t, std::nullptr_t>*,
436 std::variant<int32_t, std::nullptr_t>*,
437 std::variant<uint32_t, std::nullptr_t>*,
438 std::variant<int64_t, std::nullptr_t>*,
439 std::variant<uint64_t, std::nullptr_t>*,
440 std::variant<double, std::nullptr_t>*,
441 std::variant<bool, std::nullptr_t>*,
442 std::vector<uint8_t>*,
443 std::vector<uint16_t>*,
444 std::vector<int16_t>*,
445 std::vector<uint32_t>*,
446 std::vector<int32_t>*,
447 std::vector<uint64_t>*,
448 std::vector<int64_t>*,
449 //std::vector<bool>*,
450 std::vector<double>*,
451 std::vector<std::string>*,
452 std::vector<nlohmann::json::object_t>*,
453 std::optional<uint8_t>*,
454 std::optional<uint16_t>*,
455 std::optional<int16_t>*,
456 std::optional<uint32_t>*,
457 std::optional<int32_t>*,
458 std::optional<uint64_t>*,
459 std::optional<int64_t>*,
460 std::optional<bool>*,
461 std::optional<double>*,
462 std::optional<std::string>*,
463 std::optional<nlohmann::json::object_t>*,
464 std::optional<std::vector<uint8_t>>*,
465 std::optional<std::vector<uint16_t>>*,
466 std::optional<std::vector<int16_t>>*,
467 std::optional<std::vector<uint32_t>>*,
468 std::optional<std::vector<int32_t>>*,
469 std::optional<std::vector<uint64_t>>*,
470 std::optional<std::vector<int64_t>>*,
471 //std::optional<std::vector<bool>>*,
472 std::optional<std::vector<double>>*,
473 std::optional<std::vector<std::string>>*,
474 std::optional<std::vector<nlohmann::json::object_t>>*,
475 std::optional<std::variant<std::string, std::nullptr_t>>*,
476 std::optional<std::variant<uint8_t, std::nullptr_t>>*,
477 std::optional<std::variant<int16_t, std::nullptr_t>>*,
478 std::optional<std::variant<uint16_t, std::nullptr_t>>*,
479 std::optional<std::variant<int32_t, std::nullptr_t>>*,
480 std::optional<std::variant<uint32_t, std::nullptr_t>>*,
481 std::optional<std::variant<int64_t, std::nullptr_t>>*,
482 std::optional<std::variant<uint64_t, std::nullptr_t>>*,
483 std::optional<std::variant<double, std::nullptr_t>>*,
484 std::optional<std::variant<bool, std::nullptr_t>>*,
485 std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
486 std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
487
488 // Note, these types are kept for historical completeness, but should not be used,
489 // As they do not provide object type safety. Instead, rely on nlohmann::json::object_t
490 // Will be removed Q2 2025
491 nlohmann::json*,
492 std::optional<std::vector<nlohmann::json>>*,
493 std::vector<nlohmann::json>*,
494 std::optional<nlohmann::json>*
495 >;
496 // clang-format on
497
498 struct PerUnpack
499 {
500 std::string_view key;
501 UnpackVariant value;
502 bool complete = false;
503 };
504
505 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
506 std::span<PerUnpack> toUnpack);
507
readJsonHelperObject(nlohmann::json::object_t & obj,crow::Response & res,std::span<PerUnpack> toUnpack)508 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
509 crow::Response& res,
510 std::span<PerUnpack> toUnpack)
511 {
512 bool result = true;
513 for (auto& item : obj)
514 {
515 size_t unpackIndex = 0;
516 for (; unpackIndex < toUnpack.size(); unpackIndex++)
517 {
518 PerUnpack& unpackSpec = toUnpack[unpackIndex];
519 std::string_view key = unpackSpec.key;
520 size_t keysplitIndex = key.find('/');
521 std::string_view leftover;
522 if (keysplitIndex != std::string_view::npos)
523 {
524 leftover = key.substr(keysplitIndex + 1);
525 key = key.substr(0, keysplitIndex);
526 }
527
528 if (key != item.first || unpackSpec.complete)
529 {
530 continue;
531 }
532
533 // Sublevel key
534 if (!leftover.empty())
535 {
536 // Include the slash in the key so we can compare later
537 key = unpackSpec.key.substr(0, keysplitIndex + 1);
538 nlohmann::json j;
539 result = details::unpackValue<nlohmann::json>(item.second, key,
540 res, j) &&
541 result;
542 if (!result)
543 {
544 return result;
545 }
546
547 std::vector<PerUnpack> nextLevel;
548 for (PerUnpack& p : toUnpack)
549 {
550 if (!p.key.starts_with(key))
551 {
552 continue;
553 }
554 std::string_view thisLeftover = p.key.substr(key.size());
555 nextLevel.push_back({thisLeftover, p.value, false});
556 p.complete = true;
557 }
558
559 result = readJsonHelper(j, res, nextLevel) && result;
560 break;
561 }
562
563 result = 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
readJsonHelper(nlohmann::json & jsonRequest,crow::Response & res,std::span<PerUnpack> toUnpack)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
packVariant(std::span<PerUnpack>)620 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
621
622 template <typename FirstType, typename... UnpackTypes>
packVariant(std::span<PerUnpack> toPack,std::string_view key,FirstType & first,UnpackTypes &&...in)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>
readJsonObject(nlohmann::json::object_t & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)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, first, std::forward<UnpackTypes&&>(in)...);
644 return readJsonHelperObject(jsonRequest, res, toUnpack2);
645 }
646
647 template <typename FirstType, typename... UnpackTypes>
readJson(nlohmann::json & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)648 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
649 std::string_view key, FirstType&& first, UnpackTypes&&... in)
650 {
651 nlohmann::json::object_t* obj =
652 jsonRequest.get_ptr<nlohmann::json::object_t*>();
653 if (obj == nullptr)
654 {
655 BMCWEB_LOG_DEBUG("Json value is not an object");
656 messages::unrecognizedRequestBody(res);
657 return false;
658 }
659 return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
660 std::forward<UnpackTypes&&>(in)...);
661 }
662
663 inline std::optional<nlohmann::json::object_t>
readJsonPatchHelper(const crow::Request & req,crow::Response & res)664 readJsonPatchHelper(const crow::Request& req, crow::Response& res)
665 {
666 nlohmann::json jsonRequest;
667 if (!json_util::processJsonFromRequest(res, req, jsonRequest))
668 {
669 BMCWEB_LOG_DEBUG("Json value not readable");
670 return std::nullopt;
671 }
672 nlohmann::json::object_t* object =
673 jsonRequest.get_ptr<nlohmann::json::object_t*>();
674 if (object == nullptr || object->empty())
675 {
676 BMCWEB_LOG_DEBUG("Json value is empty");
677 messages::emptyJSON(res);
678 return std::nullopt;
679 }
680 std::erase_if(*object,
681 [](const std::pair<std::string, nlohmann::json>& item) {
682 return item.first.starts_with("@odata.");
683 });
684 if (object->empty())
685 {
686 // If the update request only contains OData annotations, the service
687 // should return the HTTP 400 Bad Request status code with the
688 // NoOperation message from the Base Message Registry, ...
689 messages::noOperation(res);
690 return std::nullopt;
691 }
692
693 return {std::move(*object)};
694 }
695
696 template <typename... UnpackTypes>
readJsonPatch(const crow::Request & req,crow::Response & res,std::string_view key,UnpackTypes &&...in)697 bool readJsonPatch(const crow::Request& req, crow::Response& res,
698 std::string_view key, UnpackTypes&&... in)
699 {
700 std::optional<nlohmann::json::object_t> jsonRequest =
701 readJsonPatchHelper(req, res);
702 if (!jsonRequest)
703 {
704 return false;
705 }
706 if (jsonRequest->empty())
707 {
708 messages::emptyJSON(res);
709 return false;
710 }
711
712 return readJsonObject(*jsonRequest, res, key,
713 std::forward<UnpackTypes&&>(in)...);
714 }
715
716 template <typename... UnpackTypes>
readJsonAction(const crow::Request & req,crow::Response & res,const char * key,UnpackTypes &&...in)717 bool readJsonAction(const crow::Request& req, crow::Response& res,
718 const char* key, UnpackTypes&&... in)
719 {
720 nlohmann::json jsonRequest;
721 if (!json_util::processJsonFromRequest(res, req, jsonRequest))
722 {
723 BMCWEB_LOG_DEBUG("Json value not readable");
724 return false;
725 }
726 nlohmann::json::object_t* object =
727 jsonRequest.get_ptr<nlohmann::json::object_t*>();
728 if (object == nullptr)
729 {
730 BMCWEB_LOG_DEBUG("Json value is empty");
731 messages::emptyJSON(res);
732 return false;
733 }
734 return readJsonObject(*object, res, key,
735 std::forward<UnpackTypes&&>(in)...);
736 }
737
738 // Determines if two json objects are less, based on the presence of the
739 // @odata.id key
odataObjectCmp(const nlohmann::json & a,const nlohmann::json & b)740 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
741 {
742 using object_t = nlohmann::json::object_t;
743 const object_t* aObj = a.get_ptr<const object_t*>();
744 const object_t* bObj = b.get_ptr<const object_t*>();
745
746 if (aObj == nullptr)
747 {
748 if (bObj == nullptr)
749 {
750 return 0;
751 }
752 return -1;
753 }
754 if (bObj == nullptr)
755 {
756 return 1;
757 }
758 object_t::const_iterator aIt = aObj->find("@odata.id");
759 object_t::const_iterator bIt = bObj->find("@odata.id");
760 // If either object doesn't have the key, they get "sorted" to the end.
761 if (aIt == aObj->end())
762 {
763 if (bIt == bObj->end())
764 {
765 return 0;
766 }
767 return -1;
768 }
769 if (bIt == bObj->end())
770 {
771 return 1;
772 }
773 const nlohmann::json::string_t* nameA =
774 aIt->second.get_ptr<const std::string*>();
775 const nlohmann::json::string_t* nameB =
776 bIt->second.get_ptr<const std::string*>();
777 // If either object doesn't have a string as the key, they get "sorted" to
778 // the end.
779 if (nameA == nullptr)
780 {
781 if (nameB == nullptr)
782 {
783 return 0;
784 }
785 return -1;
786 }
787 if (nameB == nullptr)
788 {
789 return 1;
790 }
791 boost::urls::url_view aUrl(*nameA);
792 boost::urls::url_view bUrl(*nameB);
793 auto segmentsAIt = aUrl.segments().begin();
794 auto segmentsBIt = bUrl.segments().begin();
795
796 while (true)
797 {
798 if (segmentsAIt == aUrl.segments().end())
799 {
800 if (segmentsBIt == bUrl.segments().end())
801 {
802 return 0;
803 }
804 return -1;
805 }
806 if (segmentsBIt == bUrl.segments().end())
807 {
808 return 1;
809 }
810 int res = alphanumComp(*segmentsAIt, *segmentsBIt);
811 if (res != 0)
812 {
813 return res;
814 }
815
816 segmentsAIt++;
817 segmentsBIt++;
818 }
819 };
820
821 struct ODataObjectLess
822 {
operator ()redfish::json_util::ODataObjectLess823 bool operator()(const nlohmann::json& left,
824 const nlohmann::json& right) const
825 {
826 return odataObjectCmp(left, right) < 0;
827 }
828 };
829
830 // Sort the JSON array by |element[key]|.
831 // Elements without |key| or type of |element[key]| is not string are smaller
832 // those whose |element[key]| is string.
sortJsonArrayByOData(nlohmann::json::array_t & array)833 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
834 {
835 std::ranges::sort(array, ODataObjectLess());
836 }
837
838 // Returns the estimated size of the JSON value
839 // The implementation walks through every key and every value, accumulates the
840 // total size of keys and values.
841 // Ideally, we should use a custom allocator that nlohmann JSON supports.
842
843 // Assumption made:
844 // 1. number: 8 characters
845 // 2. boolean: 5 characters (False)
846 // 3. string: len(str) + 2 characters (quote)
847 // 4. bytes: len(bytes) characters
848 // 5. null: 4 characters (null)
849 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
850
851 } // namespace json_util
852 } // namespace redfish
853