/* * MIT License * * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com> * * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and * to permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once //QAbstractGrpcClient #include <memory> #include <functional> #include <type_traits> #include <QObject> #include <QPointer> #include <QByteArray> #include <qtprotobuflogging.h> #include <qabstractprotobufserializer.h> #include "qabstractgrpcchannel.h" #include "qtgrpcglobal.h" /*! * \defgroup QtGrpc * \brief Qt framework based gRPC clients and services */ namespace QtProtobuf { class QGrpcAsyncReply; class QGrpcSubscription; class QGrpcAsyncOperationBase; class QAbstractGrpcChannel; class QAbstractGrpcClientPrivate; /*! * \private */ using SubscriptionHandler = std::function<void(const QByteArray&)>; /*! * \ingroup QtGrpc * \brief The QAbstractGrpcClient class is bridge between gRPC clients and channels. QAbstractGrpcClient provides set of * bridge functions for client classes generated out of protobuf services. */ class Q_GRPC_EXPORT QAbstractGrpcClient : public QObject { Q_OBJECT public: /*! * \brief Attaches \a channel to client as transport layer for gRPC. Parameters and return values will be serialized * to supported by channel format. * \see QAbstractGrcpChannel * \param channel Shared pointer to channel will be used as transport layer for gRPC */ void attachChannel(const std::shared_ptr<QAbstractGrpcChannel> &channel); signals: /*! * \brief error signal is emited by client when error occured in channel or while serialization/deserialization * \param[out] code gRPC channel StatusCode * \param[out] errorText Error description from channel or from QGrpc */ void error(const QGrpcStatus &status); protected: QAbstractGrpcClient(const QString &service, QObject *parent = nullptr); virtual ~QAbstractGrpcClient(); /*! * \private * \brief Calls \p method of service client synchronously * \param[in] method Name of the method to be called * \param[in] arg Protobuf message argument for \p method * \param[out] ret A pointer to memory with protobuf message to write an gRPC reply to */ template<typename A, typename R> QGrpcStatus call(const QString &method, const A &arg, const QPointer<R> &ret) { QGrpcStatus status{QGrpcStatus::Ok}; if (ret.isNull()) { static const QString errorString("Unable to call method: %1. Pointer to return data is null"); status = QGrpcStatus{QGrpcStatus::InvalidArgument, errorString.arg(method)}; error(status); qProtoCritical() << errorString.arg(method); return status; } QByteArray retData; status = call(method, arg.serialize(serializer()), retData); if (status == QGrpcStatus::StatusCode::Ok) { return tryDeserialize(*ret, retData); } return status; } /*! * \private * \brief Calls \p method of service client asynchronously and returns pointer to assigned to call AsyncReply * \param[in] method Name of the method to be called * \param[in] arg Protobuf message argument for \p method */ template<typename A> QGrpcAsyncReply *call(const QString &method, const A &arg) { return call(method, arg.serialize(serializer())); } /*! * \private * \brief Subscribes to message notifications from server-stream with given message argument \a arg * \param[in] method Name of the method to be called * \param[in] arg Protobuf message argument for \p method * \param[out] signal Callback with return-message as input parameter that will be called each time message * update recevied from server-stream */ template<typename A> QGrpcSubscription *subscribe(const QString &method, const A &arg) { return subscribe(method, arg.serialize(serializer())); } /*! * \private * \brief Subscribes to message notifications from server-stream with given message argument \a arg * \param[in] method Name of the method to be called * \param[in] arg Protobuf message argument for \p method * \param[out] ret Pointer to preallocated return-message structure. \p ret Structure fields will be update each * time message update recevied from server-stream. * \note If \p ret is used as property-fields in other object, property NOTIFY signal won't be called in case of * updated message recevied from server-stream */ template<typename A, typename R> QGrpcSubscription *subscribe(const QString &method, const A &arg, const QPointer<R> &ret) { if (ret.isNull()) { static const QString nullPointerError("Unable to subscribe method: %1. Pointer to return data is null"); error({QGrpcStatus::InvalidArgument, nullPointerError.arg(method)}); qProtoCritical() << nullPointerError.arg(method); return nullptr; } return subscribe(method, arg.serialize(serializer()), [ret, this](const QByteArray &data) { if (!ret.isNull()) { tryDeserialize(*ret, data); } else { static const QLatin1String nullPointerError("Pointer to return data is null while subscription update received"); error({QGrpcStatus::InvalidArgument, nullPointerError}); qProtoCritical() << nullPointerError; } }); } /*! * \brief Canceles all subscriptions for specified \p method * \param[in] method Name of method subscription for to be canceled */ void cancel(const QString &method); /*! * \brief serializer provides assigned to client serializer * \return pointer to serializer. Serializer is owned by QtProtobuf::QProtobufSerializerRegistry. */ QAbstractProtobufSerializer *serializer() const; friend class QGrpcAsyncOperationBase; private: //!\private QGrpcStatus call(const QString &method, const QByteArray &arg, QByteArray &ret); //!\private QGrpcAsyncReply *call(const QString &method, const QByteArray &arg); //!\private QGrpcSubscription *subscribe(const QString &method, const QByteArray &arg, const QtProtobuf::SubscriptionHandler &handler = {}); /*! * \private * \brief Deserialization helper */ template<typename R> QGrpcStatus tryDeserialize(R &ret, const QByteArray &retData) { QGrpcStatus status{QGrpcStatus::Ok}; try { ret.deserialize(serializer(), retData); } catch (std::invalid_argument &) { static const QLatin1String invalidArgumentErrorMessage("Response deserialization failed invalid field found"); status = {QGrpcStatus::InvalidArgument, invalidArgumentErrorMessage}; error(status); qProtoCritical() << invalidArgumentErrorMessage; } catch (std::out_of_range &) { static const QLatin1String outOfRangeErrorMessage("Invalid size of received buffer"); status = {QGrpcStatus::OutOfRange, outOfRangeErrorMessage}; error(status); qProtoCritical() << outOfRangeErrorMessage; } catch (...) { status = {QGrpcStatus::Internal, QLatin1String("Unknown exception caught during deserialization")}; error(status); } return status; } Q_DISABLE_COPY_MOVE(QAbstractGrpcClient) std::unique_ptr<QAbstractGrpcClientPrivate> dPtr; }; }