xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision a24526dcf9ad8de2f0bd9dbd5fc746a130351a22)
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 Processes request to extract JSON from its body. If it fails, adds
32  *       MalformedJSON message to response and ends it.
33  *
34  * @param[io]  res       Response object
35  * @param[in]  req       Request object
36  * @param[out] reqJson   JSON object extracted from request's body
37  *
38  * @return true if JSON is valid, false when JSON is invalid and response has
39  *         been filled with message and ended.
40  */
41 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
42                             nlohmann::json& reqJson);
43 namespace details
44 {
45 
46 template <typename Type> struct is_optional : std::false_type
47 {
48 };
49 
50 template <typename Type>
51 struct is_optional<std::optional<Type>> : std::true_type
52 {
53 };
54 
55 template <typename Type>
56 constexpr bool is_optional_v = is_optional<Type>::value;
57 
58 template <typename Type> struct is_vector : std::false_type
59 {
60 };
61 
62 template <typename Type> struct is_vector<std::vector<Type>> : std::true_type
63 {
64 };
65 
66 template <typename Type> constexpr bool is_vector_v = is_vector<Type>::value;
67 
68 template <typename Type>
69 void unpackValue(nlohmann::json& jsonValue, const std::string& key,
70                  crow::Response& res, Type& value)
71 {
72     if constexpr (std::is_arithmetic_v<Type>)
73     {
74         using NumType =
75             std::conditional_t<std::is_signed_v<Type>, int64_t, uint64_t>;
76 
77         NumType* jsonPtr = jsonValue.get_ptr<NumType*>();
78         if (jsonPtr == nullptr)
79         {
80             BMCWEB_LOG_DEBUG
81                 << "Value for key " << key
82                 << " was incorrect type: " << jsonValue.type_name();
83             messages::propertyValueTypeError(res, jsonValue.dump(), key);
84             return;
85         }
86         if (*jsonPtr > std::numeric_limits<Type>::max())
87         {
88             BMCWEB_LOG_DEBUG << "Value for key " << key
89                              << " was out of range: " << jsonValue.type_name();
90             messages::propertyValueNotInList(res, jsonValue.dump(), key);
91             return;
92         }
93         if (*jsonPtr < std::numeric_limits<Type>::min())
94         {
95             BMCWEB_LOG_DEBUG << "Value for key " << key
96                              << " was out of range: " << jsonValue.type_name();
97             messages::propertyValueNotInList(res, jsonValue.dump(), key);
98             return;
99         }
100         value = static_cast<Type>(*jsonPtr);
101     }
102     else if constexpr (is_optional_v<Type>)
103     {
104         value.emplace();
105         unpackValue<typename Type::value_type>(jsonValue, key, res, *value);
106     }
107     else if constexpr (is_vector_v<Type>)
108     {
109         if (!jsonValue.is_array())
110         {
111             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
112             return;
113         }
114 
115         for (const auto& val : jsonValue.items())
116         {
117             value.emplace_back();
118             unpackValue<typename Type::value_type>(val.value(), key, res,
119                                                    value.back());
120         }
121     }
122     else
123     {
124         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
125         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
126         if (jsonPtr == nullptr)
127         {
128             BMCWEB_LOG_DEBUG
129                 << "Value for key " << key
130                 << " was incorrect type: " << jsonValue.type_name();
131             messages::propertyValueTypeError(res, jsonValue.dump(), key);
132             return;
133         }
134         value = std::move(*jsonPtr);
135     }
136 }
137 
138 template <size_t Count, size_t Index>
139 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
140                     crow::Response& res, std::bitset<Count>& handled)
141 {
142     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
143     messages::propertyUnknown(res, key);
144 }
145 
146 template <size_t Count, size_t Index, typename ValueType,
147           typename... UnpackTypes>
148 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
149                     crow::Response& res, std::bitset<Count>& handled,
150                     const char* keyToCheck, ValueType& valueToFill,
151                     UnpackTypes&... in)
152 {
153     if (key != keyToCheck)
154     {
155         readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...);
156         return;
157     }
158 
159     handled.set(Index);
160 
161     unpackValue<ValueType>(jsonValue, key, res, valueToFill);
162 }
163 
164 template <size_t Index = 0, size_t Count>
165 void handleMissing(std::bitset<Count>& handled, crow::Response& res)
166 {
167 }
168 
169 template <size_t Index = 0, size_t Count, typename ValueType,
170           typename... UnpackTypes>
171 void handleMissing(std::bitset<Count>& handled, crow::Response& res,
172                    const char* key, ValueType& unused, UnpackTypes&... in)
173 {
174     if (!handled.test(Index) && !is_optional_v<ValueType>)
175     {
176         messages::propertyMissing(res, key);
177     }
178     details::handleMissing<Index + 1, Count>(handled, res, in...);
179 }
180 } // namespace details
181 
182 template <typename... UnpackTypes>
183 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
184               UnpackTypes&... in)
185 {
186     nlohmann::json jsonRequest;
187     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
188     {
189         BMCWEB_LOG_DEBUG << "Json value not readable";
190         return false;
191     }
192     if (!jsonRequest.is_object())
193     {
194         BMCWEB_LOG_DEBUG << "Json value is not an object";
195         messages::unrecognizedRequestBody(res);
196         return false;
197     }
198 
199     if (jsonRequest.empty())
200     {
201         BMCWEB_LOG_DEBUG << "Json value is empty";
202         messages::emptyJSON(res);
203         return false;
204     }
205 
206     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
207     for (const auto& item : jsonRequest.items())
208     {
209         details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
210             item.key(), item.value(), res, handled, key, in...);
211     }
212 
213     details::handleMissing(handled, res, key, in...);
214 
215     return res.result() == boost::beast::http::status::ok;
216 }
217 
218 } // namespace json_util
219 } // namespace redfish
220