QGrpcStatus: convert to THIN value type à la quip22

Provide convenience functions for: {qHash, QDebug, QDataStream,
QVariant, comparison}. Move the contained data to the header and
transform it into a THIN value class. This is a tiny class without the
prospect of extension.

Additionally add the missing test case for this class.

Task-number: QTBUG-123625
Change-Id: I2827ac0d2e9e8a2829e3294e331e1b8e26b3abdc
Reviewed-by:  Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
Dennis Oberst 2024-04-30 12:44:30 +02:00
parent 84261a38e4
commit 00d8caad17
5 changed files with 359 additions and 124 deletions

View File

@ -2,20 +2,38 @@
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qgrpcstatus.h"
#include <QtGrpc/qgrpcstatus.h>
#include <QtCore/qdatastream.h>
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
QT_BEGIN_NAMESPACE
/*!
\class QGrpcStatus
\inmodule QtGrpc
\compares equality
\compareswith equality StatusCode
\endcompareswith
\brief The QGrpcStatus class contains information about last gRPC operation.
\brief This class combines a \l StatusCode and a string message.
In case of error in call/stream processing QGrpcStatus will contain code
any of non-Ok QGrpcStatus::StatusCode.
This class combines QGrpcStatus::StatusCode and message returned from
channel or QGrpc framework.
The QGrpcStatus class contains information about the last gRPC operation
returned from the respective channel, or other functions in the QtGrpc
library.
If a RPC operation failed, contains a \l StatusCode other than \l {Ok}.
*/
/*!
\property QGrpcStatus::code
\brief QGrpcStatus::StatusCode received for prior gRPC call.
*/
/*!
\property QGrpcStatus::message
\brief Status message received for prior gRPC call.
*/
/*!
@ -64,113 +82,144 @@ QT_BEGIN_NAMESPACE
*/
/*!
\fn bool QGrpcStatus::operator==(const QGrpcStatus &lhs, QGrpcStatus::StatusCode code)
Returns \c true if \a lhs status code and \a code are equal.
Constructs a QGrpcStatus with the status code \a code and the string \a
message.
*/
/*!
\fn bool QGrpcStatus::operator!=(const QGrpcStatus &lhs, QGrpcStatus::StatusCode code)
Returns \c true if \a lhs status code and \a code are not equal.
*/
/*!
\fn bool QGrpcStatus::operator==(const QGrpcStatus &lhs, const QGrpcStatus &rhs)
Returns \c true if \a lhs status code and \a rhs status code are equal.
*/
/*!
\fn bool QGrpcStatus::operator!=(const QGrpcStatus &lhs, const QGrpcStatus &rhs)
Returns \c true if \a lhs status code and \a rhs status code are not equal.
*/
class QGrpcStatusPrivate
{
public:
QGrpcStatusPrivate(QGrpcStatus::StatusCode code) : m_code(code) { }
QGrpcStatusPrivate(QGrpcStatus::StatusCode code, const QString &message)
: m_code(code), m_message(message)
{
}
~QGrpcStatusPrivate() = default;
QGrpcStatus::StatusCode m_code;
QString m_message;
};
/*!
Creates an instance of QGrpcStatus with a status \a code only.
*/
QGrpcStatus::QGrpcStatus(StatusCode code) : dPtr(std::make_unique<QGrpcStatusPrivate>(code))
QGrpcStatus::QGrpcStatus(StatusCode code, QAnyStringView message)
: m_code(code), m_message(message.toString())
{
}
/*!
Creates an instance of QGrpcStatus with a status \a code and a \a message.
*/
QGrpcStatus::QGrpcStatus(StatusCode code, const QString &message)
: dPtr(std::make_unique<QGrpcStatusPrivate>(code, message))
{
}
/*!
Copies the \a other QGrpcStatus to this QGrpcStatus.
*/
QGrpcStatus::QGrpcStatus(const QGrpcStatus &other)
: dPtr(std::make_unique<QGrpcStatusPrivate>(other.dPtr->m_code, other.dPtr->m_message))
{
}
/*!
Moves \a other into new instance of QGrpcStatus.
*/
QGrpcStatus::QGrpcStatus(QGrpcStatus &&other) : dPtr(std::move(other.dPtr))
{
}
/*!
Assigns the \a other QGrpcStatus into this QGrpcStatus.
*/
QGrpcStatus &QGrpcStatus::operator=(const QGrpcStatus &other)
{
dPtr->m_code = other.dPtr->m_code;
dPtr->m_message = other.dPtr->m_message;
return *this;
}
/*!
Move assigns \a other into new instance of QGrpcStatus.
*/
QGrpcStatus &QGrpcStatus::operator=(QGrpcStatus &&other)
{
dPtr = std::move(other.dPtr);
return *this;
}
/*!
Destroys the QGrpcStatus.
Destroys the status object.
*/
QGrpcStatus::~QGrpcStatus() = default;
/*!
\property QGrpcStatus::code
\brief QGrpcStatus::StatusCode received for prior gRPC call.
Copy-constructs a QGrpcStatus from \a other
*/
QGrpcStatus::StatusCode QGrpcStatus::code() const noexcept
QGrpcStatus::QGrpcStatus(const QGrpcStatus &other) = default;
/*!
Assigns the data of the \a other object to this status object and returns
a reference to it.
*/
QGrpcStatus &QGrpcStatus::operator=(const QGrpcStatus &other) = default;
/*!
\fn QGrpcStatus::QGrpcStatus(QGrpcStatus &&other) noexcept
Move-constructs a new QGrpcStatus from \a other.
\note The moved-from object \a other is placed in a partially-formed state,
in which the only valid operations are destruction and assignment of a new
value.
*/
/*!
\fn QGrpcStatus& QGrpcStatus::operator=(QGrpcStatus &&other) noexcept
Move-assigns \a other to this QGrpcStatus instance and returns a reference
to it.
\note The moved-from object \a other is placed in a partially-formed state,
in which the only valid operations are destruction and assignment of a new
value.
*/
/*!
\since 6.8
Constructs a new QVariant object from this QGrpcStatus.
*/
QGrpcStatus::operator QVariant() const
{
return dPtr->m_code;
return QVariant::fromValue(*this);
}
/*!
\property QGrpcStatus::message
\brief Status message received for prior gRPC call.
\since 6.8
\fn void QGrpcStatus::swap(QGrpcStatus &other) noexcept
Swaps this instance with \a other. This operation is very fast and never fails.
*/
QString QGrpcStatus::message() const noexcept
/*!
\fn QGrpcStatus::StatusCode QGrpcStatus::code() const noexcept
Returns the contained \l StatusCode.
*/
/*!
\fn QString QGrpcStatus::message() const noexcept
Returns the contained status message.
*/
/*!
\fn bool QGrpcStatus::operator==(const QGrpcStatus &lhs, const StatusCode &rhs) noexcept
Returns \c true if the status codes in \a lhs and \a rhs are equal.
*/
/*!
\fn bool QGrpcStatus::operator!=(const QGrpcStatus &lhs, const StatusCode &rhs) noexcept
Returns \c true if the status codes in \a lhs and \a rhs are not equal.
*/
/*!
\fn bool QGrpcStatus::operator==(const QGrpcStatus &lhs, const QGrpcStatus &rhs) noexcept
Returns \c true if the status codes in \a lhs and \a rhs are equal.
*/
/*!
\fn bool QGrpcStatus::operator!=(const QGrpcStatus &lhs, const QGrpcStatus &rhs) noexcept
Returns \c true if the status codes in \a lhs and \a rhs are not equal.
*/
/*!
\since 6.8
\fn size_t QGrpcStatus::qHash(const QGrpcStatus &key, size_t seed) noexcept
Returns the hash value of \a key, using \a seed to seed the calculation.
*/
#ifndef QT_NO_DEBUG_STREAM
/*!
\since 6.8
\fn QDebug QGrpcStatus::operator<<(QDebug debug, const QGrpcStatus& status)
Writes \a status to the specified stream \a debug.
*/
QDebug operator<<(QDebug debug, const QGrpcStatus &status)
{
return dPtr->m_message;
const QDebugStateSaver save(debug);
debug.nospace() << "QGrpcStatus( code: " << status.code() << ", message: " << status.message()
<< " )";
return debug;
}
#endif // QT_NO_DEBUG_STREAM
#ifndef QT_NO_DATASTREAM
/*!
\since 6.8
\fn QDataStream &QGrpcStatus::operator<<(QDataStream &out, const QGrpcStatus &status)
Writes the given \a status to the specified stream \a out.
*/
QDataStream &operator<<(QDataStream &out, const QGrpcStatus &status)
{
out << status.m_code << status.m_message;
return out;
}
/*!
\since 6.8
\fn QDataStream &QGrpcStatus::operator>>(QDataStream &in, QGrpcStatus &status)
Reads a QGrpcStatus from stream \a in into \a status.
*/
QDataStream &operator>>(QDataStream &in, QGrpcStatus &status)
{
in >> status.m_code;
in >> status.m_message;
return in;
}
#endif // QT_NO_DATASTREAM
QT_END_NAMESPACE
#include "moc_qgrpcstatus.cpp"

View File

@ -5,20 +5,24 @@
#ifndef QGRPCSTATUS_H
#define QGRPCSTATUS_H
#include <QtCore/qmetatype.h>
#include <QtCore/qstring.h>
#include <QtCore/qobjectdefs.h>
#include <QtGrpc/qtgrpcglobal.h>
#include <memory>
#include <QtCore/qanystringview.h>
#include <QtCore/qcompare.h>
#include <QtCore/qmetatype.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qstring.h>
#include <QtCore/qtclasshelpermacros.h>
QT_BEGIN_NAMESPACE
class QGrpcStatusPrivate;
class QDataStream;
class QDebug;
class QVariant;
class Q_GRPC_EXPORT QGrpcStatus final
class QGrpcStatus final
{
Q_GADGET
Q_GADGET_EXPORT(Q_GRPC_EXPORT)
Q_PROPERTY(StatusCode code READ code CONSTANT)
Q_PROPERTY(QString message READ message CONSTANT)
@ -42,45 +46,57 @@ public:
DataLoss = 15,
Unauthenticated = 16,
};
Q_ENUM(StatusCode)
explicit QGrpcStatus(StatusCode code = StatusCode::Ok);
explicit QGrpcStatus(StatusCode code, const QString &message);
~QGrpcStatus();
Q_GRPC_EXPORT Q_IMPLICIT QGrpcStatus(StatusCode code = Ok, QAnyStringView message = {});
Q_GRPC_EXPORT ~QGrpcStatus();
Q_GRPC_EXPORT QGrpcStatus(const QGrpcStatus &other);
Q_GRPC_EXPORT QGrpcStatus &operator=(const QGrpcStatus &other);
QGrpcStatus(QGrpcStatus &&other) noexcept = default;
QGrpcStatus &operator=(QGrpcStatus &&other) noexcept = default;
QGrpcStatus(const QGrpcStatus &other);
QGrpcStatus &operator=(const QGrpcStatus &other);
Q_GRPC_EXPORT Q_IMPLICIT operator QVariant() const;
QGrpcStatus(QGrpcStatus &&other);
QGrpcStatus &operator=(QGrpcStatus &&other);
void swap(QGrpcStatus &other) noexcept
{
std::swap(m_code, other.m_code);
m_message.swap(other.m_message);
}
[[nodiscard]] StatusCode code() const noexcept;
[[nodiscard]] QString message() const noexcept;
[[nodiscard]] StatusCode code() const noexcept { return m_code; }
[[nodiscard]] QString message() const noexcept { return m_message; }
private:
friend bool operator==(const QGrpcStatus &lhs, QGrpcStatus::StatusCode code)
QGrpcStatus::StatusCode m_code;
QString m_message;
friend bool comparesEqual(const QGrpcStatus &lhs, StatusCode rhs) noexcept
{
return lhs.code() == code;
return lhs.code() == rhs;
}
friend bool operator!=(const QGrpcStatus &lhs, QGrpcStatus::StatusCode code)
{
return lhs.code() != code;
}
friend bool operator==(const QGrpcStatus &lhs, const QGrpcStatus &rhs)
friend bool comparesEqual(const QGrpcStatus &lhs, const QGrpcStatus &rhs) noexcept
{
return lhs.code() == rhs.code();
}
friend bool operator!=(const QGrpcStatus &lhs, const QGrpcStatus &rhs)
Q_DECLARE_EQUALITY_COMPARABLE(QGrpcStatus, StatusCode)
Q_DECLARE_EQUALITY_COMPARABLE(QGrpcStatus)
friend size_t qHash(const QGrpcStatus &key, size_t seed = 0) noexcept
{
return lhs.code() == rhs.code();
return qHash(key.code(), seed);
}
std::unique_ptr<QGrpcStatusPrivate> dPtr;
#ifndef QT_NO_DEBUG_STREAM
friend Q_GRPC_EXPORT QDebug operator<<(QDebug debug, const QGrpcStatus &status);
#endif
#ifndef QT_NO_DATASTREAM
friend Q_GRPC_EXPORT QDataStream &operator<<(QDataStream &out, const QGrpcStatus &status);
friend Q_GRPC_EXPORT QDataStream &operator>>(QDataStream &in, QGrpcStatus &status);
#endif
};
Q_DECLARE_SHARED(QGrpcStatus)
QT_END_NAMESPACE
Q_DECLARE_METATYPE(QGrpcStatus)
#endif // QGRPCSTATUS_H

View File

@ -9,4 +9,5 @@ if(TARGET WrapgRPC::WrapLibgRPC)
add_subdirectory(server)
add_subdirectory(qgrpchttp2channel)
add_subdirectory(qgrpcserializationformat)
add_subdirectory(qgrpcstatus)
endif()

View File

@ -0,0 +1,17 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
cmake_minimum_required(VERSION 3.16)
project(tst_qgrpcstatus LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
endif()
qt_internal_add_test(tst_qgrpcstatus
SOURCES
tst_qgrpcstatus.cpp
LIBRARIES
Qt::Test
Qt::Core
Qt::Grpc
)

View File

@ -0,0 +1,152 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtCore/qbuffer.h>
#include <QtCore/qobject.h>
#include <QtGrpc/qgrpcstatus.h>
#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
class QGrpcStatusTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void defaultConstructedIsOk() const;
void hasSpecialMemberFunctions() const;
void hasImplicitQVariant() const;
void hasMemberSwap() const;
void equalityComparable() const;
void qHashOnlyStatusCodes() const;
void streamsToDebug() const;
void streamsToDataStream() const;
};
void QGrpcStatusTest::defaultConstructedIsOk() const
{
QGrpcStatus s1;
QVERIFY(s1.message().isEmpty());
QCOMPARE_EQ(s1.code(), QGrpcStatus::Ok);
QGrpcStatus s2(QGrpcStatus::Ok);
QCOMPARE_EQ(s1.code(), s2.code());
QCOMPARE_EQ(s1.message(), s2.message());
}
void QGrpcStatusTest::hasSpecialMemberFunctions() const
{
const auto check = [](const QGrpcStatus &s1, const QGrpcStatus &s2) {
QCOMPARE_EQ(s1.code(), s2.code());
QCOMPARE_EQ(s1.message(), s2.message());
};
QString msg = u"(◕д◕✿)😀"_s;
QGrpcStatus s1 = { QGrpcStatus::PermissionDenied, msg };
QCOMPARE_EQ(s1.code(), QGrpcStatus::PermissionDenied);
QCOMPARE_EQ(s1.message(), msg);
QGrpcStatus s2(s1);
check(s1, s2);
QGrpcStatus s3 = s2;
check(s2, s3);
QGrpcStatus s4(std::move(s3));
check(s4, s1);
QGrpcStatus s5 = std::move(s4);
check(s5, s1);
}
void QGrpcStatusTest::hasImplicitQVariant() const
{
QGrpcStatus s1(QGrpcStatus::DataLoss, "testcase");
QVariant var1 = s1;
QVERIFY(var1.isValid());
const auto s2 = var1.value<QGrpcStatus>();
QCOMPARE_EQ(s1.code(), s2.code());
QCOMPARE_EQ(s1.message(), s2.message());
}
void QGrpcStatusTest::hasMemberSwap() const
{
QGrpcStatus s1(QGrpcStatus::DataLoss, "testcase");
QGrpcStatus s2(s1);
QGrpcStatus s3 = {};
s1.swap(s3);
QCOMPARE_EQ(s1.code(), QGrpcStatus::Ok);
QVERIFY(s1.message().isEmpty());
QCOMPARE_EQ(s3.code(), s2.code());
QCOMPARE_EQ(s3.message(), s2.message());
}
void QGrpcStatusTest::equalityComparable() const
{
QGrpcStatus s1;
QGrpcStatus s2(QGrpcStatus::Internal);
QCOMPARE_NE(s1, s2);
s1 = s2;
QCOMPARE_EQ(s1, s2);
s1 = { QGrpcStatus::Internal, "message is ignored" };
QCOMPARE_EQ(s1, s2);
}
void QGrpcStatusTest::qHashOnlyStatusCodes() const
{
QGrpcStatus s1;
QGrpcStatus s2(QGrpcStatus::Internal);
const auto hash1 = qHash(s1);
const auto hash2 = qHash(s2);
QCOMPARE_NE(hash1, hash2);
QGrpcStatus s3(QGrpcStatus::Internal, "ignored");
const auto hash3 = qHash(s3);
QCOMPARE_EQ(hash2, hash3);
QCOMPARE_NE(hash1, hash3);
}
void QGrpcStatusTest::streamsToDebug() const
{
QGrpcStatus s1;
QGrpcStatus s2 = { QGrpcStatus::OutOfRange, "test" };
QByteArray storage;
QBuffer buf(&storage);
QDebug dbg(&buf);
QVERIFY(buf.data().isEmpty());
buf.open(QIODevice::WriteOnly);
dbg << s1 << s2;
buf.close();
QVERIFY(!buf.data().isEmpty());
}
void QGrpcStatusTest::streamsToDataStream() const
{
QGrpcStatus s1;
QGrpcStatus s2 = { QGrpcStatus::OutOfRange };
QGrpcStatus s3 = { QGrpcStatus::Unimplemented, "test" };
QByteArray storage;
QBuffer buf(&storage);
// QDataStream
QDataStream ds(&buf);
QVERIFY(buf.data().isEmpty());
buf.open(QIODevice::ReadWrite);
ds << s1 << s2 << s3;
buf.seek(0);
QGrpcStatus os1, os2, os3;
ds >> os1 >> os2 >> os3;
QCOMPARE_EQ(s1, os1);
QCOMPARE_EQ(s1.message(), os1.message());
QCOMPARE_EQ(s2, os2);
QCOMPARE_EQ(s2.message(), os2.message());
QCOMPARE_EQ(s3, os3);
QCOMPARE_EQ(s3.message(), os3.message());
buf.close();
QVERIFY(!buf.data().isEmpty());
}
QTEST_MAIN(QGrpcStatusTest)
#include "tst_qgrpcstatus.moc"