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