Astarte device API for C++ 0.7.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// 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
28#include "astarte_device_sdk/property.hpp"
31
35namespace utils {
36// These functions are only used for pretty printing
37// NOLINTBEGIN(concurrency-mt-unsafe)
38// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
45template <typename OutputIt>
46void format_base64(OutputIt& out, const std::vector<uint8_t>& data) {
47 static constexpr std::string_view base64_chars =
48 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
49 "abcdefghijklmnopqrstuvwxyz"
50 "0123456789+/";
51
52 size_t idx = 0;
53 const size_t len = data.size();
54
55 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
56
57 while (idx + 2 < len) {
58 const uint32_t chunk = (data[idx] << 16) | (data[idx + 1] << 8) | data[idx + 2];
59 out = ASTARTE_NS_FORMAT::format_to(
60 out, "{}{}{}{}", base64_chars[(chunk >> 18) & 0x3F], base64_chars[(chunk >> 12) & 0x3F],
61 base64_chars[(chunk >> 6) & 0x3F], base64_chars[chunk & 0x3F]);
62 idx += 3;
63 }
64
65 if (idx < len) {
66 uint32_t chunk = data[idx] << 16;
67 if (idx + 1 < len) {
68 chunk |= data[idx + 1] << 8;
69 }
70
71 out = ASTARTE_NS_FORMAT::format_to(out, "{}{}", base64_chars[(chunk >> 18) & 0x3F],
72 base64_chars[(chunk >> 12) & 0x3F]);
73 if (idx + 1 < len) {
74 out = ASTARTE_NS_FORMAT::format_to(out, "{}=", base64_chars[(chunk >> 6) & 0x3F]);
75 } else {
76 out = ASTARTE_NS_FORMAT::format_to(out, "==");
77 }
78 }
79
80 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
81}
82// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
83
90template <typename OutputIt>
91void format_timestamp(OutputIt& out, const std::chrono::system_clock::time_point& data) {
92 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
93#if (__cplusplus >= 202002L) && (__has_include(<format>))
94 out = ASTARTE_NS_FORMAT::format_to(
95 out, "{}",
96 ASTARTE_NS_FORMAT::format("{0:%F}T{0:%T}Z",
97 std::chrono::time_point_cast<std::chrono::milliseconds>(data)));
98#else // (__cplusplus >= 202002L) && (__has_include(<format>))
99 const std::time_t time = std::chrono::system_clock::to_time_t(data);
100 const std::tm utc_tm = *std::gmtime(&time);
101 std::stringstream stream;
102 stream << std::put_time(&utc_tm, "%FT%T.000Z");
103 out = ASTARTE_NS_FORMAT::format_to(out, "{}", stream.str());
104#endif // (__cplusplus >= 202002L) && (__has_include(<format>))
105 out = ASTARTE_NS_FORMAT::format_to(out, "\"");
106}
107
115template <typename OutputIt, typename T>
116void format_data(OutputIt& out, const T& data) {
117 if constexpr (std::is_same_v<T, bool>) {
118 ASTARTE_NS_FORMAT::format_to(out, "{}", (data ? "true" : "false"));
119 } else if constexpr (std::is_same_v<T, std::string>) {
120 ASTARTE_NS_FORMAT::format_to(out, R"("{}")", data);
121 } else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
122 format_base64(out, data);
123 } else if constexpr (std::is_same_v<T, std::chrono::system_clock::time_point>) {
124 format_timestamp(out, data);
125 } else { // default format case
126 ASTARTE_NS_FORMAT::format_to(out, "{}", data);
127 }
128}
129
137template <typename OutputIt, typename T>
138void format_vector(OutputIt& out, const std::vector<T>& data) {
139 out = ASTARTE_NS_FORMAT::format_to(out, "[");
140 for (size_t i = 0; i < data.size(); ++i) {
141 format_data(out, data[i]);
142 if (i != data.size() - 1) {
143 out = ASTARTE_NS_FORMAT::format_to(out, ", ");
144 }
145 }
146 out = ASTARTE_NS_FORMAT::format_to(out, "]");
147}
148// NOLINTEND(concurrency-mt-unsafe)
149
150} // namespace utils
151
154
158template <>
159struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteData> {
165 template <typename ParseContext>
166 constexpr auto parse(ParseContext& ctx) const {
167 return ctx.begin();
168 }
169
176 template <typename FormatContext>
177 auto format(const AstarteDeviceSdk::AstarteData& data, FormatContext& ctx) const {
178 auto out = ctx.out();
179
180 if (std::holds_alternative<int32_t>(data.get_raw_data())) {
181 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<int32_t>(data.get_raw_data()));
182 } else if (std::holds_alternative<int64_t>(data.get_raw_data())) {
183 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<int64_t>(data.get_raw_data()));
184 } else if (std::holds_alternative<double>(data.get_raw_data())) {
185 out = ASTARTE_NS_FORMAT::format_to(out, "{}", std::get<double>(data.get_raw_data()));
186 } else if (std::holds_alternative<bool>(data.get_raw_data())) {
187 auto s = (std::get<bool>(data.get_raw_data()) ? "true" : "false");
188 out = ASTARTE_NS_FORMAT::format_to(out, "{}", s);
189 } else if (std::holds_alternative<std::string>(data.get_raw_data())) {
190 out =
191 ASTARTE_NS_FORMAT::format_to(out, R"("{}")", std::get<std::string>(data.get_raw_data()));
192 } else if (std::holds_alternative<std::vector<uint8_t>>(data.get_raw_data())) {
193 utils::format_base64(out, std::get<std::vector<uint8_t>>(data.get_raw_data()));
194 } else if (std::holds_alternative<std::chrono::system_clock::time_point>(data.get_raw_data())) {
196 std::get<std::chrono::system_clock::time_point>(data.get_raw_data()));
197 } else if (std::holds_alternative<std::vector<int32_t>>(data.get_raw_data())) {
198 utils::format_vector(out, std::get<std::vector<int32_t>>(data.get_raw_data()));
199 } else if (std::holds_alternative<std::vector<int64_t>>(data.get_raw_data())) {
200 utils::format_vector(out, std::get<std::vector<int64_t>>(data.get_raw_data()));
201 } else if (std::holds_alternative<std::vector<double>>(data.get_raw_data())) {
202 utils::format_vector(out, std::get<std::vector<double>>(data.get_raw_data()));
203 } else if (std::holds_alternative<std::vector<bool>>(data.get_raw_data())) {
204 utils::format_vector(out, std::get<std::vector<bool>>(data.get_raw_data()));
205 } else if (std::holds_alternative<std::vector<std::string>>(data.get_raw_data())) {
206 utils::format_vector(out, std::get<std::vector<std::string>>(data.get_raw_data()));
207 } else if (std::holds_alternative<std::vector<std::vector<uint8_t>>>(data.get_raw_data())) {
208 utils::format_vector(out, std::get<std::vector<std::vector<uint8_t>>>(data.get_raw_data()));
209 } else if (std::holds_alternative<std::vector<std::chrono::system_clock::time_point>>(
210 data.get_raw_data())) {
212 out, std::get<std::vector<std::chrono::system_clock::time_point>>(data.get_raw_data()));
213 }
214
215 return out;
216 }
217};
218
219inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteData data) {
220 out << ASTARTE_NS_FORMAT::format("{}", data);
221 return out;
222}
223
227template <>
228struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteType> {
234 template <typename ParseContext>
235 constexpr auto parse(ParseContext& ctx) const {
236 return ctx.begin();
237 }
238
245 template <typename FormatContext>
246 auto format(const AstarteDeviceSdk::AstarteType& typ, FormatContext& ctx) const {
247 std::string_view name = "Unknown Type";
248
249 switch (typ) {
251 name = "BinaryBlob";
252 break;
254 name = "Boolean";
255 break;
257 name = "Datetime";
258 break;
260 name = "Double";
261 break;
263 name = "Integer";
264 break;
266 name = "LongInteger";
267 break;
269 name = "String";
270 break;
272 name = "BinaryBlobArray";
273 break;
275 name = "BooleanArray";
276 break;
278 name = "DatetimeArray";
279 break;
281 name = "DoubleArray";
282 break;
284 name = "IntegerArray";
285 break;
287 name = "LongIntegerArray";
288 break;
290 name = "StringArray";
291 break;
292 }
293
294 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", name);
295 }
296};
297
298inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteType typ) {
299 out << ASTARTE_NS_FORMAT::format("{}", typ);
300 return out;
301}
302
307template <>
308struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteDatastreamIndividual> {
314 template <typename ParseContext>
315 constexpr auto parse(ParseContext& ctx) const {
316 return ctx.begin();
317 }
318
325 template <typename FormatContext>
326 auto format(const AstarteDeviceSdk::AstarteDatastreamIndividual& data, FormatContext& ctx) const {
327 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", data.get_value());
328 }
329};
330
331inline std::ostream& operator<<(std::ostream& out,
333 out << ASTARTE_NS_FORMAT::format("{}", data);
334 return out;
335}
336
340template <>
341struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteDatastreamObject> {
347 template <typename ParseContext>
348 constexpr auto parse(ParseContext& ctx) const {
349 return ctx.begin();
350 }
351
358 template <typename FormatContext>
359 auto format(const AstarteDeviceSdk::AstarteDatastreamObject& data, FormatContext& ctx) const {
360 auto out = ctx.out();
361 out = ASTARTE_NS_FORMAT::format_to(out, "{{");
362
363 bool first = true;
364 for (const auto& pair : data.get_raw_data()) {
365 if (!first) {
366 out = ASTARTE_NS_FORMAT::format_to(out, ", ");
367 }
368 out = ASTARTE_NS_FORMAT::format_to(out, R"("{}": {})", pair.first, pair.second);
369 first = false;
370 }
371
372 out = ASTARTE_NS_FORMAT::format_to(out, "}}");
373 return out;
374 }
375};
376
377inline std::ostream& operator<<(std::ostream& out,
379 out << ASTARTE_NS_FORMAT::format("{}", data);
380 return out;
381}
382
387template <>
388struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstartePropertyIndividual> {
394 template <typename ParseContext>
395 constexpr auto parse(ParseContext& ctx) const {
396 return ctx.begin();
397 }
398
405 template <typename FormatContext>
406 auto format(const AstarteDeviceSdk::AstartePropertyIndividual& data, FormatContext& ctx) const {
407 if (data.get_value().has_value()) {
408 return ASTARTE_NS_FORMAT::format_to(ctx.out(), "{}", data.get_value().value());
409 }
410
411 return ctx.out();
412 }
413};
414
415inline std::ostream& operator<<(std::ostream& out,
417 out << ASTARTE_NS_FORMAT::format("{}", data);
418 return out;
419}
420
424template <>
425struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteMessage> {
431 template <typename ParseContext>
432 constexpr auto parse(ParseContext& ctx) const {
433 return ctx.begin();
434 }
435
442 template <typename FormatContext>
443 auto format(const AstarteDeviceSdk::AstarteMessage& msg, FormatContext& ctx) const {
444 auto out = ctx.out();
445
446 out = ASTARTE_NS_FORMAT::format_to(out, "{{interface: {}, path: {}", msg.get_interface(),
447 msg.get_path());
448
449 // check if the payload is an unset property, which is the only "empty" case
450 bool is_unset_prop = false;
451 const auto* prop =
452 std::get_if<AstarteDeviceSdk::AstartePropertyIndividual>(&msg.get_raw_data());
453 if (prop && !prop->get_value().has_value()) {
454 is_unset_prop = true;
455 }
456
457 if (!is_unset_prop) {
458 out = ASTARTE_NS_FORMAT::format_to(out, ", value: ");
459 std::visit([&out](const auto& arg) { out = ASTARTE_NS_FORMAT::format_to(out, "{}", arg); },
460 msg.get_raw_data());
461 }
462
463 return ASTARTE_NS_FORMAT::format_to(out, "}}");
464 }
465};
466
467inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteMessage msg) {
468 out << ASTARTE_NS_FORMAT::format("{}", msg);
469 return out;
470}
471
475template <>
476struct ASTARTE_NS_FORMAT::formatter<AstarteDeviceSdk::AstarteStoredProperty> {
482 template <typename ParseContext>
483 constexpr auto parse(ParseContext& ctx) const {
484 return ctx.begin();
485 }
486
493 template <typename FormatContext>
494 auto format(const AstarteDeviceSdk::AstarteStoredProperty& prop, FormatContext& ctx) const {
495 return ASTARTE_NS_FORMAT::format_to(
496 ctx.out(), "Interface: {} v{}, Path: {}, Ownership: {}, Value: {}",
497 prop.get_interface_name(), prop.get_version_major(), prop.get_path(),
498 (prop.get_ownership() == AstarteDeviceSdk::AstarteOwnership::kDevice ? "device" : "server"),
499 prop.get_value());
500 }
501};
502
503inline std::ostream& operator<<(std::ostream& out,
505 out << ASTARTE_NS_FORMAT::format("{}", prop);
506 return out;
507}
508
510
511#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:35
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:91
void format_vector(OutputIt &out, const std::vector< T > &data)
Format a generic vector into a comma-separated list in brackets.
Definition formatter.hpp:138
void format_base64(OutputIt &out, const std::vector< uint8_t > &data)
Format a vector of bytes into a Base64 string literal.
Definition formatter.hpp:46
void format_data(OutputIt &out, const T &data)
Format a generic data type into an output iterator.
Definition formatter.hpp:116
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.