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