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