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