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