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