xref: /openbmc/bmcweb/redfish-core/include/utils/json_utils.hpp (revision 9a56031931c498ea44abf006540b9a243bbd0574)
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