xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision a6acbb3187910fc399152262328ee05e64486da8)
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 void unpackValue(nlohmann::json& jsonValue, const std::string& key,
122                  crow::Response& res, Type& value)
123 {
124     if constexpr (std::is_floating_point_v<Type>)
125     {
126         double helper = 0;
127         double* jsonPtr = jsonValue.get_ptr<double*>();
128 
129         if (jsonPtr == nullptr)
130         {
131             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
132             if (intPtr != nullptr)
133             {
134                 helper = static_cast<double>(*intPtr);
135                 jsonPtr = &helper;
136             }
137         }
138         if (!checkRange<Type>(jsonPtr, key, jsonValue, res))
139         {
140             return;
141         }
142         value = static_cast<Type>(*jsonPtr);
143     }
144 
145     else if constexpr (std::is_signed_v<Type>)
146     {
147         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
148         if (!checkRange<Type>(jsonPtr, key, jsonValue, res))
149         {
150             return;
151         }
152         value = static_cast<Type>(*jsonPtr);
153     }
154 
155     else if constexpr (std::is_unsigned_v<Type>)
156     {
157         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
158         if (!checkRange<Type>(jsonPtr, key, jsonValue, res))
159         {
160             return;
161         }
162         value = static_cast<Type>(*jsonPtr);
163     }
164 
165     else if constexpr (is_optional_v<Type>)
166     {
167         value.emplace();
168         unpackValue<typename Type::value_type>(jsonValue, key, res, *value);
169     }
170     else if constexpr (std::is_same_v<nlohmann::json, Type>)
171     {
172         // Must be a complex type.  Simple types (int string etc) should be
173         // unpacked directly
174         if (!jsonValue.is_object() && !jsonValue.is_array())
175         {
176             messages::propertyValueTypeError(res, jsonValue.dump(), key);
177             return;
178         }
179 
180         value = std::move(jsonValue);
181     }
182     else if constexpr (is_std_array_v<Type>)
183     {
184         if (!jsonValue.is_array())
185         {
186             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
187             return;
188         }
189         if (jsonValue.size() != value.size())
190         {
191             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
192             return;
193         }
194         size_t index = 0;
195         for (const auto& val : jsonValue.items())
196         {
197             unpackValue<typename Type::value_type>(val.value(), key, res,
198                                                    value[index++]);
199         }
200     }
201     else if constexpr (is_vector_v<Type>)
202     {
203         if (!jsonValue.is_array())
204         {
205             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
206             return;
207         }
208 
209         for (const auto& val : jsonValue.items())
210         {
211             value.emplace_back();
212             unpackValue<typename Type::value_type>(val.value(), key, res,
213                                                    value.back());
214         }
215     }
216     else
217     {
218         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
219         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
220         if (jsonPtr == nullptr)
221         {
222             BMCWEB_LOG_DEBUG
223                 << "Value for key " << key
224                 << " was incorrect type: " << jsonValue.type_name();
225             messages::propertyValueTypeError(res, jsonValue.dump(), key);
226             return;
227         }
228         value = std::move(*jsonPtr);
229     }
230 }
231 
232 template <size_t Count, size_t Index>
233 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
234                     crow::Response& res, std::bitset<Count>& handled)
235 {
236     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
237     messages::propertyUnknown(res, key);
238 }
239 
240 template <size_t Count, size_t Index, typename ValueType,
241           typename... UnpackTypes>
242 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
243                     crow::Response& res, std::bitset<Count>& handled,
244                     const char* keyToCheck, ValueType& valueToFill,
245                     UnpackTypes&... in)
246 {
247     if (key != keyToCheck)
248     {
249         readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...);
250         return;
251     }
252 
253     handled.set(Index);
254 
255     unpackValue<ValueType>(jsonValue, key, res, valueToFill);
256 }
257 
258 template <size_t Index = 0, size_t Count>
259 void handleMissing(std::bitset<Count>& handled, crow::Response& res)
260 {
261 }
262 
263 template <size_t Index = 0, size_t Count, typename ValueType,
264           typename... UnpackTypes>
265 void handleMissing(std::bitset<Count>& handled, crow::Response& res,
266                    const char* key, ValueType& unused, UnpackTypes&... in)
267 {
268     if (!handled.test(Index) && !is_optional_v<ValueType>)
269     {
270         messages::propertyMissing(res, key);
271     }
272     details::handleMissing<Index + 1, Count>(handled, res, in...);
273 }
274 } // namespace details
275 
276 template <typename... UnpackTypes>
277 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
278               UnpackTypes&... in)
279 {
280     if (!jsonRequest.is_object())
281     {
282         BMCWEB_LOG_DEBUG << "Json value is not an object";
283         messages::unrecognizedRequestBody(res);
284         return false;
285     }
286 
287     if (jsonRequest.empty())
288     {
289         BMCWEB_LOG_DEBUG << "Json value is empty";
290         messages::emptyJSON(res);
291         return false;
292     }
293 
294     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
295     for (const auto& item : jsonRequest.items())
296     {
297         details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
298             item.key(), item.value(), res, handled, key, in...);
299     }
300 
301     details::handleMissing(handled, res, key, in...);
302 
303     return res.result() == boost::beast::http::status::ok;
304 }
305 
306 template <typename... UnpackTypes>
307 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
308               UnpackTypes&... in)
309 {
310     nlohmann::json jsonRequest;
311     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
312     {
313         BMCWEB_LOG_DEBUG << "Json value not readable";
314         return false;
315     }
316     return readJson(jsonRequest, res, key, in...);
317 }
318 
319 } // namespace json_util
320 } // namespace redfish
321