xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision 318226c278a18c1492b2235cb2c3b2ce5ed09900)
177dd8813SKowalski, Kamil /*
277dd8813SKowalski, Kamil // Copyright (c) 2018 Intel Corporation
377dd8813SKowalski, Kamil //
477dd8813SKowalski, Kamil // Licensed under the Apache License, Version 2.0 (the "License");
577dd8813SKowalski, Kamil // you may not use this file except in compliance with the License.
677dd8813SKowalski, Kamil // You may obtain a copy of the License at
777dd8813SKowalski, Kamil //
877dd8813SKowalski, Kamil //      http://www.apache.org/licenses/LICENSE-2.0
977dd8813SKowalski, Kamil //
1077dd8813SKowalski, Kamil // Unless required by applicable law or agreed to in writing, software
1177dd8813SKowalski, Kamil // distributed under the License is distributed on an "AS IS" BASIS,
1277dd8813SKowalski, Kamil // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377dd8813SKowalski, Kamil // See the License for the specific language governing permissions and
1477dd8813SKowalski, Kamil // limitations under the License.
1577dd8813SKowalski, Kamil */
1677dd8813SKowalski, Kamil #pragma once
179712f8acSEd Tanous 
1877dd8813SKowalski, Kamil #include <crow/http_request.h>
1977dd8813SKowalski, Kamil #include <crow/http_response.h>
2077dd8813SKowalski, Kamil 
219712f8acSEd Tanous #include <bitset>
229712f8acSEd Tanous #include <error_messages.hpp>
231abe55efSEd Tanous #include <nlohmann/json.hpp>
240627a2c7SEd Tanous 
251abe55efSEd Tanous namespace redfish
261abe55efSEd Tanous {
271abe55efSEd Tanous 
281abe55efSEd Tanous namespace json_util
291abe55efSEd Tanous {
3077dd8813SKowalski, Kamil 
3177dd8813SKowalski, Kamil /**
3277dd8813SKowalski, Kamil  * @brief Processes request to extract JSON from its body. If it fails, adds
3377dd8813SKowalski, Kamil  *       MalformedJSON message to response and ends it.
3477dd8813SKowalski, Kamil  *
3577dd8813SKowalski, Kamil  * @param[io]  res       Response object
3677dd8813SKowalski, Kamil  * @param[in]  req       Request object
3777dd8813SKowalski, Kamil  * @param[out] reqJson   JSON object extracted from request's body
3877dd8813SKowalski, Kamil  *
3977dd8813SKowalski, Kamil  * @return true if JSON is valid, false when JSON is invalid and response has
4077dd8813SKowalski, Kamil  *         been filled with message and ended.
4177dd8813SKowalski, Kamil  */
4255c7b7a2SEd Tanous bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
4377dd8813SKowalski, Kamil                             nlohmann::json& reqJson);
449712f8acSEd Tanous namespace details
459712f8acSEd Tanous {
46771cfa0fSJason M. Bills 
47771cfa0fSJason M. Bills template <typename Type> struct is_optional : std::false_type
489712f8acSEd Tanous {
499712f8acSEd Tanous };
509712f8acSEd Tanous 
51771cfa0fSJason M. Bills template <typename Type>
52a24526dcSEd Tanous struct is_optional<std::optional<Type>> : std::true_type
539712f8acSEd Tanous {
549712f8acSEd Tanous };
559712f8acSEd Tanous 
56771cfa0fSJason M. Bills template <typename Type>
57771cfa0fSJason M. Bills constexpr bool is_optional_v = is_optional<Type>::value;
58771cfa0fSJason M. Bills 
59b1556427SEd Tanous template <typename Type> struct is_vector : std::false_type
60b1556427SEd Tanous {
61b1556427SEd Tanous };
62b1556427SEd Tanous 
63b1556427SEd Tanous template <typename Type> struct is_vector<std::vector<Type>> : std::true_type
64b1556427SEd Tanous {
65b1556427SEd Tanous };
66b1556427SEd Tanous 
67b1556427SEd Tanous template <typename Type> constexpr bool is_vector_v = is_vector<Type>::value;
68b1556427SEd Tanous 
69*318226c2SJames Feist template <typename Type> struct is_std_array : std::false_type
70*318226c2SJames Feist {
71*318226c2SJames Feist };
72*318226c2SJames Feist 
73*318226c2SJames Feist template <typename Type, std::size_t size>
74*318226c2SJames Feist struct is_std_array<std::array<Type, size>> : std::true_type
75*318226c2SJames Feist {
76*318226c2SJames Feist };
77*318226c2SJames Feist 
78*318226c2SJames Feist template <typename Type>
79*318226c2SJames Feist constexpr bool is_std_array_v = is_std_array<Type>::value;
80*318226c2SJames Feist 
81771cfa0fSJason M. Bills template <typename Type>
82771cfa0fSJason M. Bills void unpackValue(nlohmann::json& jsonValue, const std::string& key,
83771cfa0fSJason M. Bills                  crow::Response& res, Type& value)
84771cfa0fSJason M. Bills {
85771cfa0fSJason M. Bills     if constexpr (std::is_arithmetic_v<Type>)
86771cfa0fSJason M. Bills     {
87771cfa0fSJason M. Bills         using NumType =
88771cfa0fSJason M. Bills             std::conditional_t<std::is_signed_v<Type>, int64_t, uint64_t>;
89771cfa0fSJason M. Bills 
90771cfa0fSJason M. Bills         NumType* jsonPtr = jsonValue.get_ptr<NumType*>();
91771cfa0fSJason M. Bills         if (jsonPtr == nullptr)
92771cfa0fSJason M. Bills         {
93771cfa0fSJason M. Bills             BMCWEB_LOG_DEBUG
94771cfa0fSJason M. Bills                 << "Value for key " << key
95771cfa0fSJason M. Bills                 << " was incorrect type: " << jsonValue.type_name();
96771cfa0fSJason M. Bills             messages::propertyValueTypeError(res, jsonValue.dump(), key);
97771cfa0fSJason M. Bills             return;
98771cfa0fSJason M. Bills         }
99771cfa0fSJason M. Bills         if (*jsonPtr > std::numeric_limits<Type>::max())
100771cfa0fSJason M. Bills         {
101771cfa0fSJason M. Bills             BMCWEB_LOG_DEBUG << "Value for key " << key
102771cfa0fSJason M. Bills                              << " was out of range: " << jsonValue.type_name();
103771cfa0fSJason M. Bills             messages::propertyValueNotInList(res, jsonValue.dump(), key);
104771cfa0fSJason M. Bills             return;
105771cfa0fSJason M. Bills         }
106771cfa0fSJason M. Bills         if (*jsonPtr < std::numeric_limits<Type>::min())
107771cfa0fSJason M. Bills         {
108771cfa0fSJason M. Bills             BMCWEB_LOG_DEBUG << "Value for key " << key
109771cfa0fSJason M. Bills                              << " was out of range: " << jsonValue.type_name();
110771cfa0fSJason M. Bills             messages::propertyValueNotInList(res, jsonValue.dump(), key);
111771cfa0fSJason M. Bills             return;
112771cfa0fSJason M. Bills         }
113771cfa0fSJason M. Bills         value = static_cast<Type>(*jsonPtr);
114771cfa0fSJason M. Bills     }
115771cfa0fSJason M. Bills     else if constexpr (is_optional_v<Type>)
116771cfa0fSJason M. Bills     {
117771cfa0fSJason M. Bills         value.emplace();
118771cfa0fSJason M. Bills         unpackValue<typename Type::value_type>(jsonValue, key, res, *value);
119771cfa0fSJason M. Bills     }
1200627a2c7SEd Tanous     else if constexpr (std::is_same_v<nlohmann::json, Type>)
1210627a2c7SEd Tanous     {
1220627a2c7SEd Tanous         // Must be a complex type.  Simple types (int string etc) should be
1230627a2c7SEd Tanous         // unpacked directly
1240627a2c7SEd Tanous         if (!jsonValue.is_object() && !jsonValue.is_array())
1250627a2c7SEd Tanous         {
1260627a2c7SEd Tanous             messages::propertyValueTypeError(res, jsonValue.dump(), key);
1270627a2c7SEd Tanous             return;
1280627a2c7SEd Tanous         }
1290627a2c7SEd Tanous 
1300627a2c7SEd Tanous         value = std::move(jsonValue);
1310627a2c7SEd Tanous     }
132*318226c2SJames Feist     else if constexpr (is_std_array_v<Type>)
133*318226c2SJames Feist     {
134*318226c2SJames Feist         if (!jsonValue.is_array())
135*318226c2SJames Feist         {
136*318226c2SJames Feist             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
137*318226c2SJames Feist             return;
138*318226c2SJames Feist         }
139*318226c2SJames Feist         if (jsonValue.size() != value.size())
140*318226c2SJames Feist         {
141*318226c2SJames Feist             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
142*318226c2SJames Feist             return;
143*318226c2SJames Feist         }
144*318226c2SJames Feist         size_t index = 0;
145*318226c2SJames Feist         for (const auto& val : jsonValue.items())
146*318226c2SJames Feist         {
147*318226c2SJames Feist             unpackValue<typename Type::value_type>(val.value(), key, res,
148*318226c2SJames Feist                                                    value[index++]);
149*318226c2SJames Feist         }
150*318226c2SJames Feist     }
151b1556427SEd Tanous     else if constexpr (is_vector_v<Type>)
152b1556427SEd Tanous     {
153b1556427SEd Tanous         if (!jsonValue.is_array())
154b1556427SEd Tanous         {
155b1556427SEd Tanous             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
156b1556427SEd Tanous             return;
157b1556427SEd Tanous         }
158b1556427SEd Tanous 
159b1556427SEd Tanous         for (const auto& val : jsonValue.items())
160b1556427SEd Tanous         {
161b1556427SEd Tanous             value.emplace_back();
162b1556427SEd Tanous             unpackValue<typename Type::value_type>(val.value(), key, res,
163b1556427SEd Tanous                                                    value.back());
164b1556427SEd Tanous         }
165b1556427SEd Tanous     }
166771cfa0fSJason M. Bills     else
167771cfa0fSJason M. Bills     {
168771cfa0fSJason M. Bills         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
169771cfa0fSJason M. Bills         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
170771cfa0fSJason M. Bills         if (jsonPtr == nullptr)
171771cfa0fSJason M. Bills         {
172771cfa0fSJason M. Bills             BMCWEB_LOG_DEBUG
173771cfa0fSJason M. Bills                 << "Value for key " << key
174771cfa0fSJason M. Bills                 << " was incorrect type: " << jsonValue.type_name();
175771cfa0fSJason M. Bills             messages::propertyValueTypeError(res, jsonValue.dump(), key);
176771cfa0fSJason M. Bills             return;
177771cfa0fSJason M. Bills         }
178771cfa0fSJason M. Bills         value = std::move(*jsonPtr);
179771cfa0fSJason M. Bills     }
180771cfa0fSJason M. Bills }
181771cfa0fSJason M. Bills 
1829712f8acSEd Tanous template <size_t Count, size_t Index>
1839712f8acSEd Tanous void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
1849712f8acSEd Tanous                     crow::Response& res, std::bitset<Count>& handled)
1859712f8acSEd Tanous {
1869712f8acSEd Tanous     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
187a08b46ccSJason M. Bills     messages::propertyUnknown(res, key);
1889712f8acSEd Tanous }
1899712f8acSEd Tanous 
1909712f8acSEd Tanous template <size_t Count, size_t Index, typename ValueType,
1919712f8acSEd Tanous           typename... UnpackTypes>
1929712f8acSEd Tanous void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
1939712f8acSEd Tanous                     crow::Response& res, std::bitset<Count>& handled,
1949712f8acSEd Tanous                     const char* keyToCheck, ValueType& valueToFill,
1959712f8acSEd Tanous                     UnpackTypes&... in)
1969712f8acSEd Tanous {
1979712f8acSEd Tanous     if (key != keyToCheck)
1989712f8acSEd Tanous     {
1999712f8acSEd Tanous         readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...);
2009712f8acSEd Tanous         return;
2019712f8acSEd Tanous     }
2029712f8acSEd Tanous 
2039712f8acSEd Tanous     handled.set(Index);
2049712f8acSEd Tanous 
205771cfa0fSJason M. Bills     unpackValue<ValueType>(jsonValue, key, res, valueToFill);
2069712f8acSEd Tanous }
2079712f8acSEd Tanous 
2089712f8acSEd Tanous template <size_t Index = 0, size_t Count>
2099712f8acSEd Tanous void handleMissing(std::bitset<Count>& handled, crow::Response& res)
2109712f8acSEd Tanous {
2119712f8acSEd Tanous }
2129712f8acSEd Tanous 
2139712f8acSEd Tanous template <size_t Index = 0, size_t Count, typename ValueType,
2149712f8acSEd Tanous           typename... UnpackTypes>
2159712f8acSEd Tanous void handleMissing(std::bitset<Count>& handled, crow::Response& res,
2169712f8acSEd Tanous                    const char* key, ValueType& unused, UnpackTypes&... in)
2179712f8acSEd Tanous {
218771cfa0fSJason M. Bills     if (!handled.test(Index) && !is_optional_v<ValueType>)
2199712f8acSEd Tanous     {
220a08b46ccSJason M. Bills         messages::propertyMissing(res, key);
2219712f8acSEd Tanous     }
2229712f8acSEd Tanous     details::handleMissing<Index + 1, Count>(handled, res, in...);
2239712f8acSEd Tanous }
2249712f8acSEd Tanous } // namespace details
2259712f8acSEd Tanous 
2269712f8acSEd Tanous template <typename... UnpackTypes>
2270627a2c7SEd Tanous bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
2289712f8acSEd Tanous               UnpackTypes&... in)
2299712f8acSEd Tanous {
2309712f8acSEd Tanous     if (!jsonRequest.is_object())
2319712f8acSEd Tanous     {
2329712f8acSEd Tanous         BMCWEB_LOG_DEBUG << "Json value is not an object";
233f12894f8SJason M. Bills         messages::unrecognizedRequestBody(res);
2349712f8acSEd Tanous         return false;
2359712f8acSEd Tanous     }
2369712f8acSEd Tanous 
2379712f8acSEd Tanous     if (jsonRequest.empty())
2389712f8acSEd Tanous     {
2399712f8acSEd Tanous         BMCWEB_LOG_DEBUG << "Json value is empty";
240f12894f8SJason M. Bills         messages::emptyJSON(res);
2419712f8acSEd Tanous         return false;
2429712f8acSEd Tanous     }
2439712f8acSEd Tanous 
2449712f8acSEd Tanous     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
2459712f8acSEd Tanous     for (const auto& item : jsonRequest.items())
2469712f8acSEd Tanous     {
2479712f8acSEd Tanous         details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
2489712f8acSEd Tanous             item.key(), item.value(), res, handled, key, in...);
2499712f8acSEd Tanous     }
2509712f8acSEd Tanous 
2519712f8acSEd Tanous     details::handleMissing(handled, res, key, in...);
2529712f8acSEd Tanous 
2539712f8acSEd Tanous     return res.result() == boost::beast::http::status::ok;
2549712f8acSEd Tanous }
25577dd8813SKowalski, Kamil 
2560627a2c7SEd Tanous template <typename... UnpackTypes>
2570627a2c7SEd Tanous bool readJson(const crow::Request& req, crow::Response& res, const char* key,
2580627a2c7SEd Tanous               UnpackTypes&... in)
2590627a2c7SEd Tanous {
2600627a2c7SEd Tanous     nlohmann::json jsonRequest;
2610627a2c7SEd Tanous     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
2620627a2c7SEd Tanous     {
2630627a2c7SEd Tanous         BMCWEB_LOG_DEBUG << "Json value not readable";
2640627a2c7SEd Tanous         return false;
2650627a2c7SEd Tanous     }
2660627a2c7SEd Tanous     return readJson(jsonRequest, res, key, in...);
2670627a2c7SEd Tanous }
2680627a2c7SEd Tanous 
26977dd8813SKowalski, Kamil } // namespace json_util
27077dd8813SKowalski, Kamil } // namespace redfish
271