Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "wrappers.qpb.h"
#include <QtProtobufWellKnownTypes/duration.qpb.h>
#include <QtProtobufWellKnownTypes/private/qprotobufwellknowntypesjsonserializers_p.h>
#include <QtProtobufWellKnownTypes/timestamp.qpb.h>
#include <QtProtobuf/private/protobufscalarjsonserializers_p.h>
#include <QtProtobuf/private/qprotobufjsonserializer_p.h>
#include <QtProtobuf/private/qtprotobuflogging_p.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qtimezone.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
namespace {
QJsonValue serializeProtobufWellKnownTimestamp(const QProtobufMessage *message)
{
qint64 secs = 0;
qint32 nanos = 0;
if (const auto secondsValue = message->property("seconds"); secondsValue.canConvert<qint64>()) {
secs = secondsValue.value<qint64>();
} else {
qProtoWarning("Failed to convert seconds");
}
if (const auto nanosValue = message->property("nanos"); nanosValue.canConvert<qint32>()) {
nanos = nanosValue.value<qint32>();
} else {
qProtoWarning("Failed to convert nanos");
}
const auto datetime = QDateTime::fromMSecsSinceEpoch(secs * 1000 + nanos / 1000'000,
QTimeZone::UTC);
return ProtobufScalarJsonSerializers::serializeCommon<
QString>(datetime.toString(Qt::ISODateWithMs));
}
bool deserializeProtobufWellKnownTimestamp(QProtobufMessage *message, const QJsonValue &value)
{
if (!value.isString())
return false;
const auto tsString = value.toString();
// Protobuf requires upper-case letters in timestamp string be case sensitive.
if (tsString.toUpper() != tsString)
return false;
if (tsString.contains(u' '))
return false;
//Ensure the field either ends with Z or a valid offset
static const QRegularExpression TimeStampEnding(".+([\\+\\-]\\d{2}:\\d{2}|Z)$"_L1);
if (!TimeStampEnding.match(tsString).hasMatch())
return false;
const auto datetime = QDateTime::fromString(tsString, Qt::ISODateWithMs);
if (!datetime.isValid()) {
qProtoWarning("Datetime is invalid");
return false;
}
const auto msecs = datetime.toMSecsSinceEpoch();
const qint64 seconds = msecs / 1000;
const qint32 nanos = (msecs % 1000) * 1000'000;
message->setProperty("seconds", QVariant::fromValue(seconds));
message->setProperty("nanos", QVariant::fromValue(nanos));
return true;
}
QJsonValue serializeProtobufWellKnownDuration(const QProtobufMessage *message)
{
qint64 seconds = 0;
qint32 nanos = 0;
if (const auto secondsValue = message->property("seconds"); secondsValue.canConvert<qint64>()) {
seconds = secondsValue.value<qint64>();
} else {
qProtoWarning("Failed to convert seconds");
return {QJsonValue::Undefined};
}
if (const auto nanosValue = message->property("nanos"); nanosValue.canConvert<qint32>()) {
nanos = nanosValue.value<qint32>();
} else {
qProtoWarning("Failed to convert nanos");
return {QJsonValue::Undefined};
}
if (!google::protobuf::Duration::isValid(seconds, nanos))
return {QJsonValue::Undefined};
// Reserve the maximum possible number of bytes that can be used by duration string:
// - 9 symbols for nanoseconds
// - 12 symbols for seconds
// - 1 symbol for the sign
// - 1 symbol for the floating point preriod
// - 1 symbol for the trailing 's'
static constexpr qsizetype ResultMaxSize = 24;
QString result;
result.reserve(ResultMaxSize);
if (nanos < 0) {
if (seconds == 0)
result.append(u'-');
nanos = qAbs(nanos);
}
result.append(QString::number(seconds));
if (nanos != 0) {
result.append(u'.');
// Calculate the number of '0' we need to prepend before nanos and cut the traling zeros
qint32 multiplier = 100'000'000;
for (;multiplier != 0 && nanos % multiplier != 0; multiplier /= 10) {
if (nanos / multiplier == 0)
result.append(u'0');
}
nanos /= multiplier;
result.append(QString::number(nanos));
}
result.append(u's');
Q_ASSERT_X(result.size() <= ResultMaxSize, "serializeProtobufWellKnownDuration",
"Serialized duration is malformed. It's an internal Qt issue please file a bugreport"
return ProtobufScalarJsonSerializers::serializeCommon<QString>(result);
}
bool deserializeProtobufWellKnownDuration(QProtobufMessage *message, const QJsonValue &value)
{
if (!value.isString())
return false;
const static QRegularExpression DurationRegex("^(-?)(\\d{1,12})(\\.(\\d{1,9}))?s$"_L1);
const auto valueString = value.toString();
const auto match = DurationRegex.match(valueString);
if (!match.hasMatch()) {
qProtoWarning("The input string is malformed: %s", qPrintable(valueString));
return false;
}
const auto sign = match.captured(1);
auto nanosString = match.captured(4);
auto seconds = match.captured(2).toLongLong();
qint32 nanos = nanosString.toInt();
if (!google::protobuf::Duration::isValid(seconds, nanos))
return false;
if (nanos != 0)
nanos *= qint32(qPow(10, 9 - nanosString.size()));
if (!sign.isEmpty()) {
seconds *= -1;
nanos *= -1;
}
message->setProperty("seconds", QVariant::fromValue(seconds));
message->setProperty("nanos", QVariant::fromValue(nanos));
return true;
}
template <typename T>
bool deserializeProtobufWellKnownValueType(QProtobufMessage *message, const QJsonValue &value)
{
using ReturnType = std::invoke_result_t<decltype(&T::value), T>;
bool ok = false;
message->setProperty("value",
ProtobufScalarJsonSerializers::deserializeCommon<ReturnType>(value, ok));
return ok;
}
template <typename T>
void registerProtobufWellKnownValueTypeJsonHandler()
{
QtProtobufPrivate::registerCustomJsonHandler(QMetaType::fromType<T>(), nullptr,
deserializeProtobufWellKnownValueType<T>);
}
} // namespace
void QtProtobufWellKnownTypesPrivate::registerTimestampCustomJsonHandler()
{
QtProtobufPrivate::registerCustomJsonHandler(QMetaType::fromType<google::protobuf::Timestamp>(),
serializeProtobufWellKnownTimestamp,
deserializeProtobufWellKnownTimestamp);
}
void QtProtobufWellKnownTypesPrivate::registerDurationCustomJsonHandler()
{
QtProtobufPrivate::registerCustomJsonHandler(QMetaType::fromType<google::protobuf::Duration>(),
serializeProtobufWellKnownDuration,
deserializeProtobufWellKnownDuration);
}
void QtProtobufWellKnownTypesPrivate::registerBoolValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::BoolValue>();
}
void QtProtobufWellKnownTypesPrivate::registerInt32ValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::Int32Value>();
}
void QtProtobufWellKnownTypesPrivate::registerInt64ValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::Int64Value>();
}
void QtProtobufWellKnownTypesPrivate::registerUInt32ValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::UInt32Value>();
}
void QtProtobufWellKnownTypesPrivate::registerUInt64ValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::UInt64Value>();
}
void QtProtobufWellKnownTypesPrivate::registerFloatValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::FloatValue>();
}
void QtProtobufWellKnownTypesPrivate::registerDoubleValueCustomJsonHandler()
{
registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::DoubleValue>();
}
void QtProtobufWellKnownTypesPrivate::registerStringValueCustomJsonHandler()
{
QtProtobufPrivate::
registerCustomJsonHandler(QMetaType::fromType<google::protobuf::StringValue>(), nullptr,
[](QProtobufMessage *message, const QJsonValue &value) -> bool {
if (!value.isString())
return false;
message->setProperty("value",
QVariant::fromValue(value.toString()));
return true;
});
}
void QtProtobufWellKnownTypesPrivate::registerBytesValueCustomJsonHandler()
{
QtProtobufPrivate::registerCustomJsonHandler(
QMetaType::fromType<google::protobuf::BytesValue>(), nullptr,
[](QProtobufMessage *message, const QJsonValue &value) -> bool {
if (!value.isString())
return false;
message->setProperty("value",
QVariant::fromValue(QByteArray::fromBase64(value.toString()
.toLatin1())));
return true;
});
}
QT_END_NAMESPACE