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>
16namespace astarte_fmt = ::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>
22namespace astarte_fmt = ::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_fmt::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_fmt::format_to(out, "{}{}{}{}", base64_chars[(chunk >> 18) & 0x3F],
61 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_fmt::format_to(out, "{}{}", base64_chars[(chunk >> 18) & 0x3F],
73 base64_chars[(chunk >> 12) & 0x3F]);
74 if (idx + 1 < len) {
75 out = astarte_fmt::format_to(out, "{}=", base64_chars[(chunk >> 6) & 0x3F]);
76 } else {
77 out = astarte_fmt::format_to(out, "==");
78 }
79 }
80
81 out = astarte_fmt::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_fmt::format_to(out, "\"");
94#if (__cplusplus >= 202002L) && (__has_include(<format>))
95 out = astarte_fmt::format_to(
96 out, "{}",
97 astarte_fmt::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_fmt::format_to(out, "{}", stream.str());
105#endif // (__cplusplus >= 202002L) && (__has_include(<format>))
106 out = astarte_fmt::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_fmt::format_to(out, "{}", (data ? "true" : "false"));
120 } else if constexpr (std::is_same_v<T, std::string>) {
121 astarte_fmt::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_fmt::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_fmt::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_fmt::format_to(out, ", ");
145 }
146 }
147 out = astarte_fmt::format_to(out, "]");
148}
149// NOLINTEND(concurrency-mt-unsafe)
150
151} // namespace utils
152
155
159template <>
160struct astarte_fmt::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_fmt::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_fmt::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_fmt::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_fmt::format_to(out, "{}", s);
190 } else if (std::holds_alternative<std::string>(data.get_raw_data())) {
191 out = astarte_fmt::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_fmt::format("{}", data);
221 return out;
222}
223
227template <>
228struct astarte_fmt::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_fmt::format_to(ctx.out(), "{}", name);
295 }
296};
297
298inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteType typ) {
299 out << astarte_fmt::format("{}", typ);
300 return out;
301}
302
307template <>
308struct astarte_fmt::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_fmt::format_to(ctx.out(), "{}", data.get_value());
328 }
329};
330
331inline std::ostream& operator<<(std::ostream& out,
333 out << astarte_fmt::format("{}", data);
334 return out;
335}
336
341template <>
342struct astarte_fmt::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_fmt::format_to(out, "{{");
363
364 bool first = true;
365 for (const auto& pair : data.get_raw_data()) {
366 if (!first) {
367 out = astarte_fmt::format_to(out, ", ");
368 }
369 out = astarte_fmt::format_to(out, R"("{}": {})", pair.first, pair.second);
370 first = false;
371 }
372
373 out = astarte_fmt::format_to(out, "}}");
374 return out;
375 }
376};
377
378inline std::ostream& operator<<(std::ostream& out,
380 out << astarte_fmt::format("{}", data);
381 return out;
382}
383
388template <>
389struct astarte_fmt::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_fmt::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_fmt::format("{}", data);
419 return out;
420}
421
425template <>
426struct astarte_fmt::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_fmt::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_fmt::format_to(out, ", value: ");
460 std::visit([&out](const auto& arg) { out = astarte_fmt::format_to(out, "{}", arg); },
461 msg.get_raw_data());
462 }
463
464 return astarte_fmt::format_to(out, "}}");
465 }
466};
467
468inline std::ostream& operator<<(std::ostream& out, const AstarteDeviceSdk::AstarteMessage msg) {
469 out << astarte_fmt::format("{}", msg);
470 return out;
471}
472
476template <>
477struct astarte_fmt::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_fmt::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_fmt::format("{}", prop);
507 return out;
508}
509
511
512#endif // ASTARTE_FORMATTER_H
Astarte data class, representing the basic Astarte types.
Definition data.hpp:40
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:23
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.