xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision 771cfa0fb06de042f9f304abb27d5be80bc163df)
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<boost::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>
59 void unpackValue(nlohmann::json& jsonValue, const std::string& key,
60                  crow::Response& res, Type& value)
61 {
62     if constexpr (std::is_arithmetic_v<Type>)
63     {
64         using NumType =
65             std::conditional_t<std::is_signed_v<Type>, int64_t, uint64_t>;
66 
67         NumType* jsonPtr = jsonValue.get_ptr<NumType*>();
68         if (jsonPtr == nullptr)
69         {
70             BMCWEB_LOG_DEBUG
71                 << "Value for key " << key
72                 << " was incorrect type: " << jsonValue.type_name();
73             messages::propertyValueTypeError(res, jsonValue.dump(), key);
74             return;
75         }
76         if (*jsonPtr > std::numeric_limits<Type>::max())
77         {
78             BMCWEB_LOG_DEBUG << "Value for key " << key
79                              << " was out of range: " << jsonValue.type_name();
80             messages::propertyValueNotInList(res, jsonValue.dump(), key);
81             return;
82         }
83         if (*jsonPtr < std::numeric_limits<Type>::min())
84         {
85             BMCWEB_LOG_DEBUG << "Value for key " << key
86                              << " was out of range: " << jsonValue.type_name();
87             messages::propertyValueNotInList(res, jsonValue.dump(), key);
88             return;
89         }
90         value = static_cast<Type>(*jsonPtr);
91     }
92     else if constexpr (is_optional_v<Type>)
93     {
94         value.emplace();
95         unpackValue<typename Type::value_type>(jsonValue, key, res, *value);
96     }
97     else
98     {
99         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
100         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
101         if (jsonPtr == nullptr)
102         {
103             BMCWEB_LOG_DEBUG
104                 << "Value for key " << key
105                 << " was incorrect type: " << jsonValue.type_name();
106             messages::propertyValueTypeError(res, jsonValue.dump(), key);
107             return;
108         }
109         value = std::move(*jsonPtr);
110     }
111 }
112 
113 template <size_t Count, size_t Index>
114 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
115                     crow::Response& res, std::bitset<Count>& handled)
116 {
117     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
118     messages::propertyUnknown(res, key);
119 }
120 
121 template <size_t Count, size_t Index, typename ValueType,
122           typename... UnpackTypes>
123 void readJsonValues(const std::string& key, nlohmann::json& jsonValue,
124                     crow::Response& res, std::bitset<Count>& handled,
125                     const char* keyToCheck, ValueType& valueToFill,
126                     UnpackTypes&... in)
127 {
128     if (key != keyToCheck)
129     {
130         readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...);
131         return;
132     }
133 
134     handled.set(Index);
135 
136     unpackValue<ValueType>(jsonValue, key, res, valueToFill);
137 }
138 
139 template <size_t Index = 0, size_t Count>
140 void handleMissing(std::bitset<Count>& handled, crow::Response& res)
141 {
142 }
143 
144 template <size_t Index = 0, size_t Count, typename ValueType,
145           typename... UnpackTypes>
146 void handleMissing(std::bitset<Count>& handled, crow::Response& res,
147                    const char* key, ValueType& unused, UnpackTypes&... in)
148 {
149     if (!handled.test(Index) && !is_optional_v<ValueType>)
150     {
151         messages::propertyMissing(res, key);
152     }
153     details::handleMissing<Index + 1, Count>(handled, res, in...);
154 }
155 } // namespace details
156 
157 template <typename... UnpackTypes>
158 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
159               UnpackTypes&... in)
160 {
161     nlohmann::json jsonRequest;
162     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
163     {
164         BMCWEB_LOG_DEBUG << "Json value not readable";
165         return false;
166     }
167     if (!jsonRequest.is_object())
168     {
169         BMCWEB_LOG_DEBUG << "Json value is not an object";
170         messages::unrecognizedRequestBody(res);
171         return false;
172     }
173 
174     if (jsonRequest.empty())
175     {
176         BMCWEB_LOG_DEBUG << "Json value is empty";
177         messages::emptyJSON(res);
178         return false;
179     }
180 
181     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
182     for (const auto& item : jsonRequest.items())
183     {
184         details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
185             item.key(), item.value(), res, handled, key, in...);
186     }
187 
188     details::handleMissing(handled, res, key, in...);
189 
190     return res.result() == boost::beast::http::status::ok;
191 }
192 
193 } // namespace json_util
194 } // namespace redfish
195