xref: /openbmc/telemetry/src/utils/labeled_tuple.hpp (revision 583ba441654657bb4ba9d051b747144a7258c159)
1 #pragma once
2 
3 #include <nlohmann/json.hpp>
4 #include <sdbusplus/message/types.hpp>
5 
6 #include <cmath>
7 #include <limits>
8 
9 namespace utils
10 {
11 
12 namespace numeric_literals
13 {
14 constexpr std::string_view NaN = "NaN";
15 constexpr std::string_view infinity = "inf";
16 constexpr std::string_view infinity_negative = "-inf";
17 } // namespace numeric_literals
18 
from_json(const nlohmann::json & j,sdbusplus::message::object_path & o)19 inline void from_json(const nlohmann::json& j,
20                       sdbusplus::message::object_path& o)
21 {
22     o = j.get<std::string>();
23 }
24 
from_json(const nlohmann::json & j,std::vector<sdbusplus::message::object_path> & o)25 inline void from_json(const nlohmann::json& j,
26                       std::vector<sdbusplus::message::object_path>& o)
27 {
28     o.clear();
29     for (const nlohmann::json& item : j)
30     {
31         o.emplace_back(item.get<std::string>());
32     }
33 }
34 
to_json(nlohmann::json & j,const double & val)35 inline void to_json(nlohmann::json& j, const double& val)
36 {
37     if (std::isnan(val))
38     {
39         j = numeric_literals::NaN;
40     }
41     else if (val == std::numeric_limits<double>::infinity())
42     {
43         j = numeric_literals::infinity;
44     }
45     else if (val == -std::numeric_limits<double>::infinity())
46     {
47         j = numeric_literals::infinity_negative;
48     }
49     else
50     {
51         j = val;
52     }
53 }
54 
from_json(const nlohmann::json & j,double & val)55 inline void from_json(const nlohmann::json& j, double& val)
56 {
57     if (j.is_number())
58     {
59         val = j.get<double>();
60     }
61     else
62     {
63         auto str_val = j.get<std::string>();
64         if (str_val == numeric_literals::NaN)
65         {
66             val = std::numeric_limits<double>::quiet_NaN();
67         }
68         else if (str_val == numeric_literals::infinity)
69         {
70             val = std::numeric_limits<double>::infinity();
71         }
72         else if (str_val == numeric_literals::infinity_negative)
73         {
74             val = -std::numeric_limits<double>::infinity();
75         }
76         else
77         {
78             throw std::invalid_argument("Unknown numeric literal");
79         }
80     }
81 }
82 
83 namespace detail
84 {
85 
86 template <class T>
87 struct has_utils_from_json
88 {
89     template <class U>
90     static U& ref();
91 
92     template <class U>
93     static std::true_type check(
94         decltype(utils::from_json(ref<const nlohmann::json>(), ref<U>()))*);
95 
96     template <class>
97     static std::false_type check(...);
98 
99     static constexpr bool value =
100         decltype(check<std::decay_t<T>>(nullptr))::value;
101 };
102 
103 template <class T>
104 constexpr bool has_utils_from_json_v = has_utils_from_json<T>::value;
105 
106 template <class T>
107 struct has_utils_to_json
108 {
109     template <class U>
110     static U& ref();
111 
112     template <class U>
113     static std::true_type check(decltype(utils::to_json(ref<nlohmann::json>(),
114                                                         ref<const U>()))*);
115 
116     template <class>
117     static std::false_type check(...);
118 
119     static constexpr bool value =
120         decltype(check<std::decay_t<T>>(nullptr))::value;
121 };
122 
123 template <class T>
124 constexpr bool has_utils_to_json_v = has_utils_to_json<T>::value;
125 
eq(const auto & a,const auto & b)126 bool eq(const auto& a, const auto& b)
127 {
128     if constexpr (std::is_same<std::decay_t<decltype(a)>, double>())
129     {
130         if (std::isnan(a))
131         {
132             return std::isnan(b);
133         }
134     }
135     return a == b;
136 }
137 
138 } // namespace detail
139 
140 template <class, class...>
141 struct LabeledTuple;
142 
143 template <class... Args, class... Labels>
144 struct LabeledTuple<std::tuple<Args...>, Labels...>
145 {
146     static_assert(sizeof...(Args) == sizeof...(Labels));
147 
148     using tuple_type = std::tuple<Args...>;
149 
150     LabeledTuple() = default;
151     LabeledTuple(const LabeledTuple&) = default;
152     LabeledTuple(LabeledTuple&&) = default;
153 
LabeledTupleutils::LabeledTuple154     explicit LabeledTuple(tuple_type v) : value(std::move(v)) {}
LabeledTupleutils::LabeledTuple155     LabeledTuple(Args... args) : value(std::move(args)...) {}
156 
157     LabeledTuple& operator=(const LabeledTuple&) = default;
158     LabeledTuple& operator=(LabeledTuple&&) = default;
159 
to_jsonutils::LabeledTuple160     nlohmann::json to_json() const
161     {
162         nlohmann::json j;
163         to_json_all(j, std::make_index_sequence<sizeof...(Args)>());
164         return j;
165     }
166 
to_tupleutils::LabeledTuple167     const tuple_type& to_tuple() const
168     {
169         return value;
170     }
171 
from_jsonutils::LabeledTuple172     void from_json(const nlohmann::json& j)
173     {
174         from_json_all(j, std::make_index_sequence<sizeof...(Args)>());
175     }
176 
dumputils::LabeledTuple177     std::string dump() const
178     {
179         return to_json().dump();
180     }
181 
182     template <size_t Idx>
at_indexutils::LabeledTuple183     const auto& at_index() const
184     {
185         return std::get<Idx>(value);
186     }
187 
188     template <size_t Idx>
at_indexutils::LabeledTuple189     auto& at_index()
190     {
191         return std::get<Idx>(value);
192     }
193 
194     template <class Label>
at_labelutils::LabeledTuple195     const auto& at_label() const
196     {
197         return find_item<0, Label>(*this);
198     }
199 
200     template <class Label>
at_labelutils::LabeledTuple201     auto& at_label()
202     {
203         return find_item<0, Label>(*this);
204     }
205 
operator ==utils::LabeledTuple206     bool operator==(const LabeledTuple& other) const
207     {
208         return std::apply(
209             [&](auto&&... x) {
210                 return std::apply(
211                     [&](auto&&... y) {
212                         return (true && ... && detail::eq(x, y));
213                     },
214                     value);
215             },
216             other.value);
217     }
218 
operator <utils::LabeledTuple219     bool operator<(const LabeledTuple& other) const
220     {
221         return value < other.value;
222     }
223 
224   private:
225     template <size_t... Idx>
to_json_allutils::LabeledTuple226     void to_json_all(nlohmann::json& j, std::index_sequence<Idx...>) const
227     {
228         (to_json_item<Idx>(j), ...);
229     }
230 
231     template <size_t Idx>
to_json_itemutils::LabeledTuple232     void to_json_item(nlohmann::json& j) const
233     {
234         using Label = std::tuple_element_t<Idx, std::tuple<Labels...>>;
235         using T = std::tuple_element_t<Idx, tuple_type>;
236         nlohmann::json& item = j[Label::str()];
237         if constexpr (detail::has_utils_to_json_v<T>)
238         {
239             utils::to_json(item, std::get<Idx>(value));
240         }
241         else
242         {
243             item = std::get<Idx>(value);
244         }
245     }
246 
247     template <size_t... Idx>
from_json_allutils::LabeledTuple248     void from_json_all(const nlohmann::json& j, std::index_sequence<Idx...>)
249     {
250         (from_json_item<Idx>(j), ...);
251     }
252 
253     template <size_t Idx>
from_json_itemutils::LabeledTuple254     void from_json_item(const nlohmann::json& j)
255     {
256         using Label = std::tuple_element_t<Idx, std::tuple<Labels...>>;
257         using T = std::tuple_element_t<Idx, tuple_type>;
258         const nlohmann::json& item = j.at(Label::str());
259         if constexpr (detail::has_utils_from_json_v<T>)
260         {
261             T& v = std::get<Idx>(value);
262             utils::from_json(item, v);
263         }
264         else
265         {
266             std::get<Idx>(value) = item.get<T>();
267         }
268     }
269 
270     template <size_t Idx, class Label, class Self>
find_itemutils::LabeledTuple271     static auto& find_item(Self& self)
272     {
273         if constexpr (std::is_same_v<Label, std::tuple_element_t<
274                                                 Idx, std::tuple<Labels...>>>)
275         {
276             return std::get<Idx>(self.value);
277         }
278         else
279         {
280             static_assert(Idx + 1 < sizeof...(Args),
281                           "Label not found in LabeledTuple");
282             return find_item<Idx + 1, Label>(self);
283         }
284     }
285 
286     tuple_type value;
287 };
288 
289 template <class... Args, class... Labels>
to_json(nlohmann::json & json,const LabeledTuple<std::tuple<Args...>,Labels...> & tuple)290 inline void to_json(nlohmann::json& json,
291                     const LabeledTuple<std::tuple<Args...>, Labels...>& tuple)
292 {
293     json = tuple.to_json();
294 }
295 
296 template <class... Args, class... Labels>
from_json(const nlohmann::json & json,LabeledTuple<std::tuple<Args...>,Labels...> & tuple)297 inline void from_json(const nlohmann::json& json,
298                       LabeledTuple<std::tuple<Args...>, Labels...>& tuple)
299 {
300     tuple.from_json(json);
301 }
302 
303 } // namespace utils
304