xref: /openbmc/bmcweb/redfish-core/include/utils/json_utils.hpp (revision af6298daa55d41d5a47459215b96ca5ec079dfb4)
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 <http_request.h>
19 #include <http_response.h>
20 
21 #include <error_messages.hpp>
22 #include <nlohmann/json.hpp>
23 
24 #include <bitset>
25 
26 namespace redfish
27 {
28 
29 namespace json_util
30 {
31 
32 /**
33  * @brief Processes request to extract JSON from its body. If it fails, adds
34  *       MalformedJSON message to response and ends it.
35  *
36  * @param[io]  res       Response object
37  * @param[in]  req       Request object
38  * @param[out] reqJson   JSON object extracted from request's body
39  *
40  * @return true if JSON is valid, false when JSON is invalid and response has
41  *         been filled with message and ended.
42  */
43 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
44                             nlohmann::json& reqJson);
45 namespace details
46 {
47 
48 template <typename Type>
49 struct IsOptional : std::false_type
50 {};
51 
52 template <typename Type>
53 struct IsOptional<std::optional<Type>> : std::true_type
54 {};
55 
56 template <typename Type>
57 struct IsVector : std::false_type
58 {};
59 
60 template <typename Type>
61 struct IsVector<std::vector<Type>> : std::true_type
62 {};
63 
64 template <typename Type>
65 struct IsStdArray : std::false_type
66 {};
67 
68 template <typename Type, std::size_t size>
69 struct IsStdArray<std::array<Type, size>> : std::true_type
70 {};
71 
72 enum class UnpackErrorCode
73 {
74     success,
75     invalidType,
76     outOfRange
77 };
78 
79 template <typename ToType, typename FromType>
80 bool checkRange(const FromType& from, const std::string& key)
81 {
82     if (from > std::numeric_limits<ToType>::max())
83     {
84         BMCWEB_LOG_DEBUG << "Value for key " << key
85                          << " was greater than max: " << __PRETTY_FUNCTION__;
86         return false;
87     }
88     if (from < std::numeric_limits<ToType>::lowest())
89     {
90         BMCWEB_LOG_DEBUG << "Value for key " << key
91                          << " was less than min: " << __PRETTY_FUNCTION__;
92         return false;
93     }
94     if constexpr (std::is_floating_point_v<ToType>)
95     {
96         if (std::isnan(from))
97         {
98             BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN";
99             return false;
100         }
101     }
102 
103     return true;
104 }
105 
106 template <typename Type>
107 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
108                                          const std::string& key, Type& value)
109 {
110     UnpackErrorCode ret = UnpackErrorCode::success;
111 
112     if constexpr (std::is_floating_point_v<Type>)
113     {
114         double helper = 0;
115         double* jsonPtr = jsonValue.get_ptr<double*>();
116 
117         if (jsonPtr == nullptr)
118         {
119             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
120             if (intPtr != nullptr)
121             {
122                 helper = static_cast<double>(*intPtr);
123                 jsonPtr = &helper;
124             }
125         }
126         if (jsonPtr == nullptr)
127         {
128             return UnpackErrorCode::invalidType;
129         }
130         if (!checkRange<Type>(*jsonPtr, key))
131         {
132             return UnpackErrorCode::outOfRange;
133         }
134         value = static_cast<Type>(*jsonPtr);
135     }
136 
137     else if constexpr (std::is_signed_v<Type>)
138     {
139         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
140         if (jsonPtr == nullptr)
141         {
142             return UnpackErrorCode::invalidType;
143         }
144         if (!checkRange<Type>(*jsonPtr, key))
145         {
146             return UnpackErrorCode::outOfRange;
147         }
148         value = static_cast<Type>(*jsonPtr);
149     }
150 
151     else if constexpr ((std::is_unsigned_v<Type>)&&(
152                            !std::is_same_v<bool, Type>))
153     {
154         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
155         if (jsonPtr == nullptr)
156         {
157             return UnpackErrorCode::invalidType;
158         }
159         if (!checkRange<Type>(*jsonPtr, key))
160         {
161             return UnpackErrorCode::outOfRange;
162         }
163         value = static_cast<Type>(*jsonPtr);
164     }
165 
166     else if constexpr (std::is_same_v<nlohmann::json, Type>)
167     {
168         // Must be a complex type.  Simple types (int string etc) should be
169         // unpacked directly
170         if (!jsonValue.is_object() && !jsonValue.is_array() &&
171             !jsonValue.is_null())
172         {
173             return UnpackErrorCode::invalidType;
174         }
175 
176         value = std::move(jsonValue);
177     }
178     else
179     {
180         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
181         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
182         if (jsonPtr == nullptr)
183         {
184             BMCWEB_LOG_DEBUG
185                 << "Value for key " << key
186                 << " was incorrect type: " << jsonValue.type_name();
187             return UnpackErrorCode::invalidType;
188         }
189         value = std::move(*jsonPtr);
190     }
191     return ret;
192 }
193 
194 template <typename Type>
195 bool unpackValue(nlohmann::json& jsonValue, const std::string& key,
196                  crow::Response& res, Type& value)
197 {
198     bool ret = true;
199 
200     if constexpr (IsOptional<Type>::value)
201     {
202         value.emplace();
203         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
204                                                      *value) &&
205               ret;
206     }
207     else if constexpr (IsStdArray<Type>::value)
208     {
209         if (!jsonValue.is_array())
210         {
211             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
212             return false;
213         }
214         if (jsonValue.size() != value.size())
215         {
216             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
217             return false;
218         }
219         size_t index = 0;
220         for (const auto& val : jsonValue.items())
221         {
222             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
223                                                          value[index++]) &&
224                   ret;
225         }
226     }
227     else if constexpr (IsVector<Type>::value)
228     {
229         if (!jsonValue.is_array())
230         {
231             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
232             return false;
233         }
234 
235         for (const auto& val : jsonValue.items())
236         {
237             value.emplace_back();
238             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
239                                                          value.back()) &&
240                   ret;
241         }
242     }
243     else
244     {
245         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
246         if (ec != UnpackErrorCode::success)
247         {
248             if (ec == UnpackErrorCode::invalidType)
249             {
250                 messages::propertyValueTypeError(res, jsonValue.dump(), key);
251             }
252             else if (ec == UnpackErrorCode::outOfRange)
253             {
254                 messages::propertyValueNotInList(res, jsonValue.dump(), key);
255             }
256             return false;
257         }
258     }
259 
260     return ret;
261 }
262 
263 template <typename Type>
264 bool unpackValue(nlohmann::json& jsonValue, const std::string& key, Type& value)
265 {
266     bool ret = true;
267     if constexpr (IsOptional<Type>::value)
268     {
269         value.emplace();
270         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
271               ret;
272     }
273     else if constexpr (IsStdArray<Type>::value)
274     {
275         if (!jsonValue.is_array())
276         {
277             return false;
278         }
279         if (jsonValue.size() != value.size())
280         {
281             return false;
282         }
283         size_t index = 0;
284         for (const auto& val : jsonValue.items())
285         {
286             ret = unpackValue<typename Type::value_type>(val.value(), key,
287                                                          value[index++]) &&
288                   ret;
289         }
290     }
291     else if constexpr (IsVector<Type>::value)
292     {
293         if (!jsonValue.is_array())
294         {
295             return false;
296         }
297 
298         for (const auto& val : jsonValue.items())
299         {
300             value.emplace_back();
301             ret = unpackValue<typename Type::value_type>(val.value(), key,
302                                                          value.back()) &&
303                   ret;
304         }
305     }
306     else
307     {
308         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
309         if (ec != UnpackErrorCode::success)
310         {
311             return false;
312         }
313     }
314 
315     return ret;
316 }
317 
318 template <size_t Count, size_t Index>
319 bool readJsonValues(const std::string& key, nlohmann::json&,
320                     crow::Response& res, std::bitset<Count>&)
321 {
322     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
323     messages::propertyUnknown(res, key);
324     return false;
325 }
326 
327 template <size_t Count, size_t Index, typename ValueType,
328           typename... UnpackTypes>
329 bool readJsonValues(const std::string& key, nlohmann::json& jsonValue,
330                     crow::Response& res, std::bitset<Count>& handled,
331                     const char* keyToCheck, ValueType& valueToFill,
332                     UnpackTypes&... in)
333 {
334     bool ret = true;
335     if (key != keyToCheck)
336     {
337         ret = readJsonValues<Count, Index + 1>(key, jsonValue, res, handled,
338                                                in...) &&
339               ret;
340         return ret;
341     }
342 
343     handled.set(Index);
344 
345     return unpackValue<ValueType>(jsonValue, key, res, valueToFill) && ret;
346 }
347 
348 template <size_t Index = 0, size_t Count>
349 bool handleMissing(std::bitset<Count>&, crow::Response&)
350 {
351     return true;
352 }
353 
354 template <size_t Index = 0, size_t Count, typename ValueType,
355           typename... UnpackTypes>
356 bool handleMissing(std::bitset<Count>& handled, crow::Response& res,
357                    const char* key, ValueType&, UnpackTypes&... in)
358 {
359     bool ret = true;
360     if (!handled.test(Index) && !IsOptional<ValueType>::value)
361     {
362         ret = false;
363         messages::propertyMissing(res, key);
364     }
365     return details::handleMissing<Index + 1, Count>(handled, res, in...) && ret;
366 }
367 } // namespace details
368 
369 template <typename... UnpackTypes>
370 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
371               UnpackTypes&... in)
372 {
373     bool result = true;
374     if (!jsonRequest.is_object())
375     {
376         BMCWEB_LOG_DEBUG << "Json value is not an object";
377         messages::unrecognizedRequestBody(res);
378         return false;
379     }
380 
381     if (jsonRequest.empty())
382     {
383         BMCWEB_LOG_DEBUG << "Json value is empty";
384         messages::emptyJSON(res);
385         return false;
386     }
387 
388     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
389     for (const auto& item : jsonRequest.items())
390     {
391         result =
392             details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
393                 item.key(), item.value(), res, handled, key, in...) &&
394             result;
395     }
396 
397     BMCWEB_LOG_DEBUG << "JSON result is: " << result;
398 
399     return details::handleMissing(handled, res, key, in...) && result;
400 }
401 
402 template <typename... UnpackTypes>
403 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
404               UnpackTypes&... in)
405 {
406     nlohmann::json jsonRequest;
407     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
408     {
409         BMCWEB_LOG_DEBUG << "Json value not readable";
410         return false;
411     }
412     return readJson(jsonRequest, res, key, in...);
413 }
414 
415 template <typename Type>
416 bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key,
417                             Type& value)
418 {
419     nlohmann::json jsonValue = jsonData[key];
420     if (jsonValue.is_null())
421     {
422         BMCWEB_LOG_DEBUG << "Key " << key << " not exist";
423         return false;
424     }
425 
426     return details::unpackValue(jsonValue, key, value);
427 }
428 
429 } // namespace json_util
430 } // namespace redfish
431