xref: /openbmc/phosphor-power/json_parser_utils.cpp (revision 8873f428276818761348b4091574334870ae51a7)
1 /**
2  * Copyright © 2025 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 "json_parser_utils.hpp"
18 
19 #include <charconv>
20 #include <regex>
21 
22 namespace phosphor::power::json_parser_utils
23 {
24 
25 const std::map<std::string, std::string> NO_VARIABLES{};
26 
27 static std::regex VARIABLE_REGEX{R"(\$\{([A-Za-z0-9_]+)\})"};
28 
parseBitPosition(const nlohmann::json & element,const std::map<std::string,std::string> & variables)29 uint8_t parseBitPosition(const nlohmann::json& element,
30                          const std::map<std::string, std::string>& variables)
31 {
32     int value = parseInteger(element, variables);
33     if ((value < 0) || (value > 7))
34     {
35         throw std::invalid_argument{"Element is not a bit position"};
36     }
37     return static_cast<uint8_t>(value);
38 }
39 
parseBitValue(const nlohmann::json & element,const std::map<std::string,std::string> & variables)40 uint8_t parseBitValue(const nlohmann::json& element,
41                       const std::map<std::string, std::string>& variables)
42 {
43     int value = parseInteger(element, variables);
44     if ((value < 0) || (value > 1))
45     {
46         throw std::invalid_argument{"Element is not a bit value"};
47     }
48     return static_cast<uint8_t>(value);
49 }
50 
parseBoolean(const nlohmann::json & element,const std::map<std::string,std::string> & variables)51 bool parseBoolean(const nlohmann::json& element,
52                   const std::map<std::string, std::string>& variables)
53 {
54     if (element.is_boolean())
55     {
56         return element.get<bool>();
57     }
58 
59     if (element.is_string() && !variables.empty())
60     {
61         std::string value = parseString(element, true, variables);
62         if (value == "true")
63         {
64             return true;
65         }
66         else if (value == "false")
67         {
68             return false;
69         }
70     }
71 
72     throw std::invalid_argument{"Element is not a boolean"};
73 }
74 
parseDouble(const nlohmann::json & element,const std::map<std::string,std::string> & variables)75 double parseDouble(const nlohmann::json& element,
76                    const std::map<std::string, std::string>& variables)
77 {
78     if (element.is_number())
79     {
80         return element.get<double>();
81     }
82 
83     if (element.is_string() && !variables.empty())
84     {
85         std::string strValue = parseString(element, true, variables);
86         const char* first = strValue.data();
87         const char* last = strValue.data() + strValue.size();
88         double value;
89         auto [ptr, ec] = std::from_chars(first, last, value);
90         if ((ptr == last) && (ec == std::errc()))
91         {
92             return value;
93         }
94     }
95 
96     throw std::invalid_argument{"Element is not a double"};
97 }
98 
parseHexByte(const nlohmann::json & element,const std::map<std::string,std::string> & variables)99 uint8_t parseHexByte(const nlohmann::json& element,
100                      const std::map<std::string, std::string>& variables)
101 {
102     std::string value = parseString(element, true, variables);
103     bool isHex = (value.compare(0, 2, "0x") == 0) && (value.size() > 2) &&
104                  (value.size() < 5) &&
105                  (value.find_first_not_of("0123456789abcdefABCDEF", 2) ==
106                   std::string::npos);
107     if (!isHex)
108     {
109         throw std::invalid_argument{"Element is not hexadecimal string"};
110     }
111     return static_cast<uint8_t>(std::stoul(value, nullptr, 0));
112 }
113 
parseHexByteArray(const nlohmann::json & element,const std::map<std::string,std::string> & variables)114 std::vector<uint8_t> parseHexByteArray(
115     const nlohmann::json& element,
116     const std::map<std::string, std::string>& variables)
117 {
118     verifyIsArray(element);
119     std::vector<uint8_t> values;
120     for (auto& valueElement : element)
121     {
122         values.emplace_back(parseHexByte(valueElement, variables));
123     }
124     return values;
125 }
126 
parseInt8(const nlohmann::json & element,const std::map<std::string,std::string> & variables)127 int8_t parseInt8(const nlohmann::json& element,
128                  const std::map<std::string, std::string>& variables)
129 {
130     int value = parseInteger(element, variables);
131     if ((value < INT8_MIN) || (value > INT8_MAX))
132     {
133         throw std::invalid_argument{"Element is not an 8-bit signed integer"};
134     }
135     return static_cast<int8_t>(value);
136 }
137 
parseInteger(const nlohmann::json & element,const std::map<std::string,std::string> & variables)138 int parseInteger(const nlohmann::json& element,
139                  const std::map<std::string, std::string>& variables)
140 {
141     if (element.is_number_integer())
142     {
143         return element.get<int>();
144     }
145 
146     if (element.is_string() && !variables.empty())
147     {
148         std::string strValue = parseString(element, true, variables);
149         const char* first = strValue.data();
150         const char* last = strValue.data() + strValue.size();
151         int value;
152         auto [ptr, ec] = std::from_chars(first, last, value);
153         if ((ptr == last) && (ec == std::errc()))
154         {
155             return value;
156         }
157     }
158 
159     throw std::invalid_argument{"Element is not an integer"};
160 }
161 
parseString(const nlohmann::json & element,bool isEmptyValid,const std::map<std::string,std::string> & variables)162 std::string parseString(const nlohmann::json& element, bool isEmptyValid,
163                         const std::map<std::string, std::string>& variables)
164 {
165     if (!element.is_string())
166     {
167         throw std::invalid_argument{"Element is not a string"};
168     }
169     std::string value = element.get<std::string>();
170     internal::expandVariables(value, variables);
171     if (value.empty() && !isEmptyValid)
172     {
173         throw std::invalid_argument{"Element contains an empty string"};
174     }
175     return value;
176 }
177 
parseUint8(const nlohmann::json & element,const std::map<std::string,std::string> & variables)178 uint8_t parseUint8(const nlohmann::json& element,
179                    const std::map<std::string, std::string>& variables)
180 {
181     int value = parseInteger(element, variables);
182     if ((value < 0) || (value > UINT8_MAX))
183     {
184         throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
185     }
186     return static_cast<uint8_t>(value);
187 }
188 
parseUint16(const nlohmann::json & element,const std::map<std::string,std::string> & variables)189 uint16_t parseUint16(const nlohmann::json& element,
190                      const std::map<std::string, std::string>& variables)
191 {
192     int value = parseInteger(element, variables);
193     if ((value < 0) || (value > UINT16_MAX))
194     {
195         throw std::invalid_argument{"Element is not a 16-bit unsigned integer"};
196     }
197     return static_cast<uint16_t>(value);
198 }
199 
parseUnsignedInteger(const nlohmann::json & element,const std::map<std::string,std::string> & variables)200 unsigned int parseUnsignedInteger(
201     const nlohmann::json& element,
202     const std::map<std::string, std::string>& variables)
203 {
204     int value = parseInteger(element, variables);
205     if (value < 0)
206     {
207         throw std::invalid_argument{"Element is not an unsigned integer"};
208     }
209     return static_cast<unsigned int>(value);
210 }
211 
212 namespace internal
213 {
214 
expandVariables(std::string & value,const std::map<std::string,std::string> & variables)215 void expandVariables(std::string& value,
216                      const std::map<std::string, std::string>& variables)
217 {
218     if (variables.empty())
219     {
220         return;
221     }
222 
223     std::smatch results;
224     while (std::regex_search(value, results, VARIABLE_REGEX))
225     {
226         if (results.size() != 2)
227         {
228             throw std::runtime_error{
229                 "Unexpected regular expression match result while parsing string"};
230         }
231         const std::string& variable = results[1];
232         auto it = variables.find(variable);
233         if (it == variables.end())
234         {
235             throw std::invalid_argument{"Undefined variable: " + variable};
236         }
237         value.replace(results.position(0), results.length(0), it->second);
238     }
239 }
240 
241 } // namespace internal
242 
243 } // namespace phosphor::power::json_parser_utils
244