Astarte device API for C++ 0.6.2
Astarte device SDK for C++
Loading...
Searching...
No Matches
formatter.hpp
1// (C) Copyright 2025, SECO Mind Srl
2//
3// SPDX-License-Identifier: Apache-2.0
4
5#ifndef ASTARTE_FORMATTER_H
6#define ASTARTE_FORMATTER_H
7
8#include <chrono>
9#include <cstddef>
10#include <type_traits>
11
12// TODO(rgallor): stop using spdlog formatter once C++20 will become the minimu required version
13#if (__cplusplus >= 202002L) && (__has_include(<format>))
14#include <format>
15#define ASTARTE_NS_FORMAT std
16#else // (__cplusplus >= 202002L) && (__has_include(<format>))
17#include <spdlog/fmt/fmt.h> // NOLINT: avoid clang-tidy warning regarding fmt library not used directly
18
19#include <iomanip>
20#include <sstream>
21#define ASTARTE_NS_FORMAT fmt
22#endif // (__cplusplus >= 202002L) && (__has_include(<format>))
23
27#include "astarte_device_sdk/property.hpp"
29
33namespace utils {
34// These functions are only used for pretty printing
35// NOLINTBEGIN(concurrency-mt-unsafe)
36// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
43template <typename OutputIt>
44void format_base64(OutputIt& out, const std::vector<uint8_t>& data) {
45 static constexpr std::string_view base64_chars =
46 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
47 "abcdefghijklmnopqrstuvwxyz"
48 "0123456789+/";
49
50 size_t idx = 0;
51 const size_t len = data.size();
52
53 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
54
55 while (idx + 2 < len) {
56 const uint32_t chunk = (data[idx] << 16) | (data[idx + 1] << 8) | data[idx + 2];
57 out = ASTARTE_NS_FORMAT::format_to(
58 out, "{}{}{}{}", base64_chars[(chunk >> 18) & 0x3F], base64_chars[(chunk >> 12) & 0x3F],
59 base64_chars[(chunk >> 6) & 0x3F], base64_chars[chunk & 0x3F]);
60 idx += 3;
61 }
62
63 if (idx < len) {
64 uint32_t chunk = data[idx] << 16;
65 if (idx + 1 < len) {
66 chunk |= data[idx + 1] << 8;
67 }
68
69 out = ASTARTE_NS_FORMAT::format_to(out, "{}{}", base64_chars[(chunk >> 18) & 0x3F],
70 base64_chars[(chunk >> 12) & 0x3F]);
71 if (idx + 1 < len) {
72 out = ASTARTE_NS_FORMAT::format_to(out, "{}=", base64_chars[(chunk >> 6) & 0x3F]);
73 } else {
74 out = ASTARTE_NS_FORMAT::format_to(out, "==");
75 }
76 }
77
78 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
79}
80// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
81
88template <typename OutputIt>
89void format_timestamp(OutputIt& out, const std::chrono::system_clock::time_point& data) {
90 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
91#if (__cplusplus >= 202002L) && (__has_include(<format>))
92 out = ASTARTE_NS_FORMAT::format_to(
93 out, "{}",
94 ASTARTE_NS_FORMAT::format("{0:%F}T{0:%T}Z",
95 std::chrono::time_point_cast<std::chrono::milliseconds>(data)));
96#else // (__cplusplus >= 202002L) && (__has_include(<format>))
97 const std::time_t time = std::chrono::system_clock::to_time_t(data);
98 const std::tm utc_tm = *std::gmtime(&time);
99 std::stringstream stream;
100 stream << std::put_time(&utc_tm, "%FT%T.000Z");
101 out = ASTARTE_NS_FORMAT::format_to(out, "{}", stream.str());
102#endif // (__cplusplus >= 202002L) && (__has_include(<format>))
103 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
104}
105
113template <typename OutputIt, typename T>
114void format_data(OutputIt& out, const T& data) {
115 if constexpr (std::is_same_v<T, bool>) {
116 ASTARTE_NS_FORMAT::format_to(out, "{}", (data ? "true" : "false"));
117 } else if constexpr (std::is_same_v<T, std::string>) {
118 ASTARTE_NS_FORMAT::format_to(out, R"("{}")", data);
119 } else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
120 format_base64(out, data);
121 } else if constexpr (std::is_same_v<T, std::chrono::system_clock::time_point>) {
122 format_timestamp(out, data);
123 } else { // default format case
124 ASTARTE_NS_FORMAT::format_to(out, "{}", data);
125 }
126}
127
135template <typename OutputIt, typename T>
136void format_vector(OutputIt& out, const std::vector<T>& data) {
137 out = ASTARTE_NS_FORMAT::format_to(out, "[");
138 for (size_t i = 0; i < data.size(); ++i) {
139 format_data(out, data[i]);
140 if (i != data.size() - 1) {
141 out = ASTARTE_NS_FORMAT::format_to(out, ", ");
142 }
143 }
144 out = ASTARTE_NS_FORMAT::format_to(out, "]");
145}
146// NOLINTEND(concurrency-mt-unsafe)
147
148} // namespace utils
149
152
156template <>
157struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteData> {
163 template <typename ParseContext>
164 constexpr auto parse(ParseContext& ctx) const {
165 return ctx.begin();
166 }
167
174 template <typename FormatContext>
175 auto format(const AstarteDeviceSdk::AstarteData& data, FormatContext& ctx) const {
176 auto out = ctx.out();
177
178 if (std::holds_alternative<int32_t>(data.get_raw_data())) {
179 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<int32_t>(data.get_raw_data()));
180 } else if (std::holds_alternative<int64_t>(data.get_raw_data())) {
181 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<int64_t>(data.get_raw_data()));
182 } else if (std::holds_alternative<double>(data.get_raw_data())) {
183 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<double>(data.get_raw_data()));
184 } else if (std::holds_alternative<bool>(data.get_raw_data())) {
185 auto s = (std::get<bool>(data.get_raw_data()) ? "true" : "false");
186 out = ASTARTE_NS_FORMAT::format_to(out, "{}", s);
187 } else if (std::holds_alternative<std::string>(data.get_raw_data())) {
188 out =
189 ASTARTE_NS_FORMAT::format_to(out, R"("{}")", std::get<std::string>(data.get_raw_data()));
190 } else if (std::holds_alternative<std::vector<uint8_t>>(data.get_raw_data())) {
191 utils::format_base64(out, std::get<std::vector<uint8_t>>(data.get_raw_data()));
192 } else if (std::holds_alternative<std::chrono::system_clock::time_point>(data.get_raw_data())) {
194 std::get<std::chrono::system_clock::time_point>(data.get_raw_data()));
195 } else if (std::holds_alternative<std::vector<int32_t>>(data.get_raw_data())) {
196 utils::format_vector(out, std::get<std::vector<int32_t>>(data.get_raw_data()));
197 } else if (std::holds_alternative<std::vector<int64_t>>(data.get_raw_data())) {
198 utils::format_vector(out, std::get<std::vector<int64_t>>(data.get_raw_data()));
199 } else if (std::holds_alternative<std::vector<double>>(data.get_raw_data())) {
200 utils::format_vector(out, std::get<std::vector<double>>(data.get_raw_data()));
201 } else if (std::holds_alternative<std::vector<bool>>(data.get_raw_data())) {
202 utils::format_vector(out, std::get<std::vector<bool>>(data.get_raw_data()));
203 } else if (std::holds_alternative<std::vector<std::string>>(data.get_raw_data())) {
204 utils::format_vector(out, std::get<std::vector<std::string>>(data.get_raw_data()));
205 } else if (std::holds_alternative<std::vector<std::vector<uint8_t>>>(data.get_raw_data())) {
206 utils::format_vector(out, std::get<std::vector<std::vector<uint8_t>>>(data.get_raw_data()));
207 } else if (std::holds_alternative<std::vector<std::chrono::system_clock::time_point>>(
208 data.get_raw_data())) {
210 out, std::get<std::vector<std::chrono::system_clock::time_point>>(data.get_raw_data()));
211 }
212
213 return out;
214 }
215};
216
220template <>
221struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteType> {
227 template <typename ParseContext>
228 constexpr auto parse(ParseContext& ctx) const {
229 return ctx.begin();
230 }
231
238 template <typename FormatContext>
239 auto format(const AstarteDeviceSdk::AstarteType& typ, FormatContext& ctx) const {
240 std::string_view name = "Unknown Type";
241
242 switch (typ) {
244 name = "BinaryBlob";
245 break;
247 name = "Boolean";
248 break;
250 name = "Datetime";
251 break;
253 name = "Double";
254 break;
256 name = "Integer";
257 break;
259 name = "LongInteger";
260 break;
262 name = "String";
263 break;
265 name = "BinaryBlobArray";
266 break;
268 name = "BooleanArray";
269 break;
271 name = "DatetimeArray";
272 break;
274 name = "DoubleArray";
275 break;
277 name = "IntegerArray";
278 break;
280 name = "LongIntegerArray";
281 break;
283 name = "StringArray";
284 break;
285 }
286
287 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", name);
288 }
289};
290
295template <>
296struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteDatastreamIndividual> {
302 template <typename ParseContext>
303 constexpr auto parse(ParseContext& ctx) const {
304 return ctx.begin();
305 }
306
313 template <typename FormatContext>
314 auto format(const AstarteDeviceSdk::AstarteDatastreamIndividual& data, FormatContext& ctx) const {
315 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", data.get_value());
316 }
317};
318
319inline std::ostream& operator<<(std::ostream& out,
321 out << ASTARTE_NS_FORMAT::format("{}", data);
322 return out;
323}
324
328template <>
329struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteDatastreamObject> {
335 template <typename ParseContext>
336 constexpr auto parse(ParseContext& ctx) const {
337 return ctx.begin();
338 }
339
346 template <typename FormatContext>
347 auto format(const AstarteDeviceSdk::AstarteDatastreamObject& data, FormatContext& ctx) const {
348 auto out = ctx.out();
349 out = ASTARTE_NS_FORMAT::format_to(out, "{{");
350
351 bool first = true;
352 for (const auto& pair : data.get_raw_data()) {
353 if (!first) {
354 out = ASTARTE_NS_FORMAT::format_to(out, ", ");
355 }
356 out = ASTARTE_NS_FORMAT::format_to(out, R"("{}": {})", pair.first, pair.second);
357 first = false;
358 }
359
360 out = ASTARTE_NS_FORMAT::format_to(out, "}}");
361 return out;
362 }
363};
364
365inline std::ostream& operator<<(std::ostream& out,
367 out << ASTARTE_NS_FORMAT::format("{}", data);
368 return out;
369}
370
375template <>
376struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstartePropertyIndividual> {
382 template <typename ParseContext>
383 constexpr auto parse(ParseContext& ctx) const {
384 return ctx.begin();
385 }
386
393 template <typename FormatContext>
394 auto format(const AstarteDeviceSdk::AstartePropertyIndividual& data, FormatContext& ctx) const {
395 if (data.get_value().has_value()) {
396 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", data.get_value().value());
397 }
398
399 return ctx.out();
400 }
401};
402
403inline std::ostream& operator<<(std::ostream& out,
405 out << ASTARTE_NS_FORMAT::format("{}", data);
406 return out;
407}
408
412template <>
413struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteMessage> {
419 template <typename ParseContext>
420 constexpr auto parse(ParseContext& ctx) const {
421 return ctx.begin();
422 }
423
430 template <typename FormatContext>
431 auto format(const AstarteDeviceSdk::AstarteMessage& msg, FormatContext& ctx) const {
432 auto out = ctx.out();
433
434 out = ASTARTE_NS_FORMAT::format_to(out, "{{interface: {}, path: {}", msg.get_interface(),
435 msg.get_path());
436
437 // check if the payload is an unset property, which is the only "empty" case
438 bool is_unset_prop = false;
439 const auto* prop =
440 std::get_if<AstarteDeviceSdk::AstartePropertyIndividual>(&msg.get_raw_data());
441 if (prop && !prop->get_value().has_value()) {
442 is_unset_prop = true;
443 }
444
445 if (!is_unset_prop) {
446 out = ASTARTE_NS_FORMAT::format_to(out, ", value: ");
447 std::visit([&out](const auto& arg) { out = ASTARTE_NS_FORMAT::format_to(out, "{}", arg); },
448 msg.get_raw_data());
449 }
450
451 return ASTARTE_NS_FORMAT::format_to(out, "}}");
452 }
453};
454
456
457#endif // ASTARTE_FORMATTER_H
auto get_raw_data() const -> const std::variant< int32_t, int64_t, double, bool, std::string, std::vector< uint8_t >, std::chrono::system_clock::time_point, std::vector< int32_t >, std::vector< int64_t >, std::vector< double >, std::vector< bool >, std::vector< std::string >, std::vector< std::vector< uint8_t > >, std::vector< std::chrono::system_clock::time_point > > &
Return the raw data contained in this class instance.
Representing the Astarte individual datastream data.
Definition individual.hpp:18
auto get_value() const -> const AstarteData &
Get the value contained within the object.
Astarte object class, representing the Astarte object datastream data.
Definition object.hpp:22
auto get_raw_data() const -> const MapType &
Return the raw data contained in this class instance.
auto get_raw_data() const -> const std::variant< AstarteDatastreamIndividual, AstarteDatastreamObject, AstartePropertyIndividual > &
Return the raw data contained in this class instance.
auto get_path() const -> const std::string &
Get the path of the message.
auto get_interface() const -> const std::string &
Get the interface of the message.
Representing the Astarte individual datastream data.
Definition property.hpp:20
auto get_value() const -> const std::optional< AstarteData > &
Get the value contained within the object.
Astarte individual datastream class and its related methods.
Astarte message class and its related methods.
Umbrella namespace for the Astarte device SDK.
Definition data.hpp:25
AstarteType
Possible Astarte types.
Definition type.hpp:18
@ kLongIntegerArray
Long integer array Astarte type.
Definition type.hpp:44
@ kIntegerArray
Integer array Astarte type.
Definition type.hpp:42
@ kDouble
Double Astarte type.
Definition type.hpp:26
@ kBoolean
Boolean Astarte type.
Definition type.hpp:22
@ kDatetime
Date-time Astarte type.
Definition type.hpp:24
@ kBinaryBlob
Binary blob Astarte type.
Definition type.hpp:20
@ kDoubleArray
Double array Astarte type.
Definition type.hpp:40
@ kStringArray
String array Astarte type.
Definition type.hpp:46
@ kBooleanArray
Boolean array Astarte type.
Definition type.hpp:36
@ kInteger
Integer Astarte type.
Definition type.hpp:28
@ kDatetimeArray
Datetime array Astarte type.
Definition type.hpp:38
@ kString
String Astarte type.
Definition type.hpp:32
@ kBinaryBlobArray
Binary blob array Astarte type.
Definition type.hpp:34
@ kLongInteger
Long integer Astarte type.
Definition type.hpp:30
Contain utility functions for formatting data.
Definition formatter.hpp:33
void format_timestamp(OutputIt &out, const std::chrono::system_clock::time_point &data)
Format a timestamp into an ISO 8601 string literal.
Definition formatter.hpp:89
void format_vector(OutputIt &out, const std::vector< T > &data)
Format a generic vector into a comma-separated list in brackets.
Definition formatter.hpp:136
void format_base64(OutputIt &out, const std::vector< uint8_t > &data)
Format a vector of bytes into a Base64 string literal.
Definition formatter.hpp:44
void format_data(OutputIt &out, const T &data)
Format a generic data type into an output iterator.
Definition formatter.hpp:114
Astarte object class and its related methods.
Types definitions for communication with Astarte.