xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision 318226c278a18c1492b2235cb2c3b2ce5ed09900)
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 Type>
82 void unpackValue(nlohmann::json& jsonValue, const std::string& key,
83                  crow::Response& res, Type& value)
84 {
85     if constexpr (std::is_arithmetic_v<Type>)
86     {
87         using NumType =
88             std::conditional_t<std::is_signed_v<Type>, int64_t, uint64_t>;
89 
90         NumType* jsonPtr = jsonValue.get_ptr<NumType*>();
91         if (jsonPtr == nullptr)
92         {
93             BMCWEB_LOG_DEBUG
94                 << "Value for key " << key
95                 << " was incorrect type: " << jsonValue.type_name();
96             messages::propertyValueTypeError(res, jsonValue.dump(), key);
97             return;
98         }
99         if (*jsonPtr > std::numeric_limits<Type>::max())
100         {
101             BMCWEB_LOG_DEBUG << "Value for key " << key
102                              << " was out of range: " << jsonValue.type_name();
103             messages::propertyValueNotInList(res, jsonValue.dump(), key);
104             return;
105         }
106         if (*jsonPtr < std::numeric_limits<Type>::min())
107         {
108             BMCWEB_LOG_DEBUG << "Value for key " << key
109                              << " was out of range: " << jsonValue.type_name();
110             messages::propertyValueNotInList(res, jsonValue.dump(), key);
111             return;
112         }
113         value = static_cast<Type>(*jsonPtr);
114     }
115     else if constexpr (is_optional_v<Type>)
116     {
117         value.emplace();
118         unpackValue<typename Type::value_type>(jsonValue, key, res, *value);
119     }
120     else if constexpr (std::is_same_v<nlohmann::json, Type>)
121     {
122         // Must be a complex type.  Simple types (int string etc) should be
123         // unpacked directly
124         if (!jsonValue.is_object() && !jsonValue.is_array())
125         {
126             messages::propertyValueTypeError(res, jsonValue.dump(), key);
127             return;
128         }
129 
130         value = std::move(jsonValue);
131     }
132     else if constexpr (is_std_array_v<Type>)
133     {
134         if (!jsonValue.is_array())
135         {
136             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
137             return;
138         }
139         if (jsonValue.size() != value.size())
140         {
141             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
142             return;
143         }
144         size_t index = 0;
145         for (const auto& val : jsonValue.items())
146         {
147             unpackValue<typename Type::value_type>(val.value(), key, res,
148                                                    value[index++]);
149         }
150     }
151     else if constexpr (is_vector_v<Type>)
152     {
153         if (!jsonValue.is_array())
154         {
155             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
156             return;
157         }
158 
159         for (const auto& val : jsonValue.items())
160         {
161             value.emplace_back();
162             unpackValue<typename Type::value_type>(val.value(), key, res,
163                                                    value.back());
164         }
165     }
166     else
167     {
168         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
169         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
170         if (jsonPtr == nullptr)
171         {
172             BMCWEB_LOG_DEBUG
173                 << "Value for key " << key
174                 << " was incorrect type: " << jsonValue.type_name();
175             messages::propertyValueTypeError(res, jsonValue.dump(), key);
176             return;
177         }
178         value = std::move(*jsonPtr);
179     }
180 }
181 
182 template <size_t Count, size_t Index>
183 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
184                     crow::Response& res, std::bitset<Count>& handled)
185 {
186     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
187     messages::propertyUnknown(res, key);
188 }
189 
190 template <size_t Count, size_t Index, typename ValueType,
191           typename... UnpackTypes>
192 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
193                     crow::Response& res, std::bitset<Count>& handled,
194                     const char* keyToCheck, ValueType& valueToFill,
195                     UnpackTypes&... in)
196 {
197     if (key != keyToCheck)
198     {
199         readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...);
200         return;
201     }
202 
203     handled.set(Index);
204 
205     unpackValue<ValueType>(jsonValue, key, res, valueToFill);
206 }
207 
208 template <size_t Index = 0, size_t Count>
209 void handleMissing(std::bitset<Count>& handled, crow::Response& res)
210 {
211 }
212 
213 template <size_t Index = 0, size_t Count, typename ValueType,
214           typename... UnpackTypes>
215 void handleMissing(std::bitset<Count>& handled, crow::Response& res,
216                    const char* key, ValueType& unused, UnpackTypes&... in)
217 {
218     if (!handled.test(Index) && !is_optional_v<ValueType>)
219     {
220         messages::propertyMissing(res, key);
221     }
222     details::handleMissing<Index + 1, Count>(handled, res, in...);
223 }
224 } // namespace details
225 
226 template <typename... UnpackTypes>
227 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
228               UnpackTypes&... in)
229 {
230     if (!jsonRequest.is_object())
231     {
232         BMCWEB_LOG_DEBUG << "Json value is not an object";
233         messages::unrecognizedRequestBody(res);
234         return false;
235     }
236 
237     if (jsonRequest.empty())
238     {
239         BMCWEB_LOG_DEBUG << "Json value is empty";
240         messages::emptyJSON(res);
241         return false;
242     }
243 
244     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
245     for (const auto& item : jsonRequest.items())
246     {
247         details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
248             item.key(), item.value(), res, handled, key, in...);
249     }
250 
251     details::handleMissing(handled, res, key, in...);
252 
253     return res.result() == boost::beast::http::status::ok;
254 }
255 
256 template <typename... UnpackTypes>
257 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
258               UnpackTypes&... in)
259 {
260     nlohmann::json jsonRequest;
261     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
262     {
263         BMCWEB_LOG_DEBUG << "Json value not readable";
264         return false;
265     }
266     return readJson(jsonRequest, res, key, in...);
267 }
268 
269 } // namespace json_util
270 } // namespace redfish
271