Astarte device API for C++ 0.8.1
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#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) || \
13 (!defined(_MSVC_LANG) && __cplusplus >= 202002L)) && \
14 (__has_include(<format>))
15#include <format>
16#define ASTARTE_NS_FORMAT std
17#else // (__cplusplus >= 202002L) && (__has_include(<format>))
18#include <spdlog/fmt/fmt.h> // NOLINT: avoid clang-tidy warning regarding fmt library not used directly
19
20#include <iomanip>
21#include <sstream>
22#define ASTARTE_NS_FORMAT fmt
23#endif // (__cplusplus >= 202002L) && (__has_include(<format>))
24
29#include "astarte_device_sdk/property.hpp"
32
36namespace utils {
37// These functions are only used for pretty printing
38// NOLINTBEGIN(concurrency-mt-unsafe)
39// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
46template <typename OutputIt>
47void format_base64(OutputIt& out, const std::vector<uint8_t>& data) {
48 static constexpr std::string_view base64_chars =
49 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
50 "abcdefghijklmnopqrstuvwxyz"
51 "0123456789+/";
52
53 size_t idx = 0;
54 const size_t len = data.size();
55
56 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
57
58 while (idx + 2 < len) {
59 const uint32_t chunk = (data[idx] << 16) | (data[idx + 1] << 8) | data[idx + 2];
60 out = ASTARTE_NS_FORMAT::format_to(
61 out, "{}{}{}{}", base64_chars[(chunk >> 18) & 0x3F], base64_chars[(chunk >> 12) & 0x3F],
62 base64_chars[(chunk >> 6) & 0x3F], base64_chars[chunk & 0x3F]);
63 idx += 3;
64 }
65
66 if (idx < len) {
67 uint32_t chunk = data[idx] << 16;
68 if (idx + 1 < len) {
69 chunk |= data[idx + 1] << 8;
70 }
71
72 out = ASTARTE_NS_FORMAT::format_to(out, "{}{}", base64_chars[(chunk >> 18) & 0x3F],
73 base64_chars[(chunk >> 12) & 0x3F]);
74 if (idx + 1 < len) {
75 out = ASTARTE_NS_FORMAT::format_to(out, "{}=", base64_chars[(chunk >> 6) & 0x3F]);
76 } else {
77 out = ASTARTE_NS_FORMAT::format_to(out, "==");
78 }
79 }
80
81 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
82}
83// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
84
91template <typename OutputIt>
92void format_timestamp(OutputIt& out, const std::chrono::system_clock::time_point& data) {
93 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
94#if (__cplusplus >= 202002L) && (__has_include(<format>))
95 out = ASTARTE_NS_FORMAT::format_to(
96 out, "{}",
97 ASTARTE_NS_FORMAT::format("{0:%F}T{0:%T}Z",
98 std::chrono::time_point_cast<std::chrono::milliseconds>(data)));
99#else // (__cplusplus >= 202002L) && (__has_include(<format>))
100 const std::time_t time = std::chrono::system_clock::to_time_t(data);
101 const std::tm utc_tm = *std::gmtime(&time);
102 std::stringstream stream;
103 stream << std::put_time(&utc_tm, "%FT%T.000Z");
104 out = ASTARTE_NS_FORMAT::format_to(out, "{}", stream.str());
105#endif // (__cplusplus >= 202002L) && (__has_include(<format>))
106 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
107}
108
116template <typename OutputIt, typename T>
117void format_data(OutputIt& out, const T& data) {
118 if constexpr (std::is_same_v<T, bool>) {
119 ASTARTE_NS_FORMAT::format_to(out, "{}", (data ? "true" : "false"));
120 } else if constexpr (std::is_same_v<T, std::string>) {
121 ASTARTE_NS_FORMAT::format_to(out, R"("{}")", data);
122 } else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
123 format_base64(out, data);
124 } else if constexpr (std::is_same_v<T, std::chrono::system_clock::time_point>) {
125 format_timestamp(out, data);
126 } else { // default format case
127 ASTARTE_NS_FORMAT::format_to(out, "{}", data);
128 }
129}
130
138template <typename OutputIt, typename T>
139void format_vector(OutputIt& out, const std::vector<T>& data) {
140 out = ASTARTE_NS_FORMAT::format_to(out, "[");
141 for (size_t i = 0; i < data.size(); ++i) {
142 format_data(out, data[i]);
143 if (i != data.size() - 1) {
144 out = ASTARTE_NS_FORMAT::format_to(out, ", ");
145 }
146 }
147 out = ASTARTE_NS_FORMAT::format_to(out, "]");
148}
149// NOLINTEND(concurrency-mt-unsafe)
150
151} // namespace utils
152
155
159template <>
160struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteData> {
166 template <typename ParseContext>
167 constexpr auto parse(ParseContext& ctx) const {
168 return ctx.begin();
169 }
170
177 template <typename FormatContext>
178 auto format(const AstarteDeviceSdk::AstarteData& data, FormatContext& ctx) const {
179 auto out = ctx.out();
180
181 if (std::holds_alternative<int32_t>(data.get_raw_data())) {
182 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<int32_t>(data.get_raw_data()));
183 } else if (std::holds_alternative<int64_t>(data.get_raw_data())) {
184 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<int64_t>(data.get_raw_data()));
185 } else if (std::holds_alternative<double>(data.get_raw_data())) {
186 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<double>(data.get_raw_data()));
187 } else if (std::holds_alternative<bool>(data.get_raw_data())) {
188 auto s = (std::get<bool>(data.get_raw_data()) ? "true" : "false");
189 out = ASTARTE_NS_FORMAT::format_to(out, "{}", s);
190 } else if (std::holds_alternative<std::string>(data.get_raw_data())) {
191 out =
192 ASTARTE_NS_FORMAT::format_to(out, R"("{}")", std::get<std::string>(data.get_raw_data()));
193 } else if (std::holds_alternative<std::vector<uint8_t>>(data.get_raw_data())) {
194 utils::format_base64(out, std::get<std::vector<uint8_t>>(data.get_raw_data()));
195 } else if (std::holds_alternative<std::chrono::system_clock::time_point>(data.get_raw_data())) {
197 std::get<std::chrono::system_clock::time_point>(data.get_raw_data()));
198 } else if (std::holds_alternative<std::vector<int32_t>>(data.get_raw_data())) {
199 utils::format_vector(out, std::get<std::vector<int32_t>>(data.get_raw_data()));
200 } else if (std::holds_alternative<std::vector<int64_t>>(data.get_raw_data())) {
201 utils::format_vector(out, std::get<std::vector<int64_t>>(data.get_raw_data()));
202 } else if (std::holds_alternative<std::vector<double>>(data.get_raw_data())) {
203 utils::format_vector(out, std::get<std::vector<double>>(data.get_raw_data()));
204 } else if (std::holds_alternative<std::vector<bool>>(data.get_raw_data())) {
205 utils::format_vector(out, std::get<std::vector<bool>>(data.get_raw_data()));
206 } else if (std::holds_alternative<std::vector<std::string>>(data.get_raw_data())) {
207 utils::format_vector(out, std::get<std::vector<std::string>>(data.get_raw_data()));
208 } else if (std::holds_alternative<std::vector<std::vector<uint8_t>>>(data.get_raw_data())) {
209 utils::format_vector(out, std::get<std::vector<std::vector<uint8_t>>>(data.get_raw_data()));
210 } else if (std::holds_alternative<std::vector<std::chrono::system_clock::time_point>>(
211 data.get_raw_data())) {
213 out, std::get<std::vector<std::chrono::system_clock::time_point>>(data.get_raw_data()));
214 }
215
216 return out;
217 }
218};
219
220inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteData data) {
221 out << ASTARTE_NS_FORMAT::format("{}", data);
222 return out;
223}
224
228template <>
229struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteType> {
235 template <typename ParseContext>
236 constexpr auto parse(ParseContext& ctx) const {
237 return ctx.begin();
238 }
239
246 template <typename FormatContext>
247 auto format(const AstarteDeviceSdk::AstarteType& typ, FormatContext& ctx) const {
248 std::string_view name = "Unknown Type";
249
250 switch (typ) {
252 name = "BinaryBlob";
253 break;
255 name = "Boolean";
256 break;
258 name = "Datetime";
259 break;
261 name = "Double";
262 break;
264 name = "Integer";
265 break;
267 name = "LongInteger";
268 break;
270 name = "String";
271 break;
273 name = "BinaryBlobArray";
274 break;
276 name = "BooleanArray";
277 break;
279 name = "DatetimeArray";
280 break;
282 name = "DoubleArray";
283 break;
285 name = "IntegerArray";
286 break;
288 name = "LongIntegerArray";
289 break;
291 name = "StringArray";
292 break;
293 }
294
295 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", name);
296 }
297};
298
299inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteType typ) {
300 out << ASTARTE_NS_FORMAT::format("{}", typ);
301 return out;
302}
303
308template <>
309struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteDatastreamIndividual> {
315 template <typename ParseContext>
316 constexpr auto parse(ParseContext& ctx) const {
317 return ctx.begin();
318 }
319
326 template <typename FormatContext>
327 auto format(const AstarteDeviceSdk::AstarteDatastreamIndividual& data, FormatContext& ctx) const {
328 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", data.get_value());
329 }
330};
331
332inline std::ostream& operator<<(std::ostream& out,
334 out << ASTARTE_NS_FORMAT::format("{}", data);
335 return out;
336}
337
341template <>
342struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteDatastreamObject> {
348 template <typename ParseContext>
349 constexpr auto parse(ParseContext& ctx) const {
350 return ctx.begin();
351 }
352
359 template <typename FormatContext>
360 auto format(const AstarteDeviceSdk::AstarteDatastreamObject& data, FormatContext& ctx) const {
361 auto out = ctx.out();
362 out = ASTARTE_NS_FORMAT::format_to(out, "{{");
363
364 bool first = true;
365 for (const auto& pair : data.get_raw_data()) {
366 if (!first) {
367 out = ASTARTE_NS_FORMAT::format_to(out, ", ");
368 }
369 out = ASTARTE_NS_FORMAT::format_to(out, R"("{}": {})", pair.first, pair.second);
370 first = false;
371 }
372
373 out = ASTARTE_NS_FORMAT::format_to(out, "}}");
374 return out;
375 }
376};
377
378inline std::ostream& operator<<(std::ostream& out,
380 out << ASTARTE_NS_FORMAT::format("{}", data);
381 return out;
382}
383
388template <>
389struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstartePropertyIndividual> {
395 template <typename ParseContext>
396 constexpr auto parse(ParseContext& ctx) const {
397 return ctx.begin();
398 }
399
406 template <typename FormatContext>
407 auto format(const AstarteDeviceSdk::AstartePropertyIndividual& data, FormatContext& ctx) const {
408 if (data.get_value().has_value()) {
409 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", data.get_value().value());
410 }
411
412 return ctx.out();
413 }
414};
415
416inline std::ostream& operator<<(std::ostream& out,
418 out << ASTARTE_NS_FORMAT::format("{}", data);
419 return out;
420}
421
425template <>
426struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteMessage> {
432 template <typename ParseContext>
433 constexpr auto parse(ParseContext& ctx) const {
434 return ctx.begin();
435 }
436
443 template <typename FormatContext>
444 auto format(const AstarteDeviceSdk::AstarteMessage& msg, FormatContext& ctx) const {
445 auto out = ctx.out();
446
447 out = ASTARTE_NS_FORMAT::format_to(out, "{{interface: {}, path: {}", msg.get_interface(),
448 msg.get_path());
449
450 // check if the payload is an unset property, which is the only "empty" case
451 bool is_unset_prop = false;
452 const auto* prop =
453 std::get_if<AstarteDeviceSdk::AstartePropertyIndividual>(&msg.get_raw_data());
454 if (prop && !prop->get_value().has_value()) {
455 is_unset_prop = true;
456 }
457
458 if (!is_unset_prop) {
459 out = ASTARTE_NS_FORMAT::format_to(out, ", value: ");
460 std::visit([&out](const auto& arg) { out = ASTARTE_NS_FORMAT::format_to(out, "{}", arg); },
461 msg.get_raw_data());
462 }
463
464 return ASTARTE_NS_FORMAT::format_to(out, "}}");
465 }
466};
467
468inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteMessage msg) {
469 out << ASTARTE_NS_FORMAT::format("{}", msg);
470 return out;
471}
472
476template <>
477struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteStoredProperty> {
483 template <typename ParseContext>
484 constexpr auto parse(ParseContext& ctx) const {
485 return ctx.begin();
486 }
487
494 template <typename FormatContext>
495 auto format(const AstarteDeviceSdk::AstarteStoredProperty& prop, FormatContext& ctx) const {
496 return ASTARTE_NS_FORMAT::format_to(
497 ctx.out(), "Interface: {} v{}, Path: {}, Ownership: {}, Value: {}",
498 prop.get_interface_name(), prop.get_version_major(), prop.get_path(),
499 (prop.get_ownership() == AstarteDeviceSdk::AstarteOwnership::kDevice ? "device" : "server"),
500 prop.get_value());
501 }
502};
503
504inline std::ostream& operator<<(std::ostream& out,
506 out << ASTARTE_NS_FORMAT::format("{}", prop);
507 return out;
508}
509
511
512#endif // ASTARTE_FORMATTER_H
Astarte data class, representing the basic Astarte types.
Definition data.hpp:39
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.
Astarte message class, represents a full message for/from Astarte.
Definition msg.hpp:25
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.
Representing data for a stored property.
Definition stored_property.hpp:23
auto get_ownership() const -> const AstarteOwnership &
Get the ownership contained within the object.
auto get_version_major() const -> int32_t
Get the major version within the object.
auto get_interface_name() const -> const std::string &
Get the interface name contained within the object.
auto get_path() const -> const std::string &
Get the path contained within the object.
auto get_value() const -> const 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:22
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
@ kDevice
Ownership is retained by the device.
Definition ownership.hpp:21
Contain utility functions for formatting data.
Definition formatter.hpp:36
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:92
void format_vector(OutputIt &out, const std::vector< T > &data)
Format a generic vector into a comma-separated list in brackets.
Definition formatter.hpp:139
void format_base64(OutputIt &out, const std::vector< uint8_t > &data)
Format a vector of bytes into a Base64 string literal.
Definition formatter.hpp:47
void format_data(OutputIt &out, const T &data)
Format a generic data type into an output iterator.
Definition formatter.hpp:117
Astarte object class and its related methods.
Ownership definitions for communication with Astarte.
Astarte stored property class and its related methods.
Types definitions for communication with Astarte.