1 /**
2  * Copyright © 2024 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 #pragma once
17 
18 #include "rail.hpp"
19 
20 #include <nlohmann/json.hpp>
21 
22 #include <cstdint>
23 #include <filesystem>
24 #include <memory>
25 #include <stdexcept>
26 #include <string>
27 #include <vector>
28 
29 namespace phosphor::power::sequencer::config_file_parser
30 {
31 
32 /**
33  * Parses the specified JSON configuration file.
34  *
35  * Returns the corresponding C++ Rail objects.
36  *
37  * Throws a ConfigFileParserError if an error occurs.
38  *
39  * @param pathName configuration file path name
40  * @return vector of Rail objects
41  */
42 std::vector<std::unique_ptr<Rail>> parse(const std::filesystem::path& pathName);
43 
44 /*
45  * Internal implementation details for parse()
46  */
47 namespace internal
48 {
49 
50 /**
51  * Returns the specified property of the specified JSON element.
52  *
53  * Throws an invalid_argument exception if the property does not exist.
54  *
55  * @param element JSON element
56  * @param property property name
57  */
58 #pragma GCC diagnostic push
59 #if __GNUC__ == 13
60 #pragma GCC diagnostic ignored "-Wdangling-reference"
61 #endif
62 inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
63                                                  const std::string& property)
64 {
65     auto it = element.find(property);
66     if (it == element.end())
67     {
68         throw std::invalid_argument{"Required property missing: " + property};
69     }
70     return *it;
71 }
72 #pragma GCC diagnostic pop
73 
74 /**
75  * Parses a JSON element containing a boolean.
76  *
77  * Returns the corresponding C++ boolean value.
78  *
79  * Throws an exception if parsing fails.
80  *
81  * @param element JSON element
82  * @return boolean value
83  */
84 inline bool parseBoolean(const nlohmann::json& element)
85 {
86     // Verify element contains a boolean
87     if (!element.is_boolean())
88     {
89         throw std::invalid_argument{"Element is not a boolean"};
90     }
91     return element.get<bool>();
92 }
93 
94 /**
95  * Parses a JSON element containing a GPIO.
96  *
97  * Returns the corresponding C++ GPIO object.
98  *
99  * Throws an exception if parsing fails.
100  *
101  * @param element JSON element
102  * @return GPIO object
103  */
104 GPIO parseGPIO(const nlohmann::json& element);
105 
106 /**
107  * Parses a JSON element containing a rail.
108  *
109  * Returns the corresponding C++ Rail object.
110  *
111  * Throws an exception if parsing fails.
112  *
113  * @param element JSON element
114  * @return Rail object
115  */
116 std::unique_ptr<Rail> parseRail(const nlohmann::json& element);
117 
118 /**
119  * Parses a JSON element containing an array of rails.
120  *
121  * Returns the corresponding C++ Rail objects.
122  *
123  * Throws an exception if parsing fails.
124  *
125  * @param element JSON element
126  * @return vector of Rail objects
127  */
128 std::vector<std::unique_ptr<Rail>>
129     parseRailArray(const nlohmann::json& element);
130 
131 /**
132  * Parses the JSON root element of the entire configuration file.
133  *
134  * Returns the corresponding C++ Rail objects.
135  *
136  * Throws an exception if parsing fails.
137  *
138  * @param element JSON element
139  * @return vector of Rail objects
140  */
141 std::vector<std::unique_ptr<Rail>> parseRoot(const nlohmann::json& element);
142 
143 /**
144  * Parses a JSON element containing a string.
145  *
146  * Returns the corresponding C++ string.
147  *
148  * Throws an exception if parsing fails.
149  *
150  * @param element JSON element
151  * @param isEmptyValid indicates whether an empty string value is valid
152  * @return string value
153  */
154 inline std::string parseString(const nlohmann::json& element,
155                                bool isEmptyValid = false)
156 {
157     if (!element.is_string())
158     {
159         throw std::invalid_argument{"Element is not a string"};
160     }
161     std::string value = element.get<std::string>();
162     if (value.empty() && !isEmptyValid)
163     {
164         throw std::invalid_argument{"Element contains an empty string"};
165     }
166     return value;
167 }
168 
169 /**
170  * Parses a JSON element containing an 8-bit unsigned integer.
171  *
172  * Returns the corresponding C++ uint8_t value.
173  *
174  * Throws an exception if parsing fails.
175  *
176  * @param element JSON element
177  * @return uint8_t value
178  */
179 inline uint8_t parseUint8(const nlohmann::json& element)
180 {
181     // Verify element contains an integer
182     if (!element.is_number_integer())
183     {
184         throw std::invalid_argument{"Element is not an integer"};
185     }
186     int value = element.get<int>();
187     if ((value < 0) || (value > UINT8_MAX))
188     {
189         throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
190     }
191     return static_cast<uint8_t>(value);
192 }
193 
194 /**
195  * Parses a JSON element containing an unsigned integer.
196  *
197  * Returns the corresponding C++ unsigned int value.
198  *
199  * Throws an exception if parsing fails.
200  *
201  * @param element JSON element
202  * @return unsigned int value
203  */
204 inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
205 {
206     // Verify element contains an unsigned integer
207     if (!element.is_number_unsigned())
208     {
209         throw std::invalid_argument{"Element is not an unsigned integer"};
210     }
211     return element.get<unsigned int>();
212 }
213 
214 /**
215  * Verifies that the specified JSON element is a JSON array.
216  *
217  * Throws an invalid_argument exception if the element is not an array.
218  *
219  * @param element JSON element
220  */
221 inline void verifyIsArray(const nlohmann::json& element)
222 {
223     if (!element.is_array())
224     {
225         throw std::invalid_argument{"Element is not an array"};
226     }
227 }
228 
229 /**
230  * Verifies that the specified JSON element is a JSON object.
231  *
232  * Throws an invalid_argument exception if the element is not an object.
233  *
234  * @param element JSON element
235  */
236 inline void verifyIsObject(const nlohmann::json& element)
237 {
238     if (!element.is_object())
239     {
240         throw std::invalid_argument{"Element is not an object"};
241     }
242 }
243 
244 /**
245  * Verifies that the specified JSON element contains the expected number of
246  * properties.
247  *
248  * Throws an invalid_argument exception if the element contains a different
249  * number of properties.  This indicates the element contains an invalid
250  * property.
251  *
252  * @param element JSON element
253  * @param expectedCount expected number of properties in element
254  */
255 inline void verifyPropertyCount(const nlohmann::json& element,
256                                 unsigned int expectedCount)
257 {
258     if (element.size() != expectedCount)
259     {
260         throw std::invalid_argument{"Element contains an invalid property"};
261     }
262 }
263 
264 } // namespace internal
265 
266 } // namespace phosphor::power::sequencer::config_file_parser
267