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>
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>
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>
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
239     {
240         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
241         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
242         if (jsonPtr == nullptr)
243         {
244             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
245                              jsonValue.type_name());
246             return UnpackErrorCode::invalidType;
247         }
248         value = std::move(*jsonPtr);
249     }
250     return ret;
251 }
252 
253 template <typename Type>
254 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
255                  crow::Response& res, Type& value)
256 {
257     bool ret = true;
258 
259     if constexpr (IsOptional<Type>::value)
260     {
261         value.emplace();
262         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
263                                                      *value) &&
264               ret;
265     }
266     else if constexpr (IsStdArray<Type>::value)
267     {
268         if (!jsonValue.is_array())
269         {
270             messages::propertyValueTypeError(res, res.jsonValue, key);
271             return false;
272         }
273         if (jsonValue.size() != value.size())
274         {
275             messages::propertyValueTypeError(res, res.jsonValue, key);
276             return false;
277         }
278         size_t index = 0;
279         for (const auto& val : jsonValue.items())
280         {
281             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
282                                                          value[index++]) &&
283                   ret;
284         }
285     }
286     else if constexpr (IsVector<Type>::value)
287     {
288         if (!jsonValue.is_array())
289         {
290             messages::propertyValueTypeError(res, res.jsonValue, key);
291             return false;
292         }
293 
294         for (const auto& val : jsonValue.items())
295         {
296             value.emplace_back();
297             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
298                                                          value.back()) &&
299                   ret;
300         }
301     }
302     else if constexpr (IsVariant<Type>::value)
303     {
304         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
305         if (ec != UnpackErrorCode::success)
306         {
307             if (ec == UnpackErrorCode::invalidType)
308             {
309                 messages::propertyValueTypeError(res, jsonValue, key);
310             }
311             else if (ec == UnpackErrorCode::outOfRange)
312             {
313                 messages::propertyValueNotInList(res, jsonValue, key);
314             }
315             return false;
316         }
317     }
318     else
319     {
320         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
321         if (ec != UnpackErrorCode::success)
322         {
323             if (ec == UnpackErrorCode::invalidType)
324             {
325                 messages::propertyValueTypeError(res, jsonValue, key);
326             }
327             else if (ec == UnpackErrorCode::outOfRange)
328             {
329                 messages::propertyValueNotInList(res, jsonValue, key);
330             }
331             return false;
332         }
333     }
334 
335     return ret;
336 }
337 
338 template <typename Type>
339 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
340 {
341     bool ret = true;
342     if constexpr (IsOptional<Type>::value)
343     {
344         value.emplace();
345         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
346               ret;
347     }
348     else if constexpr (IsStdArray<Type>::value)
349     {
350         if (!jsonValue.is_array())
351         {
352             return false;
353         }
354         if (jsonValue.size() != value.size())
355         {
356             return false;
357         }
358         size_t index = 0;
359         for (const auto& val : jsonValue.items())
360         {
361             ret = unpackValue<typename Type::value_type>(val.value(), key,
362                                                          value[index++]) &&
363                   ret;
364         }
365     }
366     else if constexpr (IsVector<Type>::value)
367     {
368         if (!jsonValue.is_array())
369         {
370             return false;
371         }
372 
373         for (const auto& val : jsonValue.items())
374         {
375             value.emplace_back();
376             ret = unpackValue<typename Type::value_type>(val.value(), key,
377                                                          value.back()) &&
378                   ret;
379         }
380     }
381     else
382     {
383         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
384         if (ec != UnpackErrorCode::success)
385         {
386             return false;
387         }
388     }
389 
390     return ret;
391 }
392 } // namespace details
393 
394 // clang-format off
395 using UnpackVariant = std::variant<
396     uint8_t*,
397     uint16_t*,
398     int16_t*,
399     uint32_t*,
400     int32_t*,
401     uint64_t*,
402     int64_t*,
403     bool*,
404     double*,
405     std::string*,
406     nlohmann::json*,
407     nlohmann::json::object_t*,
408     std::variant<std::string, std::nullptr_t>*,
409     std::variant<uint8_t, std::nullptr_t>*,
410     std::variant<int16_t, std::nullptr_t>*,
411     std::variant<uint16_t, std::nullptr_t>*,
412     std::variant<int32_t, std::nullptr_t>*,
413     std::variant<uint32_t, std::nullptr_t>*,
414     std::variant<int64_t, std::nullptr_t>*,
415     std::variant<uint64_t, std::nullptr_t>*,
416     std::variant<double, std::nullptr_t>*,
417     std::variant<bool, std::nullptr_t>*,
418     std::vector<uint8_t>*,
419     std::vector<uint16_t>*,
420     std::vector<int16_t>*,
421     std::vector<uint32_t>*,
422     std::vector<int32_t>*,
423     std::vector<uint64_t>*,
424     std::vector<int64_t>*,
425     //std::vector<bool>*,
426     std::vector<double>*,
427     std::vector<std::string>*,
428     std::vector<nlohmann::json>*,
429     std::vector<nlohmann::json::object_t>*,
430     std::optional<uint8_t>*,
431     std::optional<uint16_t>*,
432     std::optional<int16_t>*,
433     std::optional<uint32_t>*,
434     std::optional<int32_t>*,
435     std::optional<uint64_t>*,
436     std::optional<int64_t>*,
437     std::optional<bool>*,
438     std::optional<double>*,
439     std::optional<std::string>*,
440     std::optional<nlohmann::json>*,
441     std::optional<nlohmann::json::object_t>*,
442     std::optional<std::vector<uint8_t>>*,
443     std::optional<std::vector<uint16_t>>*,
444     std::optional<std::vector<int16_t>>*,
445     std::optional<std::vector<uint32_t>>*,
446     std::optional<std::vector<int32_t>>*,
447     std::optional<std::vector<uint64_t>>*,
448     std::optional<std::vector<int64_t>>*,
449     //std::optional<std::vector<bool>>*,
450     std::optional<std::vector<double>>*,
451     std::optional<std::vector<std::string>>*,
452     std::optional<std::vector<nlohmann::json>>*,
453     std::optional<std::vector<nlohmann::json::object_t>>*,
454     std::optional<std::variant<std::string, std::nullptr_t>>*,
455     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
456     std::optional<std::variant<int16_t, std::nullptr_t>>*,
457     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
458     std::optional<std::variant<int32_t, std::nullptr_t>>*,
459     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
460     std::optional<std::variant<int64_t, std::nullptr_t>>*,
461     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
462     std::optional<std::variant<double, std::nullptr_t>>*,
463     std::optional<std::variant<bool, std::nullptr_t>>*,
464     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
465     std::optional<std::variant<nlohmann::json::object_t, std::nullptr_t>>*
466 >;
467 // clang-format on
468 
469 struct PerUnpack
470 {
471     std::string_view key;
472     UnpackVariant value;
473     bool complete = false;
474 };
475 
476 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
477                            std::span<PerUnpack> toUnpack);
478 
479 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
480                                  crow::Response& res,
481                                  std::span<PerUnpack> toUnpack)
482 {
483     bool result = true;
484     for (auto& item : obj)
485     {
486         size_t unpackIndex = 0;
487         for (; unpackIndex < toUnpack.size(); unpackIndex++)
488         {
489             PerUnpack& unpackSpec = toUnpack[unpackIndex];
490             std::string_view key = unpackSpec.key;
491             size_t keysplitIndex = key.find('/');
492             std::string_view leftover;
493             if (keysplitIndex != std::string_view::npos)
494             {
495                 leftover = key.substr(keysplitIndex + 1);
496                 key = key.substr(0, keysplitIndex);
497             }
498 
499             if (key != item.first || unpackSpec.complete)
500             {
501                 continue;
502             }
503 
504             // Sublevel key
505             if (!leftover.empty())
506             {
507                 // Include the slash in the key so we can compare later
508                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
509                 nlohmann::json j;
510                 result = details::unpackValue<nlohmann::json>(item.second, key,
511                                                               res, j) &&
512                          result;
513                 if (!result)
514                 {
515                     return result;
516                 }
517 
518                 std::vector<PerUnpack> nextLevel;
519                 for (PerUnpack& p : toUnpack)
520                 {
521                     if (!p.key.starts_with(key))
522                     {
523                         continue;
524                     }
525                     std::string_view thisLeftover = p.key.substr(key.size());
526                     nextLevel.push_back({thisLeftover, p.value, false});
527                     p.complete = true;
528                 }
529 
530                 result = readJsonHelper(j, res, nextLevel) && result;
531                 break;
532             }
533 
534             result = std::visit(
535                          [&item, &unpackSpec, &res](auto&& val) {
536                 using ContainedT =
537                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
538                 return details::unpackValue<ContainedT>(
539                     item.second, unpackSpec.key, res, *val);
540             },
541                          unpackSpec.value) &&
542                      result;
543 
544             unpackSpec.complete = true;
545             break;
546         }
547 
548         if (unpackIndex == toUnpack.size())
549         {
550             messages::propertyUnknown(res, item.first);
551             result = false;
552         }
553     }
554 
555     for (PerUnpack& perUnpack : toUnpack)
556     {
557         if (!perUnpack.complete)
558         {
559             bool isOptional = std::visit(
560                 [](auto&& val) {
561                 using ContainedType =
562                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
563                 return details::IsOptional<ContainedType>::value;
564             },
565                 perUnpack.value);
566             if (isOptional)
567             {
568                 continue;
569             }
570             messages::propertyMissing(res, perUnpack.key);
571             result = false;
572         }
573     }
574     return result;
575 }
576 
577 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
578                            std::span<PerUnpack> toUnpack)
579 {
580     nlohmann::json::object_t* obj =
581         jsonRequest.get_ptr<nlohmann::json::object_t*>();
582     if (obj == nullptr)
583     {
584         BMCWEB_LOG_DEBUG("Json value is not an object");
585         messages::unrecognizedRequestBody(res);
586         return false;
587     }
588     return readJsonHelperObject(*obj, res, toUnpack);
589 }
590 
591 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
592 
593 template <typename FirstType, typename... UnpackTypes>
594 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
595                  FirstType& first, UnpackTypes&&... in)
596 {
597     if (toPack.empty())
598     {
599         return;
600     }
601     toPack[0].key = key;
602     toPack[0].value = &first;
603     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
604     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
605 }
606 
607 template <typename FirstType, typename... UnpackTypes>
608 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
609                     std::string_view key, FirstType&& first,
610                     UnpackTypes&&... in)
611 {
612     const std::size_t n = sizeof...(UnpackTypes) + 2;
613     std::array<PerUnpack, n / 2> toUnpack2;
614     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
615     return readJsonHelperObject(jsonRequest, res, toUnpack2);
616 }
617 
618 template <typename FirstType, typename... UnpackTypes>
619 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
620               std::string_view key, FirstType&& first, UnpackTypes&&... in)
621 {
622     nlohmann::json::object_t* obj =
623         jsonRequest.get_ptr<nlohmann::json::object_t*>();
624     if (obj == nullptr)
625     {
626         BMCWEB_LOG_DEBUG("Json value is not an object");
627         messages::unrecognizedRequestBody(res);
628         return false;
629     }
630     return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
631                           std::forward<UnpackTypes&&>(in)...);
632 }
633 
634 inline std::optional<nlohmann::json::object_t>
635     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
636 {
637     nlohmann::json jsonRequest;
638     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
639     {
640         BMCWEB_LOG_DEBUG("Json value not readable");
641         return std::nullopt;
642     }
643     nlohmann::json::object_t* object =
644         jsonRequest.get_ptr<nlohmann::json::object_t*>();
645     if (object == nullptr || object->empty())
646     {
647         BMCWEB_LOG_DEBUG("Json value is empty");
648         messages::emptyJSON(res);
649         return std::nullopt;
650     }
651     std::erase_if(*object,
652                   [](const std::pair<std::string, nlohmann::json>& item) {
653         return item.first.starts_with("@odata.");
654     });
655     if (object->empty())
656     {
657         //  If the update request only contains OData annotations, the service
658         //  should return the HTTP 400 Bad Request status code with the
659         //  NoOperation message from the Base Message Registry, ...
660         messages::noOperation(res);
661         return std::nullopt;
662     }
663 
664     return {std::move(*object)};
665 }
666 
667 template <typename... UnpackTypes>
668 bool readJsonPatch(const crow::Request& req, crow::Response& res,
669                    std::string_view key, UnpackTypes&&... in)
670 {
671     std::optional<nlohmann::json::object_t> jsonRequest =
672         readJsonPatchHelper(req, res);
673     if (!jsonRequest)
674     {
675         return false;
676     }
677     if (jsonRequest->empty())
678     {
679         messages::emptyJSON(res);
680         return false;
681     }
682 
683     return readJsonObject(*jsonRequest, res, key,
684                           std::forward<UnpackTypes&&>(in)...);
685 }
686 
687 template <typename... UnpackTypes>
688 bool readJsonAction(const crow::Request& req, crow::Response& res,
689                     const char* key, UnpackTypes&&... in)
690 {
691     nlohmann::json jsonRequest;
692     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
693     {
694         BMCWEB_LOG_DEBUG("Json value not readable");
695         return false;
696     }
697     nlohmann::json::object_t* object =
698         jsonRequest.get_ptr<nlohmann::json::object_t*>();
699     if (object == nullptr)
700     {
701         BMCWEB_LOG_DEBUG("Json value is empty");
702         messages::emptyJSON(res);
703         return false;
704     }
705     return readJsonObject(*object, res, key,
706                           std::forward<UnpackTypes&&>(in)...);
707 }
708 
709 // Determines if two json objects are less, based on the presence of the
710 // @odata.id key
711 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
712 {
713     using object_t = nlohmann::json::object_t;
714     const object_t* aObj = a.get_ptr<const object_t*>();
715     const object_t* bObj = b.get_ptr<const object_t*>();
716 
717     if (aObj == nullptr)
718     {
719         if (bObj == nullptr)
720         {
721             return 0;
722         }
723         return -1;
724     }
725     if (bObj == nullptr)
726     {
727         return 1;
728     }
729     object_t::const_iterator aIt = aObj->find("@odata.id");
730     object_t::const_iterator bIt = bObj->find("@odata.id");
731     // If either object doesn't have the key, they get "sorted" to the end.
732     if (aIt == aObj->end())
733     {
734         if (bIt == bObj->end())
735         {
736             return 0;
737         }
738         return -1;
739     }
740     if (bIt == bObj->end())
741     {
742         return 1;
743     }
744     const nlohmann::json::string_t* nameA =
745         aIt->second.get_ptr<const std::string*>();
746     const nlohmann::json::string_t* nameB =
747         bIt->second.get_ptr<const std::string*>();
748     // If either object doesn't have a string as the key, they get "sorted" to
749     // the end.
750     if (nameA == nullptr)
751     {
752         if (nameB == nullptr)
753         {
754             return 0;
755         }
756         return -1;
757     }
758     if (nameB == nullptr)
759     {
760         return 1;
761     }
762     boost::urls::url_view aUrl(*nameA);
763     boost::urls::url_view bUrl(*nameB);
764     auto segmentsAIt = aUrl.segments().begin();
765     auto segmentsBIt = bUrl.segments().begin();
766 
767     while (true)
768     {
769         if (segmentsAIt == aUrl.segments().end())
770         {
771             if (segmentsBIt == bUrl.segments().end())
772             {
773                 return 0;
774             }
775             return -1;
776         }
777         if (segmentsBIt == bUrl.segments().end())
778         {
779             return 1;
780         }
781         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
782         if (res != 0)
783         {
784             return res;
785         }
786 
787         segmentsAIt++;
788         segmentsBIt++;
789     }
790 };
791 
792 struct ODataObjectLess
793 {
794     bool operator()(const nlohmann::json& left,
795                     const nlohmann::json& right) const
796     {
797         return odataObjectCmp(left, right) < 0;
798     }
799 };
800 
801 // Sort the JSON array by |element[key]|.
802 // Elements without |key| or type of |element[key]| is not string are smaller
803 // those whose |element[key]| is string.
804 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
805 {
806     std::ranges::sort(array, ODataObjectLess());
807 }
808 
809 // Returns the estimated size of the JSON value
810 // The implementation walks through every key and every value, accumulates the
811 //  total size of keys and values.
812 // Ideally, we should use a custom allocator that nlohmann JSON supports.
813 
814 // Assumption made:
815 //  1. number: 8 characters
816 //  2. boolean: 5 characters (False)
817 //  3. string: len(str) + 2 characters (quote)
818 //  4. bytes: len(bytes) characters
819 //  5. null: 4 characters (null)
820 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
821 
822 } // namespace json_util
823 } // namespace redfish
824