xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision 9712f8ac42746e65f32c2bc3de0835846652568e)
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 namespace redfish
25 {
26 
27 namespace json_util
28 {
29 
30 /**
31  * @brief Defines JSON utils operation status
32  */
33 enum class Result
34 {
35     SUCCESS,
36     NOT_EXIST,
37     WRONG_TYPE,
38     NULL_POINTER
39 };
40 
41 /**
42  * @brief Describes JSON utils messages requirement
43  */
44 enum class MessageSetting
45 {
46     NONE = 0x0,      ///< No messages will be added
47     MISSING = 0x1,   ///< PropertyMissing message will be added
48     TYPE_ERROR = 0x2 ///< PropertyValueTypeError message will be added
49 };
50 
51 /**
52  * @brief Wrapper function for extracting string from JSON object without
53  *        throwing exceptions
54  *
55  * @param[in]  fieldName   Name of requested field
56  * @param[in]  json        JSON object from which field should be extracted
57  * @param[out] output      Variable to which extracted will be written in case
58  *                         of success
59  *
60  * @return Result informing about operation status, output will be
61  *         written only in case of Result::SUCCESS
62  */
63 Result getString(const char* fieldName, const nlohmann::json& json,
64                  const std::string*& output);
65 
66 /**
67  * @brief Wrapper function for extracting object from JSON object without
68  *        throwing exceptions
69  *
70  * @param[in]  fieldName   Name of requested field
71  * @param[in]  json        JSON object from which field should be extracted
72  * @param[out] output      Variable to which extracted will be written in case
73  *                         of success
74  *
75  * @return Result informing about operation status, output will be
76  *         written only in case of Result::SUCCESS
77  */
78 Result getObject(const char* fieldName, const nlohmann::json& json,
79                  nlohmann::json* output);
80 
81 /**
82  * @brief Wrapper function for extracting array from JSON object without
83  *        throwing exceptions
84  *
85  * @param[in]  fieldName   Name of requested field
86  * @param[in]  json        JSON object from which field should be extracted
87  * @param[out] output      Variable to which extracted will be written in case
88  *                         of success
89  *
90  * @return Result informing about operation status, output will be
91  *         written only in case of Result::SUCCESS
92  */
93 Result getArray(const char* fieldName, const nlohmann::json& json,
94                 nlohmann::json* output);
95 
96 /**
97  * @brief Wrapper function for extracting int from JSON object without
98  *        throwing exceptions
99  *
100  * @param[in]  fieldName   Name of requested field
101  * @param[in]  json        JSON object from which field should be extracted
102  * @param[out] output      Variable to which extracted will be written in case
103  *                         of success
104  *
105  * @return Result informing about operation status, output will be
106  *         written only in case of Result::SUCCESS
107  */
108 Result getInt(const char* fieldName, const nlohmann::json& json,
109               int64_t& output);
110 
111 /**
112  * @brief Wrapper function for extracting uint from JSON object without
113  *        throwing exceptions
114  *
115  * @param[in]  fieldName   Name of requested field
116  * @param[in]  json        JSON object from which field should be extracted
117  * @param[out] output      Variable to which extracted will be written in case
118  *                         of success
119  *
120  * @return Result informing about operation status, output will be
121  *         written only in case of Result::SUCCESS
122  */
123 Result getUnsigned(const char* fieldName, const nlohmann::json& json,
124                    uint64_t& output);
125 
126 /**
127  * @brief Wrapper function for extracting bool from JSON object without
128  *        throwing exceptions
129  *
130  * @param[in]  fieldName   Name of requested field
131  * @param[in]  json        JSON object from which field should be extracted
132  * @param[out] output      Variable to which extracted will be written in case
133  *                         of success
134  *
135  * @return Result informing about operation status, output will be
136  *         written only in case of Result::SUCCESS
137  */
138 Result getBool(const char* fieldName, const nlohmann::json& json, bool& output);
139 
140 /**
141  * @brief Wrapper function for extracting float from JSON object without
142  *        throwing exceptions (nlohmann stores JSON floats as C++ doubles)
143  *
144  * @param[in]  fieldName   Name of requested field
145  * @param[in]  json        JSON object from which field should be extracted
146  * @param[out] output      Variable to which extracted will be written in case
147  *                         of success
148  *
149  * @return Result informing about operation status, output will be
150  *         written only in case of Result::SUCCESS
151  */
152 Result getDouble(const char* fieldName, const nlohmann::json& json,
153                  double& output);
154 
155 /**
156  * @brief Wrapper function for extracting string from JSON object without
157  *        throwing exceptions
158  *
159  * @param[in]  fieldName   Name of requested field
160  * @param[in]  json        JSON object from which field should be extracted
161  * @param[out] output      Variable to which extracted will be written in case
162  *                         of success
163  * @param[in]  msgCfgMap   Map for message addition settings
164  * @param[out] msgJson     JSON to which error messages will be added
165  * @param[in]  fieldPath   Field path in JSON
166  *
167  * @return Result informing about operation status, output will be
168  *         written only in case of Result::SUCCESS
169  */
170 Result getString(const char* fieldName, const nlohmann::json& json,
171                  const std::string*& output, uint8_t msgCfgMap,
172                  nlohmann::json& msgJson, const std::string&& fieldPath);
173 
174 /**
175  * @brief Wrapper function for extracting object from JSON object without
176  *        throwing exceptions
177  *
178  * @param[in]  fieldName   Name of requested field
179  * @param[in]  json        JSON object from which field should be extracted
180  * @param[out] output      Variable to which extracted will be written in case
181  *                         of success
182  * @param[in]  msgCfgMap   Map for message addition settings
183  * @param[out] msgJson     JSON to which error messages will be added
184  * @param[in]  fieldPath   Field path in JSON
185  *
186  * @return Result informing about operation status, output will be
187  *         written only in case of Result::SUCCESS
188  */
189 Result getObject(const char* fieldName, const nlohmann::json& json,
190                  nlohmann::json* output, uint8_t msgCfgMap,
191                  nlohmann::json& msgJson, const std::string&& fieldPath);
192 
193 /**
194  * @brief Wrapper function for extracting array from JSON object without
195  *        throwing exceptions
196  *
197  * @param[in]  fieldName   Name of requested field
198  * @param[in]  json        JSON object from which field should be extracted
199  * @param[out] output      Variable to which extracted will be written in case
200  *                         of success
201  * @param[in]  msgCfgMap   Map for message addition settings
202  * @param[out] msgJson     JSON to which error messages will be added
203  * @param[in]  fieldPath   Field path in JSON
204  *
205  * @return Result informing about operation status, output will be
206  *         written only in case of Result::SUCCESS
207  */
208 Result getArray(const char* fieldName, const nlohmann::json& json,
209                 nlohmann::json* output, uint8_t msgCfgMap,
210                 nlohmann::json& msgJson, const std::string&& fieldPath);
211 
212 /**
213  * @brief Wrapper function for extracting int from JSON object without
214  *        throwing exceptions
215  *
216  * @param[in]  fieldName   Name of requested field
217  * @param[in]  json        JSON object from which field should be extracted
218  * @param[out] output      Variable to which extracted will be written in case
219  *                         of success
220  * @param[in]  msgCfgMap   Map for message addition settings
221  * @param[out] msgJson     JSON to which error messages will be added
222  * @param[in]  fieldPath   Field path in JSON
223  *
224  * @return Result informing about operation status, output will be
225  *         written only in case of Result::SUCCESS
226  */
227 Result getInt(const char* fieldName, const nlohmann::json& json,
228               int64_t& output, uint8_t msgCfgMap, nlohmann::json& msgJson,
229               const std::string&& fieldPath);
230 
231 /**
232  * @brief Wrapper function for extracting uint from JSON object without
233  *        throwing exceptions
234  *
235  * @param[in]  fieldName   Name of requested field
236  * @param[in]  json        JSON object from which field should be extracted
237  * @param[out] output      Variable to which extracted will be written in case
238  *                         of success
239  * @param[in]  msgCfgMap   Map for message addition settings
240  * @param[out] msgJson     JSON to which error messages will be added
241  * @param[in]  fieldPath   Field path in JSON
242  *
243  * @return Result informing about operation status, output will be
244  *         written only in case of Result::SUCCESS
245  */
246 Result getUnsigned(const char* fieldName, const nlohmann::json& json,
247                    uint64_t& output, uint8_t msgCfgMap, nlohmann::json& msgJson,
248                    const std::string&& fieldPath);
249 
250 /**
251  * @brief Wrapper function for extracting bool from JSON object without
252  *        throwing exceptions
253  *
254  * @param[in]  fieldName   Name of requested field
255  * @param[in]  json        JSON object from which field should be extracted
256  * @param[out] output      Variable to which extracted will be written in case
257  *                         of success
258  * @param[in]  msgCfgMap   Map for message addition settings
259  * @param[out] msgJson     JSON to which error messages will be added
260  * @param[in]  fieldPath   Field path in JSON
261  *
262  * @return Result informing about operation status, output will be
263  *         written only in case of Result::SUCCESS
264  */
265 Result getBool(const char* fieldName, const nlohmann::json& json, bool& output,
266                uint8_t msgCfgMap, nlohmann::json& msgJson,
267                const std::string&& fieldPath);
268 
269 /**
270  * @brief Wrapper function for extracting float from JSON object without
271  *        throwing exceptions (nlohmann stores JSON floats as C++ doubles)
272  *
273  * @param[in]  fieldName   Name of requested field
274  * @param[in]  json        JSON object from which field should be extracted
275  * @param[out] output      Variable to which extracted will be written in case
276  * of success
277  * @param[in]  msgCfgMap   Map for message addition settings
278  * @param[out] msgJson     JSON to which error messages will be added
279  * @param[in]  fieldPath   Field path in JSON
280  *
281  * @return Result informing about operation status, output will be
282  *         written only in case of Result::SUCCESS
283  */
284 Result getDouble(const char* fieldName, const nlohmann::json& json,
285                  double& output, uint8_t msgCfgMap, nlohmann::json& msgJson,
286                  const std::string&& fieldPath);
287 
288 /**
289  * @brief Processes request to extract JSON from its body. If it fails, adds
290  *       MalformedJSON message to response and ends it.
291  *
292  * @param[io]  res       Response object
293  * @param[in]  req       Request object
294  * @param[out] reqJson   JSON object extracted from request's body
295  *
296  * @return true if JSON is valid, false when JSON is invalid and response has
297  *         been filled with message and ended.
298  */
299 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
300                             nlohmann::json& reqJson);
301 namespace details
302 {
303 template <typename Type> struct unpackValue
304 {
305     using isRequired = std::true_type;
306     using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
307 };
308 
309 template <typename OptionalType>
310 struct unpackValue<boost::optional<OptionalType>>
311 {
312     using isRequired = std::false_type;
313     using JsonType = std::add_const_t<std::add_pointer_t<OptionalType>>;
314 };
315 
316 template <size_t Count, size_t Index>
317 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
318                     crow::Response& res, std::bitset<Count>& handled)
319 {
320     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
321     messages::addMessageToErrorJson(res.jsonValue,
322                                     messages::propertyUnknown(key));
323     res.result(boost::beast::http::status::bad_request);
324 }
325 
326 template <size_t Count, size_t Index, typename ValueType,
327           typename... UnpackTypes>
328 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
329                     crow::Response& res, std::bitset<Count>& handled,
330                     const char* keyToCheck, ValueType& valueToFill,
331                     UnpackTypes&... in)
332 {
333     if (key != keyToCheck)
334     {
335         readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...);
336         return;
337     }
338 
339     handled.set(Index);
340 
341     using UnpackType = typename unpackValue<ValueType>::JsonType;
342     UnpackType value = jsonValue.get_ptr<UnpackType>();
343     if (value == nullptr)
344     {
345         BMCWEB_LOG_DEBUG << "Value for key " << key
346                          << " was incorrect type: " << jsonValue.type_name();
347         messages::addMessageToErrorJson(
348             res.jsonValue,
349             messages::propertyValueTypeError(jsonValue.dump(), key));
350         res.result(boost::beast::http::status::bad_request);
351 
352         return;
353     }
354 
355     valueToFill = *value;
356 }
357 
358 template <size_t Index = 0, size_t Count>
359 void handleMissing(std::bitset<Count>& handled, crow::Response& res)
360 {
361 }
362 
363 template <size_t Index = 0, size_t Count, typename ValueType,
364           typename... UnpackTypes>
365 void handleMissing(std::bitset<Count>& handled, crow::Response& res,
366                    const char* key, ValueType& unused, UnpackTypes&... in)
367 {
368     if (!handled.test(Index) && unpackValue<ValueType>::isRequired::value)
369     {
370         messages::addMessageToErrorJson(res.jsonValue,
371                                         messages::propertyMissing(key));
372         res.result(boost::beast::http::status::bad_request);
373     }
374     details::handleMissing<Index + 1, Count>(handled, res, in...);
375 }
376 } // namespace details
377 
378 template <typename... UnpackTypes>
379 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
380               UnpackTypes&... in)
381 {
382     nlohmann::json jsonRequest;
383     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
384     {
385         BMCWEB_LOG_DEBUG << "Json value not readable";
386         return false;
387     }
388     if (!jsonRequest.is_object())
389     {
390         BMCWEB_LOG_DEBUG << "Json value is not an object";
391         messages::addMessageToErrorJson(res.jsonValue,
392                                         messages::unrecognizedRequestBody());
393         res.result(boost::beast::http::status::bad_request);
394         return false;
395     }
396 
397     if (jsonRequest.empty())
398     {
399         BMCWEB_LOG_DEBUG << "Json value is empty";
400         messages::addMessageToErrorJson(res.jsonValue, messages::emptyJSON());
401         res.result(boost::beast::http::status::bad_request);
402         return false;
403     }
404 
405     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
406     for (const auto& item : jsonRequest.items())
407     {
408         details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
409             item.key(), item.value(), res, handled, key, in...);
410     }
411 
412     if (!handled.all())
413     {
414         details::handleMissing(handled, res, key, in...);
415 
416         return false;
417     }
418     return res.result() == boost::beast::http::status::ok;
419 }
420 
421 } // namespace json_util
422 } // namespace redfish
423