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

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