QGrpcOperation: handle failed deserialization directly

Originally motivated by Axivion(SV3), which nagged about the const
errorOccurred signal; this patch removes signal emission for failed
deserialization in the read() functions. Immediately handling this can
lead to better user code as an fallback mechanism can avoid further
execution of unneeded logic.

This patch makes the errorOcurred signal non-const and changes the
signature of the read methods to either return an optional or bool to
signal failure immediately.

Users can then retrieve the error through 'deserializationError()' or
'deserializationErrorString()'. This can be seen in the generated QML
integration code, which uses those to still emit the 'errorOcurred'
signal on deserialization failure.

Change-Id: Ie6761753145536a42d5dd5bf1eac18afa555581a
Reviewed-by:  Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
Dennis Oberst 2024-04-15 13:01:07 +02:00
parent 636b70f93d
commit 57b875c33b
14 changed files with 164 additions and 165 deletions

View File

@ -82,8 +82,8 @@ void SimpleChatEngine::login(const QString &name, const QString &password)
emit userNameChanged();
}
setState(Connected);
m_messages.append(
stream->read<qtgrpc::examples::chat::ChatMessages>().messages());
if (const auto msg = stream->read<qtgrpc::examples::chat::ChatMessages>())
m_messages.append(msg->messages());
});
// ![1]
}

View File

@ -30,10 +30,12 @@ void NaviThread::run()
Empty request;
m_stream = m_client->streamGetNaviStream(request);
connect(m_stream.get(), &QGrpcServerStream::messageReceived, this, [this] {
DistanceMsg result = m_stream->read<DistanceMsg>();
emit totalDistanceChanged(result.totalDistance());
emit remainingDistanceChanged(result.remainingDistance());
emit directionChanged(result.direction());
const auto result = m_stream->read<DistanceMsg>();
if (!result)
return;
emit totalDistanceChanged(result->totalDistance());
emit remainingDistanceChanged(result->remainingDistance());
emit directionChanged(result->direction());
});
connect(m_stream.get(), &QGrpcServerStream::errorOccurred, this,

View File

@ -36,16 +36,15 @@ void VehicleThread::run()
});
connect(replyFuel.get(), &QGrpcCallReply::finished, [replyFuel, this] {
FuelLevelMsg fuelLvl = replyFuel->read<FuelLevelMsg>();
emit fuelLevelChanged(fuelLvl.fuelLevel());
if (const auto fuelLvl = replyFuel->read<FuelLevelMsg>())
emit fuelLevelChanged(fuelLvl->fuelLevel());
});
Empty speedRequest;
m_streamSpeed = m_client->streamGetSpeedStream(speedRequest);
connect(m_streamSpeed.get(), &QGrpcServerStream::messageReceived, this, [this] {
SpeedMsg speedResponse;
speedResponse = m_streamSpeed->read<SpeedMsg>();
emit speedChanged(speedResponse.speed());
if (const auto speedResponse = m_streamSpeed->read<SpeedMsg>())
emit speedChanged(speedResponse->speed());
});
connect(m_streamSpeed.get(), &QGrpcServerStream::errorOccurred, this,
@ -62,8 +61,8 @@ void VehicleThread::run()
Empty gearRequest;
m_streamGear = m_client->streamGetGearStream(gearRequest);
connect(m_streamGear.get(), &QGrpcServerStream::messageReceived, this, [this] {
GearMsg gearResponse = m_streamGear->read<GearMsg>();
emit rpmChanged(gearResponse.rpm());
if (const auto gearResponse = m_streamGear->read<GearMsg>())
emit rpmChanged(gearResponse->rpm());
});
connect(m_streamGear.get(), &QGrpcServerStream::errorOccurred, this,

View File

@ -125,8 +125,9 @@ it, call the \c PingPong method:
auto reply = cl.PingPong(request,{});
QObject::connect(reply.get(), &QGrpcCallReply::finished, reply.get(),
[requestTime, replyPtr = reply.get()]() {
auto response = replyPtr->read<ping::pong::Pong>();
qDebug() << "Ping-Pong time difference" << response.time() - requestTime;
if (const auto response = replyPtr->read<ping::pong::Pong>())
qDebug() << "Ping-Pong time difference" << response->time() - requestTime;
qDebug() << "Failed deserialization";
});
QObject::connect(reply.get(), &QGrpcCallReply::errorOccurred, stream.get()
@ -153,8 +154,8 @@ an argument to the callback function that is used in the call:
\code
...
cl.PingPong(request, &a, [requestTime](std::shared_ptr<QGrpcCallReply> reply) {
auto response = reply->read<ping::pong::Pong>();
qDebug() << "Ping and Pong time difference" << response.time() - requestTime;
if (const auto response = reply->read<ping::pong::Pong>())
qDebug() << "Ping and Pong time difference" << response->time() - requestTime;
});
\endcode
This variant makes a connection to the \l{QGrpcCallReply::finished} signal
@ -182,9 +183,10 @@ the method that returns the pointer to \l QGrpcServerStream:
\code
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, stream.get(),
[streamPtr = stream.get(), requestTime]() {
auto response = streamPtr->read<ping::pong::Pong>();
qDebug() << "Ping-Pong next response time difference"
<< response.time() - requestTime;
if (const auto response = streamPtr->read<ping::pong::Pong>()) {
qDebug() << "Ping-Pong next response time difference"
<< response->time() - requestTime;
}
});
QObject::connect(stream.get(), &QGrpcServerStream::errorOccurred, stream.get()
@ -232,8 +234,9 @@ To send multiple requests to the server, use the
QObject::connect(stream.get(), &QGrpcServerStream::finished, stream.get(),
[streamPtr = stream.get(), &timer]{
auto response = streamPtr->read<ping::pong::Pong>();
qDebug() << "Slowest Ping time: " << response.time();
if (const auto response = streamPtr->read<ping::pong::Pong>()) {
qDebug() << "Slowest Ping time: " << response->time();
}
timer.stop();
});
@ -277,8 +280,8 @@ breaking the connection session:
QObject::connect(stream.get(), &QGrpcBidirStream::messageReceived, stream.get(),
[streamPtr = stream.get(), &timer, &maxPingPongTime, &requestTime]{
auto response = streamPtr->read<ping::pong::Pong>();
maxPingPongTime = std::max(maxPingPongTime, response.time() - requestTime);
if (const auto response = streamPtr->read<ping::pong::Pong>())
maxPingPongTime = std::max(maxPingPongTime, response->time() - requestTime);
});
QObject::connect(stream.get(), &QGrpcBidirStream::finished, stream.get(),

View File

@ -55,8 +55,8 @@ protected:
// Intercept the response
QObject::connect(response.get(), &QGrpcCallReply::finished, this,
[operation, response] {
SimpleStringMessage mess = response->read<SimpleStringMessage>();
cache.insert(operation->method(), operation->service(), mess.testFieldString());
if (const auto mess = response->read<SimpleStringMessage>())
cache.insert(operation->method(), operation->service(), mess->testFieldString());
});
// Deserialize the request
SimpleStringMessage deserializedArg;
@ -88,8 +88,8 @@ protected:
// Intercept the response
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this,
[operation, stream] {
SimpleStringMessage mess = stream->read<SimpleStringMessage>();
cache.insert_or_append(operation->method(), operation->service(), mess.testFieldString());
if (const auto mess = stream->read<SimpleStringMessage>())
cache.insert_or_append(operation->method(), operation->service(), mess->testFieldString());
});
QObject::connect(stream.get(), &QGrpcServerStream::finished, this,
[operation] {

View File

@ -55,8 +55,10 @@ protected:
auto responsePtr = response.get();
QObject::connect(responsePtr, &QGrpcServerStream::messageReceived, responsePtr,
[responsePtr]{
SimpleStringMessage mess = responsePtr->read<SimpleStringMessage>();
qDebug() << "Response received:" << mess.testFieldString();
const auto mess = responsePtr->read<SimpleStringMessage>();
if (!mess)
qDebug() << "Failed deserialization";
qDebug() << "Response received:" << mess->testFieldString();
});
}
@ -67,8 +69,8 @@ protected:
// Intercept the response
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this,
[stream] {
SimpleStringMessage mess = stream->read<SimpleStringMessage>();
qDebug() << "Response received:" << mess.testFieldString();
if (const auto mess = responsePtr->read<SimpleStringMessage>())
qDebug() << "Response received:" << mess->testFieldString();
});
// Log incoming and outgoing requests here

View File

@ -2,15 +2,13 @@
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qgrpcoperation.h"
#include "qtgrpcglobal_p.h"
#include "qgrpcchanneloperation.h"
#include <QtCore/qatomic.h>
#include <QtCore/private/qobject_p.h>
#include <QtCore/qpointer.h>
#include <QtCore/qatomic.h>
#include <QtCore/qeventloop.h>
#include <QtCore/qpointer.h>
#include <QtGrpc/private/qtgrpcglobal_p.h>
#include <QtGrpc/qgrpcchanneloperation.h>
#include <QtGrpc/qgrpcoperation.h>
QT_BEGIN_NAMESPACE
@ -24,14 +22,17 @@ using namespace Qt::StringLiterals;
*/
/*!
\fn template <typename T> T QGrpcOperation::read() const
\fn template <typename T> std::optional<T> QGrpcOperation::read() const
Reads message from raw byte array stored in QGrpcOperation.
Reads a message from a raw byte array stored within this QGrpcOperation
instance.
Returns a deserialized message or, on failure, a default-constructed
message.
If deserialization is not successful the \l QGrpcOperation::errorOccurred
signal is emitted.
Returns an optional deserialized message. On failure, \c {std::nullopt} is
returned.
The error can be retrieved using \l deserializationError.
\sa read, deserializationError, deserializationErrorString
*/
/*!
@ -44,12 +45,9 @@ using namespace Qt::StringLiterals;
*/
/*!
\fn void QGrpcOperation::errorOccurred(const QGrpcStatus &status) const
\fn void QGrpcOperation::errorOccurred(const QGrpcStatus &status)
This signal indicates the error occurred during serialization.
This signal is emitted when error with \a status occurs in channel
or during serialization.
This signal is emitted when an error with \a status occurs in the channel.
\sa QAbstractGrpcClient::errorOccurred
*/
@ -112,25 +110,23 @@ QByteArray QGrpcOperation::data() const noexcept
/*!
\since 6.8
Reads a message from a raw byte array stored in QGrpcOperation.
Reads a message from a raw byte array which is stored within this
QGrpcOperation instance.
The function writes a deserialized value to \a message pointer.
The function writes the deserialized value to the \a message pointer.
If deserialization is not successful the \l QGrpcOperation::errorOccurred
signal is emitted.
\note This function has slower message deserialization compared to its
template counterpart.
If the deserialization is successful, this function returns \c true.
Otherwise, it returns \c false, and the error can be retrieved with \l
deserializationError.
\sa read, deserializationError, deserializationErrorString
*/
void QGrpcOperation::read(QProtobufMessage *message) const
bool QGrpcOperation::read(QProtobufMessage *message) const
{
Q_ASSERT_X(message != nullptr, "QGrpcOperation::read",
"Can't read to nullptr QProtobufMessage");
if (auto ser = serializer(); ser) {
if (!ser->deserialize(message, data()))
emit errorOccurred(deserializationError());
}
const auto ser = d_func()->channelOperation->serializer();
return ser && ser->deserialize(message, data());
}
/*!
@ -222,39 +218,6 @@ bool QGrpcOperation::isFinished() const noexcept
return d_func()->isFinished.loadRelaxed();
}
QGrpcStatus QGrpcOperation::deserializationError() const
{
QGrpcStatus status;
switch (serializer()->deserializationError()) {
case QAbstractProtobufSerializer::InvalidHeaderError: {
const QString errStr = tr("Response deserialization failed: invalid field found.");
status = QGrpcStatus{ QGrpcStatus::InvalidArgument, errStr };
qGrpcWarning() << errStr;
emit errorOccurred(status);
} break;
case QAbstractProtobufSerializer::NoDeserializerError: {
const QString errStr = tr("No deserializer was found for a given type.");
status = QGrpcStatus{ QGrpcStatus::InvalidArgument, errStr };
qGrpcWarning() << errStr;
emit errorOccurred(status);
} break;
case QAbstractProtobufSerializer::UnexpectedEndOfStreamError: {
const QString errStr = tr("Invalid size of received buffer.");
status = QGrpcStatus{ QGrpcStatus::OutOfRange, errStr };
qGrpcWarning() << errStr;
emit errorOccurred(status);
} break;
case QAbstractProtobufSerializer::NoError:
Q_FALLTHROUGH();
default:
const QString errStr = tr("Deserializing failed, but no error was set.");
status = QGrpcStatus{ QGrpcStatus::InvalidArgument, errStr };
qGrpcWarning() << errStr;
emit errorOccurred(status);
}
return status;
}
QT_END_NAMESPACE
#include "moc_qgrpcoperation.cpp"

View File

@ -24,17 +24,14 @@ public:
~QGrpcOperation() override;
template <typename T>
T read() const
std::optional<T> read() const
{
T value;
if (auto ser = serializer(); ser) {
if (!ser->deserialize(&value, data()))
errorOccurred(deserializationError());
}
return value;
const auto ser = serializer();
return ser && ser->deserialize(&value, data()) ? std::optional<T>(value) : std::nullopt;
}
void read(QProtobufMessage *message) const;
bool read(QProtobufMessage *message) const;
[[nodiscard]] QAbstractProtobufSerializer::DeserializationError deserializationError() const;
[[nodiscard]] QString deserializationErrorString() const;
@ -47,7 +44,7 @@ public:
Q_SIGNALS:
void finished();
void errorOccurred(const QGrpcStatus &status) const;
void errorOccurred(const QGrpcStatus &status);
protected:
explicit QGrpcOperation(std::shared_ptr<QGrpcChannelOperation> channelOperation,
@ -58,11 +55,9 @@ protected:
private:
Q_DISABLE_COPY_MOVE(QGrpcOperation)
Q_DECLARE_PRIVATE(QGrpcOperation)
[[nodiscard]] QByteArray data() const noexcept;
[[nodiscard]] QGrpcStatus deserializationError() const;
Q_DECLARE_PRIVATE(QGrpcOperation)
};
QT_END_NAMESPACE

View File

@ -107,8 +107,14 @@ const char *GrpcTemplates::ClientMethodDefinitionQmlTemplate()
" std::shared_ptr<QGrpcCallReply> reply = call(\"$method_name$\"_L1, "
"$param_name$, options);\n"
" reply->subscribe(jsEngine, [reply, callback, jsEngine]() {\n"
" auto result = reply->read<$return_type$>();\n"
" callback.call(QJSValueList{jsEngine->toScriptValue(result)});\n"
" if (const auto result = reply->read<$return_type$>()) {\n"
" callback.call(QJSValueList{jsEngine->toScriptValue(*result)});\n"
" return;\n"
" }\n"
" QGrpcStatus::StatusCode code = QGrpcStatus::StatusCode::InvalidArgument;\n"
" if (reply->deserializationError() == QAbstractProtobufSerializer::UnexpectedEndOfStreamError)\n"
" code = QGrpcStatus::StatusCode::OutOfRange;\n"
" emit reply->errorOccurred(QGrpcStatus{ code, reply->deserializationErrorString() });\n"
" }, [errorCallback, jsEngine](const QGrpcStatus &status) {\n"
" errorCallback.call(QJSValueList{jsEngine->toScriptValue(status)});\n"
" });\n"

View File

@ -39,9 +39,10 @@ void QtGrpcClientBidirStreamTest::Valid()
int i = 0;
QObject::connect(stream.get(), &QGrpcBidirStream::messageReceived, this,
[stream, &request, &fullResponse, &i]() {
SimpleStringMessage rsp = stream->read<SimpleStringMessage>();
fullResponse += rsp.testFieldString() + QString::number(++i);
stream->sendMessage(request);
if (const auto rsp = stream->read<SimpleStringMessage>()) {
fullResponse += rsp->testFieldString() + QString::number(++i);
stream->sendMessage(request);
}
});
QSignalSpy streamFinishedSpy(stream.get(), &QGrpcServerStream::finished);

View File

@ -55,8 +55,8 @@ void QtGrpcClientClientStreamTest::Valid()
MessageLatencyWithThreshold * ExpectedMessageCount);
QCOMPARE(streamErrorSpy.count(), 0);
SimpleStringMessage result = stream->read<SimpleStringMessage>();
QCOMPARE_EQ(result.testFieldString(), "Stream1Stream2Stream3Stream4");
const auto result = stream->read<SimpleStringMessage>();
QCOMPARE_EQ(result->testFieldString(), "Stream1Stream2Stream3Stream4");
}
void QtGrpcClientClientStreamTest::SequentialSend()
@ -83,8 +83,9 @@ void QtGrpcClientClientStreamTest::SequentialSend()
MessageLatencyWithThreshold * ExpectedMessageCount);
QCOMPARE(streamErrorSpy.count(), 0);
SimpleStringMessage result = stream->read<SimpleStringMessage>();
QCOMPARE_EQ(result.testFieldString(), "Stream1Stream2Stream3Stream4");
const auto result = stream->read<SimpleStringMessage>();
QVERIFY(result.has_value());
QCOMPARE_EQ(result->testFieldString(), "Stream1Stream2Stream3Stream4");
}
QTEST_MAIN(QtGrpcClientClientStreamTest)

View File

@ -67,8 +67,9 @@ void QtGrpcClientServerStreamTest::Valid()
QVERIFY(streamFinishedSpy.isValid());
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&result, stream] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(streamFinishedSpy.count(), 1,
@ -96,8 +97,9 @@ void QtGrpcClientServerStreamTest::Cancel()
int i = 0;
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
if (++i == ExpectedMessageCount)
stream->cancel();
});
@ -130,8 +132,9 @@ void QtGrpcClientServerStreamTest::DeferredCancel()
int i = 0;
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
if (++i == ExpectedMessageCount)
QTimer::singleShot(MessageLatencyThreshold, stream.get(), &QGrpcServerStream::cancel);
});
@ -162,8 +165,9 @@ void QtGrpcClientServerStreamTest::HugeBlob()
QVERIFY(streamErrorSpy.isValid());
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&result, stream] {
BlobMessage ret = stream->read<BlobMessage>();
result.setTestBytes(ret.testBytes());
const auto ret = stream->read<BlobMessage>();
QVERIFY(ret.has_value());
result.setTestBytes(ret->testBytes());
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(streamFinishedSpy.count(), 1, MessageLatencyWithThreshold);
@ -200,7 +204,11 @@ void QtGrpcClientServerStreamTest::GetAsyncReply()
request.setTestFieldString("Hello Qt!");
reply = client()->testMethod(request);
reply->subscribe(this, [reply, &result] { result = reply->read<SimpleStringMessage>(); });
reply->subscribe(this, [reply, &result] {
const auto ret = reply->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result = *ret;
});
QTRY_COMPARE_WITH_TIMEOUT(result.testFieldString(), request.testFieldString(),
MessageLatencyWithThreshold);
@ -209,9 +217,15 @@ void QtGrpcClientServerStreamTest::GetAsyncReply()
request.setTestFieldString("Hello Qt1!");
reply = client()->testMethod(request);
reply->subscribe(
this, [reply, &result] { result = reply->read<SimpleStringMessage>(); },
[] { QVERIFY(false); });
reply->subscribe(this, [reply, &result] {
const auto ret = reply->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result = *ret;
},
[] {
QVERIFY(false);
}
);
QTRY_COMPARE_WITH_TIMEOUT(result.testFieldString(), request.testFieldString(),
MessageLatencyWithThreshold);
@ -238,8 +252,9 @@ void QtGrpcClientServerStreamTest::MultipleStreams()
QVERIFY(steamMessageRecievedSpy.isValid());
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&result, stream] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(streamFinishedSpy.count(), 1,
@ -320,9 +335,10 @@ void QtGrpcClientServerStreamTest::InThread()
auto stream = client()->streamTestMethodServerStream(request);
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, &waiter,
[&result, &i, &waiter, stream] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString()
+ ret.testFieldString());
+ ret->testFieldString());
if (++i == 4)
waiter.quit();
});
@ -398,8 +414,9 @@ void QtGrpcClientServerStreamTest::Deadline()
SimpleStringMessage result;
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&result, stream] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
});
if (timeout.count() < MessageLatency * ExpectedMessageCount) {
@ -455,8 +472,9 @@ void QtGrpcClientServerStreamTest::Interceptor()
QSignalSpy streamFinishedSpy(stream.get(), &QGrpcServerStream::finished);
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&result, stream] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(streamFinishedSpy.count(), 1,
@ -490,8 +508,8 @@ void QtGrpcClientServerStreamTest::CancelledInterceptor()
QVERIFY(streamErrorSpy.isValid());
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(streamErrorSpy.count(), 1, MessageLatencyWithThreshold);
@ -511,9 +529,9 @@ void QtGrpcClientServerStreamTest::InterceptResponse()
QLatin1StringView) {
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this,
[&serverResponse, stream] {
SimpleStringMessage mess = stream->read<SimpleStringMessage>();
const auto mess = stream->read<SimpleStringMessage>();
serverResponse.setTestFieldString(serverResponse.testFieldString()
+ mess.testFieldString());
+ mess->testFieldString());
});
continuation(std::move(stream), operation);
};
@ -539,8 +557,9 @@ void QtGrpcClientServerStreamTest::InterceptResponse()
SimpleStringMessage result;
QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, this, [&] {
SimpleStringMessage ret = stream->read<SimpleStringMessage>();
result.setTestFieldString(result.testFieldString() + ret.testFieldString());
const auto ret = stream->read<SimpleStringMessage>();
QVERIFY(ret.has_value());
result.setTestFieldString(result.testFieldString() + ret->testFieldString());
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(streamFinishedSpy.count(), 1,

View File

@ -60,8 +60,8 @@ private slots:
void QtGrpcClientUnaryCallTest::AsyncWithSubscribe()
{
SimpleStringMessage request;
SimpleStringMessage result;
request.setTestFieldString("Hello Qt!");
std::optional<SimpleStringMessage> result;
bool waitForReply = false;
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
@ -71,12 +71,13 @@ void QtGrpcClientUnaryCallTest::AsyncWithSubscribe()
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(waitForReply, true, MessageLatency);
QCOMPARE_EQ(result.testFieldString(), "Hello Qt!");
QVERIFY(result.has_value());
QCOMPARE_EQ(result->testFieldString(), "Hello Qt!");
}
void QtGrpcClientUnaryCallTest::AsyncWithLambda()
{
SimpleStringMessage result;
std::optional<SimpleStringMessage> result = SimpleStringMessage();
SimpleStringMessage request;
request.setTestFieldString("Hello Qt!");
bool waitForReply = false;
@ -87,18 +88,19 @@ void QtGrpcClientUnaryCallTest::AsyncWithLambda()
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(waitForReply, true, MessageLatency);
QCOMPARE_EQ(result.testFieldString(), "Hello Qt!");
QVERIFY(result.has_value());
QCOMPARE_EQ(result->testFieldString(), "Hello Qt!");
}
void QtGrpcClientUnaryCallTest::ImmediateCancel()
{
SimpleStringMessage result;
SimpleStringMessage request;
request.setTestFieldString("sleep");
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
result.setTestFieldString("Result not changed by echo");
std::optional<SimpleStringMessage> result = SimpleStringMessage();
result->setTestFieldString("Result not changed by echo");
QObject::connect(reply.get(), &QGrpcCallReply::finished, this,
[&result, reply] { result = reply->read<SimpleStringMessage>(); });
@ -116,7 +118,7 @@ void QtGrpcClientUnaryCallTest::ImmediateCancel()
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyFinishedSpy.count(), 0, FailTimeout);
QCOMPARE_EQ(result.testFieldString(), "Result not changed by echo");
QCOMPARE_EQ(result->testFieldString(), "Result not changed by echo");
QCOMPARE_EQ(qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first()).code(),
QGrpcStatus::Cancelled);
}
@ -126,8 +128,8 @@ void QtGrpcClientUnaryCallTest::DeferredCancel()
SimpleStringMessage request;
request.setTestFieldString("sleep");
SimpleStringMessage result;
result.setTestFieldString("Result not changed by echo");
std::optional<SimpleStringMessage> result = SimpleStringMessage();
result->setTestFieldString("Result not changed by echo");
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
QObject::connect(reply.get(), &QGrpcCallReply::finished, this, [reply, &result] {
@ -141,7 +143,7 @@ void QtGrpcClientUnaryCallTest::DeferredCancel()
QTimer::singleShot(MessageLatencyThreshold, reply.get(), &QGrpcCallReply::cancel);
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyErrorSpy.count(), 1, FailTimeout);
QCOMPARE_EQ(result.testFieldString(), "Result not changed by echo");
QCOMPARE_EQ(result->testFieldString(), "Result not changed by echo");
}
void QtGrpcClientUnaryCallTest::AsyncClientStatusMessage()
@ -200,12 +202,12 @@ void QtGrpcClientUnaryCallTest::InThread()
void QtGrpcClientUnaryCallTest::AsyncInThread()
{
SimpleStringMessage request;
SimpleStringMessage result;
request.setTestFieldString("Hello Qt from thread!");
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
QVERIFY(clientErrorSpy.isValid());
std::optional<SimpleStringMessage> result = SimpleStringMessage();
const std::unique_ptr<QThread> thread(QThread::create([&] {
QEventLoop waiter;
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
@ -219,7 +221,7 @@ void QtGrpcClientUnaryCallTest::AsyncInThread()
thread->start();
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
QTRY_VERIFY(result.testFieldString().isEmpty());
QTRY_VERIFY(result.has_value());
QTRY_VERIFY(
qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first())
.message()
@ -293,7 +295,7 @@ void QtGrpcClientUnaryCallTest::Deadline()
|| code == QGrpcStatus::StatusCode::Unavailable);
} else if (timeout.count() >= MessageLatencyWithThreshold) {
QTRY_COMPARE_EQ_WITH_TIMEOUT(callFinishedSpy.count(), 1, MessageLatencyWithThreshold);
QCOMPARE(reply->read<SimpleStringMessage>().testFieldString(), request.testFieldString());
QCOMPARE(reply->read<SimpleStringMessage>()->testFieldString(), request.testFieldString());
} else {
// Because we're can't be sure about the result,
// cancel the call, that might affect other tests.
@ -348,13 +350,13 @@ void QtGrpcClientUnaryCallTest::CancelledInterceptor()
channel->addInterceptorManager(manager);
client()->attachChannel(channel);
SimpleStringMessage result;
SimpleStringMessage request;
request.setTestFieldString("sleep");
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
result.setTestFieldString("Result not changed by echo");
std::optional<SimpleStringMessage> result = SimpleStringMessage();
result->setTestFieldString("Result not changed by echo");
QObject::connect(reply.get(), &QGrpcCallReply::finished, this,
[&result, reply] { result = reply->read<SimpleStringMessage>(); });
@ -370,12 +372,12 @@ void QtGrpcClientUnaryCallTest::CancelledInterceptor()
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyFinishedSpy.count(), 0, FailTimeout);
QCOMPARE_EQ(result.testFieldString(), "Result not changed by echo");
QCOMPARE_EQ(result->testFieldString(), "Result not changed by echo");
}
void QtGrpcClientUnaryCallTest::InterceptResponse()
{
SimpleStringMessage serverResponse;
std::optional<SimpleStringMessage> serverResponse = SimpleStringMessage();
auto interceptFunc =
[this, &serverResponse](std::shared_ptr<QGrpcChannelOperation> operation,
std::shared_ptr<QGrpcCallReply> response,
@ -395,15 +397,15 @@ void QtGrpcClientUnaryCallTest::InterceptResponse()
client()->attachChannel(channel);
SimpleStringMessage request;
SimpleStringMessage result;
request.setTestFieldString("Hello Qt!");
std::optional<SimpleStringMessage> result;
client()->testMethod(request, client().get(), [&result](std::shared_ptr<QGrpcCallReply> reply) {
result = reply->read<SimpleStringMessage>();
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(serverResponse.testFieldString(),
QTRY_COMPARE_EQ_WITH_TIMEOUT(serverResponse->testFieldString(),
"Hello Qt!", MessageLatencyWithThreshold);
QCOMPARE_EQ(result.testFieldString(), "Hello Qt!");
QCOMPARE_EQ(result->testFieldString(), "Hello Qt!");
}
void QtGrpcClientUnaryCallTest::CacheIntercept()
@ -431,13 +433,13 @@ void QtGrpcClientUnaryCallTest::CacheIntercept()
client()->attachChannel(channel);
SimpleStringMessage request;
SimpleStringMessage result;
std::optional<SimpleStringMessage> result = SimpleStringMessage();
request.setTestFieldString("Hello Qt!");
client()->testMethod(request, client().get(), [&result](std::shared_ptr<QGrpcCallReply> reply) {
result = reply->read<SimpleStringMessage>();
});
QTRY_COMPARE_EQ_WITH_TIMEOUT(result.testFieldString(),
QTRY_COMPARE_EQ_WITH_TIMEOUT(result->testFieldString(),
"inter1", MessageLatencyWithThreshold);
}

View File

@ -27,8 +27,14 @@ void QmlClient::testMethod(const qtgrpc::tests::SimpleStringMessage &arg, const
std::shared_ptr<QGrpcCallReply> reply = call("testMethod"_L1, arg, options);
reply->subscribe(jsEngine, [reply, callback, jsEngine]() {
auto result = reply->read<qtgrpc::tests::SimpleStringMessage>();
callback.call(QJSValueList{jsEngine->toScriptValue(result)});
if (const auto result = reply->read<qtgrpc::tests::SimpleStringMessage>()) {
callback.call(QJSValueList{jsEngine->toScriptValue(*result)});
return;
}
QGrpcStatus::StatusCode code = QGrpcStatus::StatusCode::InvalidArgument;
if (reply->deserializationError() == QAbstractProtobufSerializer::UnexpectedEndOfStreamError)
code = QGrpcStatus::StatusCode::OutOfRange;
emit reply->errorOccurred(QGrpcStatus{ code, reply->deserializationErrorString() });
}, [errorCallback, jsEngine](const QGrpcStatus &status) {
errorCallback.call(QJSValueList{jsEngine->toScriptValue(status)});
});