1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "error_messages.hpp"
19 #include "http_connection.hpp"
20 #include "http_request.hpp"
21 #include "http_response.hpp"
22 #include "human_sort.hpp"
23 #include "logging.hpp"
24 
25 #include <nlohmann/json.hpp>
26 
27 #include <algorithm>
28 #include <array>
29 #include <cmath>
30 #include <cstddef>
31 #include <cstdint>
32 #include <limits>
33 #include <map>
34 #include <optional>
35 #include <ranges>
36 #include <span>
37 #include <string>
38 #include <string_view>
39 #include <type_traits>
40 #include <utility>
41 #include <variant>
42 #include <vector>
43 
44 // IWYU pragma: no_include <stdint.h>
45 // IWYU pragma: no_forward_declare crow::Request
46 
47 namespace redfish
48 {
49 
50 namespace json_util
51 {
52 
53 /**
54  * @brief Processes request to extract JSON from its body. If it fails, adds
55  *       MalformedJSON message to response and ends it.
56  *
57  * @param[io]  res       Response object
58  * @param[in]  req       Request object
59  * @param[out] reqJson   JSON object extracted from request's body
60  *
61  * @return true if JSON is valid, false when JSON is invalid and response has
62  *         been filled with message and ended.
63  */
64 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
65                             nlohmann::json& reqJson);
66 namespace details
67 {
68 
69 template <typename Type>
70 struct IsOptional : std::false_type
71 {};
72 
73 template <typename Type>
74 struct IsOptional<std::optional<Type>> : std::true_type
75 {};
76 
77 template <typename Type>
78 struct IsVector : std::false_type
79 {};
80 
81 template <typename Type>
82 struct IsVector<std::vector<Type>> : std::true_type
83 {};
84 
85 template <typename Type>
86 struct IsStdArray : std::false_type
87 {};
88 
89 template <typename Type, std::size_t size>
90 struct IsStdArray<std::array<Type, size>> : std::true_type
91 {};
92 
93 template <typename Type>
94 struct IsVariant : std::false_type
95 {};
96 
97 template <typename... Types>
98 struct IsVariant<std::variant<Types...>> : std::true_type
99 {};
100 
101 enum class UnpackErrorCode
102 {
103     success,
104     invalidType,
105     outOfRange
106 };
107 
108 template <typename ToType, typename FromType>
checkRange(const FromType & from,std::string_view key)109 bool checkRange(const FromType& from [[maybe_unused]],
110                 std::string_view key [[maybe_unused]])
111 {
112     if constexpr (std::is_floating_point_v<ToType>)
113     {
114         if (std::isnan(from))
115         {
116             BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
117             return false;
118         }
119     }
120     if constexpr (std::numeric_limits<ToType>::max() <
121                   std::numeric_limits<FromType>::max())
122     {
123         if (from > std::numeric_limits<ToType>::max())
124         {
125             BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key,
126                              std::numeric_limits<FromType>::max());
127             return false;
128         }
129     }
130     if constexpr (std::numeric_limits<ToType>::lowest() >
131                   std::numeric_limits<FromType>::lowest())
132     {
133         if (from < std::numeric_limits<ToType>::lowest())
134         {
135             BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key,
136                              std::numeric_limits<FromType>::lowest());
137             return false;
138         }
139     }
140 
141     return true;
142 }
143 
144 template <typename Type>
145 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
146                                          std::string_view key, Type& value);
147 
148 template <std::size_t Index = 0, typename... Args>
unpackValueVariant(nlohmann::json & j,std::string_view key,std::variant<Args...> & v)149 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key,
150                                    std::variant<Args...>& v)
151 {
152     if constexpr (Index < std::variant_size_v<std::variant<Args...>>)
153     {
154         std::variant_alternative_t<Index, std::variant<Args...>> type{};
155         UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type);
156         if (unpack == UnpackErrorCode::success)
157         {
158             v = std::move(type);
159             return unpack;
160         }
161 
162         return unpackValueVariant<Index + 1, Args...>(j, key, v);
163     }
164     return UnpackErrorCode::invalidType;
165 }
166 
167 template <typename Type>
unpackValueWithErrorCode(nlohmann::json & jsonValue,std::string_view key,Type & value)168 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
169                                          std::string_view key, Type& value)
170 {
171     UnpackErrorCode ret = UnpackErrorCode::success;
172 
173     if constexpr (std::is_floating_point_v<Type>)
174     {
175         double helper = 0;
176         double* jsonPtr = jsonValue.get_ptr<double*>();
177 
178         if (jsonPtr == nullptr)
179         {
180             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
181             if (intPtr != nullptr)
182             {
183                 helper = static_cast<double>(*intPtr);
184                 jsonPtr = &helper;
185             }
186         }
187         if (jsonPtr == nullptr)
188         {
189             return UnpackErrorCode::invalidType;
190         }
191         if (!checkRange<Type>(*jsonPtr, key))
192         {
193             return UnpackErrorCode::outOfRange;
194         }
195         value = static_cast<Type>(*jsonPtr);
196     }
197 
198     else if constexpr (std::is_signed_v<Type>)
199     {
200         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
201         if (jsonPtr == nullptr)
202         {
203             return UnpackErrorCode::invalidType;
204         }
205         if (!checkRange<Type>(*jsonPtr, key))
206         {
207             return UnpackErrorCode::outOfRange;
208         }
209         value = static_cast<Type>(*jsonPtr);
210     }
211 
212     else if constexpr ((std::is_unsigned_v<Type>)&&(
213                            !std::is_same_v<bool, Type>))
214     {
215         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
216         if (jsonPtr == nullptr)
217         {
218             return UnpackErrorCode::invalidType;
219         }
220         if (!checkRange<Type>(*jsonPtr, key))
221         {
222             return UnpackErrorCode::outOfRange;
223         }
224         value = static_cast<Type>(*jsonPtr);
225     }
226 
227     else if constexpr (std::is_same_v<nlohmann::json, Type>)
228     {
229         value = std::move(jsonValue);
230     }
231     else if constexpr (std::is_same_v<std::nullptr_t, Type>)
232     {
233         if (!jsonValue.is_null())
234         {
235             return UnpackErrorCode::invalidType;
236         }
237     }
238     else if constexpr (IsVector<Type>::value)
239     {
240         nlohmann::json::object_t* obj =
241             jsonValue.get_ptr<nlohmann::json::object_t*>();
242         if (obj == nullptr)
243         {
244             return UnpackErrorCode::invalidType;
245         }
246 
247         for (const auto& val : *obj)
248         {
249             value.emplace_back();
250             ret = unpackValueWithErrorCode<typename Type::value_type>(
251                       val, key, value.back()) &&
252                   ret;
253         }
254     }
255     else
256     {
257         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
258         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
259         if (jsonPtr == nullptr)
260         {
261             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
262                              jsonValue.type_name());
263             return UnpackErrorCode::invalidType;
264         }
265         value = std::move(*jsonPtr);
266     }
267     return ret;
268 }
269 
270 template <typename Type>
unpackValue(nlohmann::json & jsonValue,std::string_view key,crow::Response & res,Type & value)271 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
272                  crow::Response& res, Type& value)
273 {
274     bool ret = true;
275 
276     if constexpr (IsOptional<Type>::value)
277     {
278         value.emplace();
279         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
280                                                      *value) &&
281               ret;
282     }
283     else if constexpr (IsStdArray<Type>::value)
284     {
285         nlohmann::json::array_t* arr =
286             jsonValue.get_ptr<nlohmann::json::array_t*>();
287         if (arr == nullptr)
288         {
289             messages::propertyValueTypeError(res, res.jsonValue, key);
290             return false;
291         }
292         if (jsonValue.size() != value.size())
293         {
294             messages::propertyValueTypeError(res, res.jsonValue, key);
295             return false;
296         }
297         size_t index = 0;
298         for (auto& val : *arr)
299         {
300             ret = unpackValue<typename Type::value_type>(val, key, res,
301                                                          value[index++]) &&
302                   ret;
303         }
304     }
305     else if constexpr (IsVector<Type>::value)
306     {
307         nlohmann::json::array_t* arr =
308             jsonValue.get_ptr<nlohmann::json::array_t*>();
309         if (arr == nullptr)
310         {
311             messages::propertyValueTypeError(res, res.jsonValue, key);
312             return false;
313         }
314 
315         for (auto& val : *arr)
316         {
317             value.emplace_back();
318             ret = unpackValue<typename Type::value_type>(val, key, res,
319                                                          value.back()) &&
320                   ret;
321         }
322     }
323     else if constexpr (IsVariant<Type>::value)
324     {
325         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
326         if (ec != UnpackErrorCode::success)
327         {
328             if (ec == UnpackErrorCode::invalidType)
329             {
330                 messages::propertyValueTypeError(res, jsonValue, key);
331             }
332             else if (ec == UnpackErrorCode::outOfRange)
333             {
334                 messages::propertyValueNotInList(res, jsonValue, key);
335             }
336             return false;
337         }
338     }
339     else
340     {
341         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
342         if (ec != UnpackErrorCode::success)
343         {
344             if (ec == UnpackErrorCode::invalidType)
345             {
346                 messages::propertyValueTypeError(res, jsonValue, key);
347             }
348             else if (ec == UnpackErrorCode::outOfRange)
349             {
350                 messages::propertyValueNotInList(res, jsonValue, key);
351             }
352             return false;
353         }
354     }
355 
356     return ret;
357 }
358 
359 template <typename Type>
unpackValue(nlohmann::json & jsonValue,std::string_view key,Type & value)360 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
361 {
362     bool ret = true;
363     if constexpr (IsOptional<Type>::value)
364     {
365         value.emplace();
366         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
367               ret;
368     }
369     else if constexpr (IsStdArray<Type>::value)
370     {
371         nlohmann::json::array_t* arr =
372             jsonValue.get_ptr<nlohmann::json::array_t*>();
373         if (arr == nullptr)
374         {
375             return false;
376         }
377         if (jsonValue.size() != value.size())
378         {
379             return false;
380         }
381         size_t index = 0;
382         for (const auto& val : *arr)
383         {
384             ret = unpackValue<typename Type::value_type>(val, key,
385                                                          value[index++]) &&
386                   ret;
387         }
388     }
389     else if constexpr (IsVector<Type>::value)
390     {
391         nlohmann::json::array_t* arr =
392             jsonValue.get_ptr<nlohmann::json::array_t*>();
393         if (arr == nullptr)
394         {
395             return false;
396         }
397 
398         for (const auto& val : *arr)
399         {
400             value.emplace_back();
401             ret = unpackValue<typename Type::value_type>(val, key,
402                                                          value.back()) &&
403                   ret;
404         }
405     }
406     else
407     {
408         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
409         if (ec != UnpackErrorCode::success)
410         {
411             return false;
412         }
413     }
414 
415     return ret;
416 }
417 } // namespace details
418 
419 // clang-format off
420 using UnpackVariant = std::variant<
421     uint8_t*,
422     uint16_t*,
423     int16_t*,
424     uint32_t*,
425     int32_t*,
426     uint64_t*,
427     int64_t*,
428     bool*,
429     double*,
430     std::string*,
431     nlohmann::json::object_t*,
432     std::variant<std::string, std::nullptr_t>*,
433     std::variant<uint8_t, std::nullptr_t>*,
434     std::variant<int16_t, std::nullptr_t>*,
435     std::variant<uint16_t, std::nullptr_t>*,
436     std::variant<int32_t, std::nullptr_t>*,
437     std::variant<uint32_t, std::nullptr_t>*,
438     std::variant<int64_t, std::nullptr_t>*,
439     std::variant<uint64_t, std::nullptr_t>*,
440     std::variant<double, std::nullptr_t>*,
441     std::variant<bool, std::nullptr_t>*,
442     std::vector<uint8_t>*,
443     std::vector<uint16_t>*,
444     std::vector<int16_t>*,
445     std::vector<uint32_t>*,
446     std::vector<int32_t>*,
447     std::vector<uint64_t>*,
448     std::vector<int64_t>*,
449     //std::vector<bool>*,
450     std::vector<double>*,
451     std::vector<std::string>*,
452     std::vector<nlohmann::json::object_t>*,
453     std::optional<uint8_t>*,
454     std::optional<uint16_t>*,
455     std::optional<int16_t>*,
456     std::optional<uint32_t>*,
457     std::optional<int32_t>*,
458     std::optional<uint64_t>*,
459     std::optional<int64_t>*,
460     std::optional<bool>*,
461     std::optional<double>*,
462     std::optional<std::string>*,
463     std::optional<nlohmann::json::object_t>*,
464     std::optional<std::vector<uint8_t>>*,
465     std::optional<std::vector<uint16_t>>*,
466     std::optional<std::vector<int16_t>>*,
467     std::optional<std::vector<uint32_t>>*,
468     std::optional<std::vector<int32_t>>*,
469     std::optional<std::vector<uint64_t>>*,
470     std::optional<std::vector<int64_t>>*,
471     //std::optional<std::vector<bool>>*,
472     std::optional<std::vector<double>>*,
473     std::optional<std::vector<std::string>>*,
474     std::optional<std::vector<nlohmann::json::object_t>>*,
475     std::optional<std::variant<std::string, std::nullptr_t>>*,
476     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
477     std::optional<std::variant<int16_t, std::nullptr_t>>*,
478     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
479     std::optional<std::variant<int32_t, std::nullptr_t>>*,
480     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
481     std::optional<std::variant<int64_t, std::nullptr_t>>*,
482     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
483     std::optional<std::variant<double, std::nullptr_t>>*,
484     std::optional<std::variant<bool, std::nullptr_t>>*,
485     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
486     std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
487 
488     // Note, these types are kept for historical completeness, but should not be used,
489     // As they do not provide object type safety.  Instead, rely on nlohmann::json::object_t
490     // Will be removed Q2 2025
491     nlohmann::json*,
492     std::optional<std::vector<nlohmann::json>>*,
493     std::vector<nlohmann::json>*,
494     std::optional<nlohmann::json>*
495 >;
496 // clang-format on
497 
498 struct PerUnpack
499 {
500     std::string_view key;
501     UnpackVariant value;
502     bool complete = false;
503 };
504 
505 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
506                            std::span<PerUnpack> toUnpack);
507 
readJsonHelperObject(nlohmann::json::object_t & obj,crow::Response & res,std::span<PerUnpack> toUnpack)508 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
509                                  crow::Response& res,
510                                  std::span<PerUnpack> toUnpack)
511 {
512     bool result = true;
513     for (auto& item : obj)
514     {
515         size_t unpackIndex = 0;
516         for (; unpackIndex < toUnpack.size(); unpackIndex++)
517         {
518             PerUnpack& unpackSpec = toUnpack[unpackIndex];
519             std::string_view key = unpackSpec.key;
520             size_t keysplitIndex = key.find('/');
521             std::string_view leftover;
522             if (keysplitIndex != std::string_view::npos)
523             {
524                 leftover = key.substr(keysplitIndex + 1);
525                 key = key.substr(0, keysplitIndex);
526             }
527 
528             if (key != item.first || unpackSpec.complete)
529             {
530                 continue;
531             }
532 
533             // Sublevel key
534             if (!leftover.empty())
535             {
536                 // Include the slash in the key so we can compare later
537                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
538                 nlohmann::json j;
539                 result = details::unpackValue<nlohmann::json>(item.second, key,
540                                                               res, j) &&
541                          result;
542                 if (!result)
543                 {
544                     return result;
545                 }
546 
547                 std::vector<PerUnpack> nextLevel;
548                 for (PerUnpack& p : toUnpack)
549                 {
550                     if (!p.key.starts_with(key))
551                     {
552                         continue;
553                     }
554                     std::string_view thisLeftover = p.key.substr(key.size());
555                     nextLevel.push_back({thisLeftover, p.value, false});
556                     p.complete = true;
557                 }
558 
559                 result = readJsonHelper(j, res, nextLevel) && result;
560                 break;
561             }
562 
563             result = std::visit(
564                          [&item, &unpackSpec, &res](auto&& val) {
565                 using ContainedT =
566                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
567                 return details::unpackValue<ContainedT>(
568                     item.second, unpackSpec.key, res, *val);
569             },
570                          unpackSpec.value) &&
571                      result;
572 
573             unpackSpec.complete = true;
574             break;
575         }
576 
577         if (unpackIndex == toUnpack.size())
578         {
579             messages::propertyUnknown(res, item.first);
580             result = false;
581         }
582     }
583 
584     for (PerUnpack& perUnpack : toUnpack)
585     {
586         if (!perUnpack.complete)
587         {
588             bool isOptional = std::visit(
589                 [](auto&& val) {
590                 using ContainedType =
591                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
592                 return details::IsOptional<ContainedType>::value;
593             },
594                 perUnpack.value);
595             if (isOptional)
596             {
597                 continue;
598             }
599             messages::propertyMissing(res, perUnpack.key);
600             result = false;
601         }
602     }
603     return result;
604 }
605 
readJsonHelper(nlohmann::json & jsonRequest,crow::Response & res,std::span<PerUnpack> toUnpack)606 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
607                            std::span<PerUnpack> toUnpack)
608 {
609     nlohmann::json::object_t* obj =
610         jsonRequest.get_ptr<nlohmann::json::object_t*>();
611     if (obj == nullptr)
612     {
613         BMCWEB_LOG_DEBUG("Json value is not an object");
614         messages::unrecognizedRequestBody(res);
615         return false;
616     }
617     return readJsonHelperObject(*obj, res, toUnpack);
618 }
619 
packVariant(std::span<PerUnpack>)620 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
621 
622 template <typename FirstType, typename... UnpackTypes>
packVariant(std::span<PerUnpack> toPack,std::string_view key,FirstType & first,UnpackTypes &&...in)623 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
624                  FirstType& first, UnpackTypes&&... in)
625 {
626     if (toPack.empty())
627     {
628         return;
629     }
630     toPack[0].key = key;
631     toPack[0].value = &first;
632     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
633     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
634 }
635 
636 template <typename FirstType, typename... UnpackTypes>
readJsonObject(nlohmann::json::object_t & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)637 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
638                     std::string_view key, FirstType&& first,
639                     UnpackTypes&&... in)
640 {
641     const std::size_t n = sizeof...(UnpackTypes) + 2;
642     std::array<PerUnpack, n / 2> toUnpack2;
643     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
644     return readJsonHelperObject(jsonRequest, res, toUnpack2);
645 }
646 
647 template <typename FirstType, typename... UnpackTypes>
readJson(nlohmann::json & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)648 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
649               std::string_view key, FirstType&& first, UnpackTypes&&... in)
650 {
651     nlohmann::json::object_t* obj =
652         jsonRequest.get_ptr<nlohmann::json::object_t*>();
653     if (obj == nullptr)
654     {
655         BMCWEB_LOG_DEBUG("Json value is not an object");
656         messages::unrecognizedRequestBody(res);
657         return false;
658     }
659     return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
660                           std::forward<UnpackTypes&&>(in)...);
661 }
662 
663 inline std::optional<nlohmann::json::object_t>
readJsonPatchHelper(const crow::Request & req,crow::Response & res)664     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
665 {
666     nlohmann::json jsonRequest;
667     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
668     {
669         BMCWEB_LOG_DEBUG("Json value not readable");
670         return std::nullopt;
671     }
672     nlohmann::json::object_t* object =
673         jsonRequest.get_ptr<nlohmann::json::object_t*>();
674     if (object == nullptr || object->empty())
675     {
676         BMCWEB_LOG_DEBUG("Json value is empty");
677         messages::emptyJSON(res);
678         return std::nullopt;
679     }
680     std::erase_if(*object,
681                   [](const std::pair<std::string, nlohmann::json>& item) {
682         return item.first.starts_with("@odata.");
683     });
684     if (object->empty())
685     {
686         //  If the update request only contains OData annotations, the service
687         //  should return the HTTP 400 Bad Request status code with the
688         //  NoOperation message from the Base Message Registry, ...
689         messages::noOperation(res);
690         return std::nullopt;
691     }
692 
693     return {std::move(*object)};
694 }
695 
696 template <typename... UnpackTypes>
readJsonPatch(const crow::Request & req,crow::Response & res,std::string_view key,UnpackTypes &&...in)697 bool readJsonPatch(const crow::Request& req, crow::Response& res,
698                    std::string_view key, UnpackTypes&&... in)
699 {
700     std::optional<nlohmann::json::object_t> jsonRequest =
701         readJsonPatchHelper(req, res);
702     if (!jsonRequest)
703     {
704         return false;
705     }
706     if (jsonRequest->empty())
707     {
708         messages::emptyJSON(res);
709         return false;
710     }
711 
712     return readJsonObject(*jsonRequest, res, key,
713                           std::forward<UnpackTypes&&>(in)...);
714 }
715 
716 template <typename... UnpackTypes>
readJsonAction(const crow::Request & req,crow::Response & res,const char * key,UnpackTypes &&...in)717 bool readJsonAction(const crow::Request& req, crow::Response& res,
718                     const char* key, UnpackTypes&&... in)
719 {
720     nlohmann::json jsonRequest;
721     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
722     {
723         BMCWEB_LOG_DEBUG("Json value not readable");
724         return false;
725     }
726     nlohmann::json::object_t* object =
727         jsonRequest.get_ptr<nlohmann::json::object_t*>();
728     if (object == nullptr)
729     {
730         BMCWEB_LOG_DEBUG("Json value is empty");
731         messages::emptyJSON(res);
732         return false;
733     }
734     return readJsonObject(*object, res, key,
735                           std::forward<UnpackTypes&&>(in)...);
736 }
737 
738 // Determines if two json objects are less, based on the presence of the
739 // @odata.id key
odataObjectCmp(const nlohmann::json & a,const nlohmann::json & b)740 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
741 {
742     using object_t = nlohmann::json::object_t;
743     const object_t* aObj = a.get_ptr<const object_t*>();
744     const object_t* bObj = b.get_ptr<const object_t*>();
745 
746     if (aObj == nullptr)
747     {
748         if (bObj == nullptr)
749         {
750             return 0;
751         }
752         return -1;
753     }
754     if (bObj == nullptr)
755     {
756         return 1;
757     }
758     object_t::const_iterator aIt = aObj->find("@odata.id");
759     object_t::const_iterator bIt = bObj->find("@odata.id");
760     // If either object doesn't have the key, they get "sorted" to the end.
761     if (aIt == aObj->end())
762     {
763         if (bIt == bObj->end())
764         {
765             return 0;
766         }
767         return -1;
768     }
769     if (bIt == bObj->end())
770     {
771         return 1;
772     }
773     const nlohmann::json::string_t* nameA =
774         aIt->second.get_ptr<const std::string*>();
775     const nlohmann::json::string_t* nameB =
776         bIt->second.get_ptr<const std::string*>();
777     // If either object doesn't have a string as the key, they get "sorted" to
778     // the end.
779     if (nameA == nullptr)
780     {
781         if (nameB == nullptr)
782         {
783             return 0;
784         }
785         return -1;
786     }
787     if (nameB == nullptr)
788     {
789         return 1;
790     }
791     boost::urls::url_view aUrl(*nameA);
792     boost::urls::url_view bUrl(*nameB);
793     auto segmentsAIt = aUrl.segments().begin();
794     auto segmentsBIt = bUrl.segments().begin();
795 
796     while (true)
797     {
798         if (segmentsAIt == aUrl.segments().end())
799         {
800             if (segmentsBIt == bUrl.segments().end())
801             {
802                 return 0;
803             }
804             return -1;
805         }
806         if (segmentsBIt == bUrl.segments().end())
807         {
808             return 1;
809         }
810         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
811         if (res != 0)
812         {
813             return res;
814         }
815 
816         segmentsAIt++;
817         segmentsBIt++;
818     }
819 };
820 
821 struct ODataObjectLess
822 {
operator ()redfish::json_util::ODataObjectLess823     bool operator()(const nlohmann::json& left,
824                     const nlohmann::json& right) const
825     {
826         return odataObjectCmp(left, right) < 0;
827     }
828 };
829 
830 // Sort the JSON array by |element[key]|.
831 // Elements without |key| or type of |element[key]| is not string are smaller
832 // those whose |element[key]| is string.
sortJsonArrayByOData(nlohmann::json::array_t & array)833 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
834 {
835     std::ranges::sort(array, ODataObjectLess());
836 }
837 
838 // Returns the estimated size of the JSON value
839 // The implementation walks through every key and every value, accumulates the
840 //  total size of keys and values.
841 // Ideally, we should use a custom allocator that nlohmann JSON supports.
842 
843 // Assumption made:
844 //  1. number: 8 characters
845 //  2. boolean: 5 characters (False)
846 //  3. string: len(str) + 2 characters (quote)
847 //  4. bytes: len(bytes) characters
848 //  5. null: 4 characters (null)
849 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
850 
851 } // namespace json_util
852 } // namespace redfish
853