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