1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include <QtGrpc/private/qabstractgrpcchannel_p.h>
6#include <QtGrpc/private/qgrpcoperation_p.h>
7#include <QtGrpc/private/qtgrpclogging_p.h>
8#include <QtGrpc/qgrpcclientbase.h>
9#include <QtGrpc/qgrpcoperation.h>
10
11#include <QtProtobuf/qprotobufmessage.h>
12#include <QtProtobuf/qprotobufserializer.h>
13
14#include <QtCore/private/qminimalflatset_p.h>
15#include <QtCore/private/qobject_p.h>
16#include <QtCore/qbytearray.h>
17#include <QtCore/qlatin1stringview.h>
18
19#include <optional>
20#include <type_traits>
21
22QT_BEGIN_NAMESPACE
23
24/*!
25 \class QGrpcClientBase
26 \inmodule QtGrpc
27 \brief The QGrpcClientBase class serves as base for generated client
28 interfaces.
29
30 The QGrpcClientBase class provides a common set of functionalities for the
31 generated client interface of the \gRPC service definition.
32
33 The RPC methods of this class should not be called directly.
34
35 \note Thread safety is enforced for the non-const member functions. These
36 functions must be called from the same \l{QObject::} {thread} in which the
37 object was created.
38*/
39
40/*!
41 \fn void QGrpcClientBase::channelChanged()
42 \since 6.7
43
44 Indicates that a new channel got attached to the client.
45*/
46
47namespace {
48template <typename Operation>
49inline constexpr bool IsStream = false;
50template <>
51inline constexpr bool IsStream<QGrpcServerStream> = true;
52template <>
53inline constexpr bool IsStream<QGrpcClientStream> = true;
54template <>
55inline constexpr bool IsStream<QGrpcBidiStream> = true;
56}
57
58class QGrpcClientBasePrivate : public QObjectPrivate
59{
60 Q_DECLARE_PUBLIC(QGrpcClientBase)
61public:
62 explicit QGrpcClientBasePrivate(QLatin1StringView service) : service(service) { }
63
64 void addStream(QGrpcOperation *stream);
65 std::optional<QByteArray> trySerialize(const QProtobufMessage &arg) const;
66 bool isReady() const;
67
68 std::shared_ptr<QAbstractProtobufSerializer> serializer() const
69 {
70 return channel ? channel->serializer() : nullptr;
71 }
72
73 template <typename T>
74 [[nodiscard]] std::unique_ptr<T> initOperation(QLatin1StringView method,
75 const QProtobufMessage &arg,
76 const QGrpcCallOptions &options)
77 {
78 if (!isReady())
79 return {};
80
81 const auto argData = trySerialize(arg);
82 if (!argData)
83 return {};
84
85 using ChannelFn = std::unique_ptr<T> (QAbstractGrpcChannel::*)(QLatin1StringView,
86 QLatin1StringView,
87 QByteArrayView,
88 const QGrpcCallOptions &);
89 constexpr ChannelFn initializer = [&]() -> ChannelFn {
90 if constexpr (std::is_same_v<T, QGrpcServerStream>) {
91 return &QAbstractGrpcChannel::serverStream;
92 } else if constexpr (std::is_same_v<T, QGrpcClientStream>) {
93 return &QAbstractGrpcChannel::clientStream;
94 } else if constexpr (std::is_same_v<T, QGrpcBidiStream>) {
95 return &QAbstractGrpcChannel::bidiStream;
96 } else if constexpr (std::is_same_v<T, QGrpcCallReply>) {
97 return &QAbstractGrpcChannel::call;
98 } else {
99 Q_UNREACHABLE_RETURN(nullptr);
100 }
101 }();
102
103 std::unique_ptr<T> operation = ((*channel).*(initializer))(method, service, *argData,
104 options);
105
106 if constexpr (IsStream<T>)
107 addStream(stream: operation.get());
108 return operation;
109 }
110
111 std::shared_ptr<QAbstractGrpcChannel> channel;
112 const QLatin1StringView service;
113 QMinimalFlatSet<QGrpcOperation *> activeStreams;
114};
115
116void QGrpcClientBasePrivate::addStream(QGrpcOperation *grpcStream)
117{
118 Q_ASSERT(grpcStream);
119
120 Q_Q(QGrpcClientBase);
121 // Remove the operation pointer upon QObject destruction if it hasn't
122 // already been gracefully removed by receiving finished()
123 QObject::connect(sender: grpcStream, signal: &QObject::destroyed, context: q, slot: [this, grpcStream](QObject *obj) {
124 Q_ASSERT(obj == grpcStream);
125 activeStreams.remove(v: grpcStream);
126 });
127
128 QObject::connect(
129 sender: grpcStream, signal: &QGrpcOperation::finished, context: q,
130 slot: [this, grpcStream] {
131 Q_ASSERT(activeStreams.contains(grpcStream));
132 activeStreams.remove(v: grpcStream);
133 },
134 type: Qt::SingleShotConnection);
135 const auto it = activeStreams.insert(v: grpcStream);
136 Q_ASSERT(it.second);
137}
138
139std::optional<QByteArray> QGrpcClientBasePrivate::trySerialize(const QProtobufMessage &arg) const
140{
141 if (auto s = serializer())
142 return s->serialize(message: &arg);
143
144 qGrpcWarning("Serializing failed. Serializer is not ready");
145 return std::nullopt;
146}
147
148bool QGrpcClientBasePrivate::isReady() const
149{
150 Q_Q(const QGrpcClientBase);
151 if (q->thread() != QThread::currentThread()) {
152 qGrpcWarning("QtGrpc doesn't support invocation from a different thread");
153 return false;
154 }
155
156 if (!channel) {
157 qGrpcWarning("No channel(s) attached");
158 return false;
159 }
160 return true;
161}
162
163/*!
164 \internal
165 Constructs a QGrpcClientBase using \a service name from the protobuf schema
166 and sets \a parent as the owner.
167*/
168QGrpcClientBase::QGrpcClientBase(QLatin1StringView service, QObject *parent)
169 : QObject(*new QGrpcClientBasePrivate(service), parent)
170{
171}
172
173/*!
174 Destroys the QGrpcClientBase.
175*/
176QGrpcClientBase::~QGrpcClientBase() = default;
177
178/*!
179 Attaches \a channel to the client as transport layer for \gRPC operations.
180 Returns \c true if the channel successfully attached; otherwise, returns \c
181 false.
182
183 Request and response messages will be serialized in a format that the
184 channel supports.
185
186 \note \b Warning: Qt GRPC doesn't guarantee thread safety on the channel level.
187 You have to invoke the channel-related functions on the same thread as
188 QGrpcClientBase.
189*/
190bool QGrpcClientBase::attachChannel(std::shared_ptr<QAbstractGrpcChannel> channel)
191{
192 Q_D(QGrpcClientBase);
193 // channel is not a QObject so we compare against the threadId set on construction.
194 if (channel->d_func()->threadId != QThread::currentThreadId()) {
195 qGrpcWarning("QtGrpc doesn't allow attaching the channel from a different thread");
196 return false;
197 }
198
199 for (const auto &stream : d->activeStreams) {
200 assert(stream != nullptr);
201 stream->cancel();
202 }
203
204 d->channel = std::move(channel);
205 emit channelChanged();
206 return true;
207}
208
209/*!
210 \since 6.7
211 Returns the channel attached to this client.
212*/
213std::shared_ptr<QAbstractGrpcChannel> QGrpcClientBase::channel() const
214{
215 Q_D(const QGrpcClientBase);
216 return d->channel;
217}
218
219/*!
220 \internal
221//! [rpc-init-desc]
222 Initializes the RPC with \a method name and initial argument \a arg by
223 calling the corresponding QAbstractGrpcChannel method. The RPC is
224 customized through the provided \a options.
225//! [rpc-init-desc]
226*/
227std::unique_ptr<QGrpcCallReply> QGrpcClientBase::call(QLatin1StringView method,
228 const QProtobufMessage &arg,
229 const QGrpcCallOptions &options)
230{
231 Q_D(QGrpcClientBase);
232 return d->initOperation<QGrpcCallReply>(method, arg, options);
233}
234
235/*!
236 \internal
237 \include qgrpcclientbase.cpp rpc-init-desc
238*/
239std::unique_ptr<QGrpcServerStream> QGrpcClientBase::serverStream(QLatin1StringView method,
240 const QProtobufMessage &arg,
241 const QGrpcCallOptions &options)
242{
243 Q_D(QGrpcClientBase);
244 return d->initOperation<QGrpcServerStream>(method, arg, options);
245}
246
247/*!
248 \internal
249 \include qgrpcclientbase.cpp rpc-init-desc
250*/
251std::unique_ptr<QGrpcClientStream> QGrpcClientBase::clientStream(QLatin1StringView method,
252 const QProtobufMessage &arg,
253 const QGrpcCallOptions &options)
254{
255 Q_D(QGrpcClientBase);
256 return d->initOperation<QGrpcClientStream>(method, arg, options);
257}
258
259/*!
260 \internal
261 \include qgrpcclientbase.cpp rpc-init-desc
262*/
263std::unique_ptr<QGrpcBidiStream> QGrpcClientBase::bidiStream(QLatin1StringView method,
264 const QProtobufMessage &arg,
265 const QGrpcCallOptions &options)
266{
267 Q_D(QGrpcClientBase);
268 return d->initOperation<QGrpcBidiStream>(method, arg, options);
269}
270
271void QGrpcClientBase::setOperationResponseMetaType(QGrpcOperation *operation,
272 QMetaType responseMetaType)
273{
274 Q_ASSERT(operation);
275 QGrpcOperationPrivate::get(op: operation)->operationContext->setResponseMetaType(responseMetaType);
276}
277
278bool QGrpcClientBase::event(QEvent *event)
279{
280 return QObject::event(event);
281}
282
283QT_END_NAMESPACE
284
285#include "moc_qgrpcclientbase.cpp"
286

source code of qtgrpc/src/grpc/qgrpcclientbase.cpp