xref: /openbmc/phosphor-fan-presence/control/json/utils/modifier.cpp (revision 64b5ac203518568ec8b7569d0e785352278f2472)
1 /**
2  * Copyright © 2021 IBM 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 
17 #include "modifier.hpp"
18 
19 #include "json/config_base.hpp"
20 #include "json/manager.hpp"
21 
22 #include <phosphor-logging/lg2.hpp>
23 
24 namespace phosphor::fan::control::json
25 {
26 
27 /**
28  * @brief Variant visitor to return a value of the template type specified.
29  */
30 template <typename T>
31 struct ToTypeVisitor
32 {
33     template <typename U>
operator ()phosphor::fan::control::json::ToTypeVisitor34     T operator()(const U& t) const
35     {
36         if constexpr (std::is_arithmetic_v<U> && std::is_arithmetic_v<T>)
37         {
38             return static_cast<T>(t);
39         }
40         throw std::invalid_argument(
41             "Non arithmetic type used in ToTypeVisitor");
42     }
43 };
44 
45 /**
46  * @brief Return a default value to use when the argument passed
47  *        to LessThanOperator is out of range.
48  */
getDefaultValue(const PropertyVariantType & val,const std::optional<PropertyVariantType> & defaultValue)49 PropertyVariantType getDefaultValue(
50     const PropertyVariantType& val,
51     const std::optional<PropertyVariantType>& defaultValue)
52 {
53     // When a default value is given, return that
54     if (defaultValue)
55     {
56         return *defaultValue;
57     }
58 
59     if (std::holds_alternative<bool>(val))
60     {
61         return false;
62     }
63     else if (std::holds_alternative<std::string>(val))
64     {
65         return std::string{};
66     }
67     else if (std::holds_alternative<double>(val))
68     {
69         return std::numeric_limits<double>::quiet_NaN();
70     }
71     else if (std::holds_alternative<int32_t>(val))
72     {
73         return std::numeric_limits<int32_t>::quiet_NaN();
74     }
75     else if (std::holds_alternative<int64_t>(val))
76     {
77         return std::numeric_limits<int64_t>::quiet_NaN();
78     }
79 
80     throw std::runtime_error(
81         "Invalid variant type when determining default value");
82 }
83 
84 /**
85  * @brief Implements the minus operator to subtract two values.
86  *
87  * With strings values, A - B removes all occurrences of B in A.
88  * Throws if the type is a bool.
89  */
90 struct MinusOperator : public Modifier::BaseOperator
91 {
MinusOperatorphosphor::fan::control::json::MinusOperator92     explicit MinusOperator(const json& jsonObj) :
93         arg(ConfigBase::getJsonValue(jsonObj["value"]))
94     {}
95 
operator ()phosphor::fan::control::json::MinusOperator96     PropertyVariantType operator()(double val) override
97     {
98         return val - std::visit(ToTypeVisitor<double>(), arg);
99     }
100 
operator ()phosphor::fan::control::json::MinusOperator101     PropertyVariantType operator()(int32_t val) override
102     {
103         return val - std::visit(ToTypeVisitor<int32_t>(), arg);
104     }
105 
operator ()phosphor::fan::control::json::MinusOperator106     PropertyVariantType operator()(int64_t val) override
107     {
108         return val - std::visit(ToTypeVisitor<int64_t>(), arg);
109     }
110 
operator ()phosphor::fan::control::json::MinusOperator111     PropertyVariantType operator()(const std::string& val) override
112     {
113         // Remove all occurrences of arg from val.
114         auto value = val;
115         auto toRemove = std::get<std::string>(arg);
116         size_t pos;
117         while ((pos = value.find(toRemove)) != std::string::npos)
118         {
119             value.erase(pos, toRemove.size());
120         }
121 
122         return value;
123     }
124 
operator ()phosphor::fan::control::json::MinusOperator125     PropertyVariantType operator()(bool) override
126     {
127         throw std::runtime_error{
128             "Bool not allowed as a 'minus' modifier value"};
129     }
130 
131     PropertyVariantType arg;
132 };
133 
134 /**
135  * @brief Implements an operator to return a value specified in the JSON that is
136  * chosen based on if the value passed into the operator is less than the lowest
137  * arg_value it is true for or the given `default_value` if not found to be less
138  * than any entries.
139  *
140  * "modifier": {
141  *  "operator": "less_than",
142  *  "default_value": 1000, // OPTIONAL
143  *  "value": [
144  *    {
145  *      "arg_value": 30, // if value is less than 30
146  *      "parameter_value": 300  // then return 300
147  *    },
148  *    {
149  *      "arg_value": 40, // else if value is less than 40
150  *      "parameter_value": 400 // then return 400
151  *    },
152  *   ]
153  *  }
154  *
155  * If the value passed in is higher than the highest arg_value, it returns a
156  * default value this is the `default_value` given or based on the data type of
157  * parameter_value.
158  */
159 struct LessThanOperator : public Modifier::BaseOperator
160 {
LessThanOperatorphosphor::fan::control::json::LessThanOperator161     explicit LessThanOperator(const json& jsonObj)
162     {
163         const auto& valueArray = jsonObj["value"];
164         if (!valueArray.is_array())
165         {
166             lg2::error("Invalid JSON data for less_than config: {VALUE_ARRAY}",
167                        "VALUE_ARRAY", valueArray.dump());
168             throw std::invalid_argument("Invalid modifier JSON");
169         }
170 
171         for (const auto& valueEntry : valueArray)
172         {
173             if (!valueEntry.contains("arg_value") ||
174                 !valueEntry.contains("parameter_value"))
175             {
176                 lg2::error("Missing arg_value or parameter_value keys "
177                            "in less_than config: {VALUE_ARRAY}",
178                            "VALUE_ARRAY", valueArray.dump());
179                 throw std::invalid_argument("Invalid modifier JSON");
180             }
181 
182             auto argVal = ConfigBase::getJsonValue(valueEntry.at("arg_value"));
183 
184             if (std::holds_alternative<bool>(argVal))
185             {
186                 lg2::error(
187                     "Invalid data type in arg_value key in modifier JSON "
188                     "config: {VALUE_ARRAY}",
189                     "VALUE_ARRAY", valueArray.dump());
190                 throw std::invalid_argument("Invalid modifier JSON");
191             }
192 
193             auto paramVal =
194                 ConfigBase::getJsonValue(valueEntry.at("parameter_value"));
195 
196             rangeValues.emplace_back(argVal, paramVal);
197         }
198 
199         if (rangeValues.empty())
200         {
201             lg2::error("No valid range values found in "
202                        "modifier json: {VALUE_ARRAY}",
203                        "VALUE_ARRAY", valueArray.dump());
204             throw std::invalid_argument("Invalid modifier JSON");
205         }
206 
207         if (jsonObj.contains("default_value"))
208         {
209             defaultValue = ConfigBase::getJsonValue(jsonObj["default_value"]);
210         }
211     }
212 
operator ()phosphor::fan::control::json::LessThanOperator213     PropertyVariantType operator()(double val) override
214     {
215         for (const auto& rangeValue : rangeValues)
216         {
217             if (val < std::visit(ToTypeVisitor<double>(), rangeValue.first))
218             {
219                 return rangeValue.second;
220             }
221         }
222         // Return a default value based on last entry type
223         return getDefaultValue(rangeValues.back().second, defaultValue);
224     }
225 
operator ()phosphor::fan::control::json::LessThanOperator226     PropertyVariantType operator()(int32_t val) override
227     {
228         for (const auto& rangeValue : rangeValues)
229         {
230             if (val < std::visit(ToTypeVisitor<int32_t>(), rangeValue.first))
231             {
232                 return rangeValue.second;
233             }
234         }
235         return getDefaultValue(rangeValues.back().second, defaultValue);
236     }
237 
operator ()phosphor::fan::control::json::LessThanOperator238     PropertyVariantType operator()(int64_t val) override
239     {
240         for (const auto& rangeValue : rangeValues)
241         {
242             if (val < std::visit(ToTypeVisitor<int64_t>(), rangeValue.first))
243             {
244                 return rangeValue.second;
245             }
246         }
247         return getDefaultValue(rangeValues.back().second, defaultValue);
248     }
249 
operator ()phosphor::fan::control::json::LessThanOperator250     PropertyVariantType operator()(const std::string& val) override
251     {
252         for (const auto& rangeValue : rangeValues)
253         {
254             if (val <
255                 std::visit(ToTypeVisitor<std::string>(), rangeValue.first))
256             {
257                 return rangeValue.second;
258             }
259         }
260         return getDefaultValue(rangeValues.back().second, defaultValue);
261     }
262 
operator ()phosphor::fan::control::json::LessThanOperator263     PropertyVariantType operator()(bool) override
264     {
265         throw std::runtime_error{
266             "Bool not allowed as a 'less_than' modifier value"};
267     }
268 
269     std::vector<std::pair<PropertyVariantType, PropertyVariantType>>
270         rangeValues;
271     std::optional<PropertyVariantType> defaultValue;
272 };
273 
Modifier(const json & jsonObj)274 Modifier::Modifier(const json& jsonObj)
275 {
276     setOperator(jsonObj);
277 }
278 
setOperator(const json & jsonObj)279 void Modifier::setOperator(const json& jsonObj)
280 {
281     if (!jsonObj.contains("operator") || !jsonObj.contains("value"))
282     {
283         lg2::error(
284             "Modifier entry in JSON missing 'operator' or 'value': {JSON_OBJECT}",
285             "JSON_OBJECT", jsonObj.dump());
286         throw std::invalid_argument("Invalid modifier JSON");
287     }
288 
289     auto op = jsonObj["operator"].get<std::string>();
290 
291     if (op == "minus")
292     {
293         _operator = std::make_unique<MinusOperator>(jsonObj);
294     }
295     else if (op == "less_than")
296     {
297         _operator = std::make_unique<LessThanOperator>(jsonObj);
298     }
299     else
300     {
301         lg2::error("Invalid operator in the modifier JSON: {JSON_OBJECT}",
302                    "JSON_OBJECT", jsonObj.dump());
303         throw std::invalid_argument("Invalid operator in the modifier JSON");
304     }
305 }
306 
doOp(const PropertyVariantType & val)307 PropertyVariantType Modifier::doOp(const PropertyVariantType& val)
308 {
309     return std::visit(*_operator, val);
310 }
311 
312 } // namespace phosphor::fan::control::json
313