Browse Source

Add QGrpcStatus

- Add QGrpcStatus as replacement for StatusCode
- QGrpcStatus combine status code and message
- Replace deprecated api that used StatusCode
- Update tests
TODO: Update doxy
      Need to update sync "bool QAbstractGrpcClient::call" method,
      it should return QGrpcStatus instead of bool
Alexey Edelev 5 years ago
parent
commit
b2ea1c75c6

+ 2 - 0
src/grpc/CMakeLists.txt

@@ -13,6 +13,7 @@ set(CMAKE_AUTORCC ON)
 include(${CMAKE_SOURCE_DIR}/cmake/Coverage.cmake)
 
 file(GLOB SOURCES qgrpcasyncreply.cpp
+    qgrpcstatus.cpp
     qabstractgrpcchannel.cpp
     qgrpchttp2channel.cpp
     qabstractgrpcclient.cpp
@@ -21,6 +22,7 @@ file(GLOB SOURCES qgrpcasyncreply.cpp
     insecurecredentials.cpp)
 
 file(GLOB HEADERS qgrpcasyncreply.h
+    qgrpcstatus.h
     qabstractgrpcchannel.h
     qgrpchttp2channel.h
     qabstractgrpcclient.h

+ 2 - 27
src/grpc/qabstractgrpcchannel.h

@@ -29,6 +29,7 @@
 #include <QByteArray>
 #include <functional>
 
+#include "qgrpcstatus.h"
 #include "qtgrpcglobal.h"
 
 namespace QtProtobuf {
@@ -43,32 +44,6 @@ class QAbstractGrpcClient;
 class Q_GRPC_EXPORT QAbstractGrpcChannel
 {
 public:
-    /*!
-     * \enum StatusCode
-     * \brief Channel's status codes
-     *
-     * \see <a href="https://github.com/grpc/grpc/blob/master/doc/statuscodes.md">gRPC status codes</a>
-     */
-    enum StatusCode {
-        Ok = 0,                 //!< No error
-        Cancelled = 1,          //!< The operation was cancelled, typically by the caller
-        Unknown = 2,            //!< Unknown error
-        InvalidArgument = 3,    //!< The client specified an invalid argument
-        DeadlineExceeded = 4,   //!< The deadline expired before the operation could complete
-        NotFound = 5,           //!< Some requested entity (e.g., file or directory) was not found
-        AlreadyExists = 6,      //!< The entity that a client attempted to create (e.g., file or directory) already exists. 	409 Conflict
-        PermissionDenied = 7,   //!< The caller does not have permission to execute the specified operation. PERMISSION_DENIED must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED instead for those errors). PERMISSION_DENIED must not be used if the caller can not be identified (use UNAUTHENTICATED instead for those errors). This error code does not imply the request is valid or the requested entity exists or satisfies other pre-conditions. 	403 Forbidden
-        Unauthenticated = 16,   //!< The request does not have valid authentication credentials for the operation
-        ResourceExhausted = 8,  //!< Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space
-        FailedPrecondition = 9, //!< The operation was rejected because the system is not in a state required for the operation's execution
-        Aborted = 10,           //!< The operation was aborted, typically due to a concurrency issue such as a sequencer check failure or transaction abort
-        OutOfRange = 11,        //!< The operation was attempted past the valid range
-        Unimplemented = 12,     //!< The operation is not implemented or is not supported/enabled in this service
-        Internal = 13,          //!< Internal errors. This means that some invariants expected by the underlying system have been broken.
-        Unavailable = 14,       //!< The service is currently unavailable
-        DataLoss = 15,          //!< Unrecoverable data loss or corruption
-    };
-
     /*!
      * \brief Calls \p method synchronously with given serialized messge \p args and write result of call to \p ret.
      *        \note This method is synchronous, that means it doesn't returns until call complete or aborted by timeout if it's
@@ -79,7 +54,7 @@ public:
      * \param[out] ret output bytearray to collect returned message
      * \return returns gRPC QAbstractGrpcChannel::Status code for operation
      */
-    virtual StatusCode call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret) = 0;
+    virtual QGrpcStatus call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret) = 0;
 
     /*!
      * \brief Calls \p method asynchronously with given serialized messge \p args. Result of method call is written to QGrpcAsyncReply.

+ 7 - 7
src/grpc/qabstractgrpcclient.cpp

@@ -54,13 +54,13 @@ void QAbstractGrpcClient::attachChannel(const std::shared_ptr<QAbstractGrpcChann
     d->channel = channel;
 }
 
-QAbstractGrpcChannel::StatusCode QAbstractGrpcClient::call(const QString &method, const QByteArray &arg, QByteArray &ret)
+QGrpcStatus QAbstractGrpcClient::call(const QString &method, const QByteArray &arg, QByteArray &ret)
 {
-    QAbstractGrpcChannel::StatusCode callStatus = QAbstractGrpcChannel::Unknown;
+    QGrpcStatus callStatus{QGrpcStatus::Unknown};
     if (d->channel) {
         callStatus = d->channel->call(method, d->service, arg, ret);
     } else {
-        error(callStatus, QLatin1String("No channel(s) attached."));
+        error({QGrpcStatus::Unknown, QLatin1String("No channel(s) attached.")});
     }
 
     return callStatus;
@@ -72,8 +72,8 @@ QGrpcAsyncReply *QAbstractGrpcClient::call(const QString &method, const QByteArr
     if (d->channel) {
         reply = new QGrpcAsyncReply(d->channel, this);
 
-        connect(reply, &QGrpcAsyncReply::error, this, [this, reply](QAbstractGrpcChannel::StatusCode statusCode, const QString &errorMessage) {
-            error(statusCode, errorMessage);
+        connect(reply, &QGrpcAsyncReply::error, this, [this, reply](const QGrpcStatus &status) {
+            error(status);
             reply->deleteLater();
         });
 
@@ -83,7 +83,7 @@ QGrpcAsyncReply *QAbstractGrpcClient::call(const QString &method, const QByteArr
 
         d->channel->call(method, d->service, arg, reply);
     } else {
-        error(QAbstractGrpcChannel::Unknown, QLatin1String("No channel(s) attached."));
+        error({QGrpcStatus::Unknown, QLatin1String("No channel(s) attached.")});
     }
 
     return reply;
@@ -94,6 +94,6 @@ void QAbstractGrpcClient::subscribe(const QString &method, const QByteArray &arg
     if (d->channel) {
         d->channel->subscribe(method, d->service, arg, this, handler);
     } else {
-        error(QAbstractGrpcChannel::Unknown, QLatin1String("No channel(s) attached."));
+        error({QGrpcStatus::Unknown, QLatin1String("No channel(s) attached.")});
     }
 }

+ 9 - 9
src/grpc/qabstractgrpcclient.h

@@ -65,7 +65,7 @@ signals:
      * \param[out] code gRPC channel StatusCode
      * \param[out] errorText Error description from channel or from QGrpc
      */
-    void error(QAbstractGrpcChannel::StatusCode code, const QString &errorText);
+    void error(const QGrpcStatus &status);
 
 protected:
     QAbstractGrpcClient(const QString &service, QObject *parent = nullptr);
@@ -82,13 +82,13 @@ protected:
     bool call(const QString &method, const A &arg, const QPointer<R> &ret) {
         if (ret.isNull()) {
             static const QString errorString("Unable to call method: %1. Pointer to return data is null");
-            error(QAbstractGrpcChannel::InvalidArgument, errorString.arg(method));
+            error({QGrpcStatus::InvalidArgument, errorString.arg(method)});
             qProtoCritical() << errorString.arg(method);
             return false;
         }
 
         QByteArray retData;
-        if (QAbstractGrpcChannel::StatusCode::Ok == call(method, arg.serialize(), retData)) {
+        if (call(method, arg.serialize(), retData) == QGrpcStatus::StatusCode::Ok) {
             return tryDeserialize(*ret, retData);
         }
         return false;
@@ -138,7 +138,7 @@ protected:
     void 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(QAbstractGrpcChannel::InvalidArgument, nullPointerError.arg(method));
+            error({QGrpcStatus::InvalidArgument, nullPointerError.arg(method)});
             qProtoCritical() << nullPointerError.arg(method);
             return;
         }
@@ -148,7 +148,7 @@ protected:
                 tryDeserialize(*ret, data);
             } else {
                 static const QLatin1String nullPointerError("Pointer to return data is null while subscription update received");
-                error(QAbstractGrpcChannel::InvalidArgument, nullPointerError);
+                error({QGrpcStatus::InvalidArgument, nullPointerError});
                 qProtoCritical() << nullPointerError;
             }
         });
@@ -158,7 +158,7 @@ private:
     /*!
      * \private
      */
-    QAbstractGrpcChannel::StatusCode call(const QString &method, const QByteArray &arg, QByteArray &ret);
+    QGrpcStatus call(const QString &method, const QByteArray &arg, QByteArray &ret);
 
     /*!
      * \private
@@ -180,16 +180,16 @@ private:
             ret.deserialize(retData);
         } catch (std::invalid_argument &) {
             static const QLatin1String invalidArgumentErrorMessage("Response deserialization failed invalid field found");
-            error(QAbstractGrpcChannel::InvalidArgument, invalidArgumentErrorMessage);
+            error({QGrpcStatus::InvalidArgument, invalidArgumentErrorMessage});
             qProtoCritical() << invalidArgumentErrorMessage;
             return false;
         } catch (std::out_of_range &) {
             static const QLatin1String outOfRangeErrorMessage("Invalid size of received buffer");
-            error(QAbstractGrpcChannel::OutOfRange, outOfRangeErrorMessage);
+            error({QGrpcStatus::OutOfRange, outOfRangeErrorMessage});
             qProtoCritical() << outOfRangeErrorMessage;
             return false;
         } catch (...) {
-            error(QAbstractGrpcChannel::Internal, QLatin1String("Unknown exception caught during deserialization"));
+            error({QGrpcStatus::Internal, QLatin1String("Unknown exception caught during deserialization")});
             return false;
         }
         return true;

+ 5 - 5
src/grpc/qgrpcasyncreply.h

@@ -58,12 +58,12 @@ public:
             value.deserialize(m_data);
         } catch (std::invalid_argument &) {
             static const QLatin1String invalidArgumentErrorMessage("Response deserialization failed invalid field found");
-            error(QAbstractGrpcChannel::InvalidArgument, invalidArgumentErrorMessage);
+            error({QGrpcStatus::InvalidArgument, invalidArgumentErrorMessage});
         } catch (std::out_of_range &) {
             static const QLatin1String outOfRangeErrorMessage("Invalid size of received buffer");
-            error(QAbstractGrpcChannel::OutOfRange, outOfRangeErrorMessage);
+            error({QGrpcStatus::OutOfRange, outOfRangeErrorMessage});
         } catch (...) {
-            error(QAbstractGrpcChannel::Internal, QLatin1String("Unknown exception caught during deserialization"));
+            error({QGrpcStatus::Internal, QLatin1String("Unknown exception caught during deserialization")});
         }
         return value;
     }
@@ -95,10 +95,10 @@ signals:
 
     /*!
      * \brief The signal is emitted when error happend in channel or during serialization
-     * \param code gRPC channel QAbstractGrpcChannel::StatusCode
+     * \param code gRPC channel QGrpcStatus::StatusCode
      * \param errorMessage Description of error occured
      */
-    void error(QAbstractGrpcChannel::StatusCode code, const QString &errorMessage);
+    void error(const QGrpcStatus &status);
 
 protected:
     //! \private

+ 45 - 45
src/grpc/qgrpchttp2channel.cpp

@@ -46,43 +46,43 @@ using namespace QtProtobuf;
 namespace  {
 
 /*!
- * This QNetworkReply::NetworkError -> AbstractChannel::StatusCode mapping should be kept in sync with original
+ * This QNetworkReply::NetworkError -> QGrpcStatus::StatusCode mapping should be kept in sync with original
  * <a href="https://github.com/grpc/grpc/blob/master/doc/statuscodes.md">gRPC status codes</a>
  */
-const static std::unordered_map<QNetworkReply::NetworkError, QAbstractGrpcChannel::StatusCode> StatusCodeMap = {
-                                                                { QNetworkReply::ConnectionRefusedError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::RemoteHostClosedError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::HostNotFoundError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::TimeoutError, QAbstractGrpcChannel::DeadlineExceeded },
-                                                                { QNetworkReply::OperationCanceledError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::SslHandshakeFailedError, QAbstractGrpcChannel::PermissionDenied },
-                                                                { QNetworkReply::TemporaryNetworkFailureError, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::NetworkSessionFailedError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::BackgroundRequestNotAllowedError, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::TooManyRedirectsError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::InsecureRedirectError, QAbstractGrpcChannel::PermissionDenied },
-                                                                { QNetworkReply::UnknownNetworkError, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::ProxyConnectionRefusedError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::ProxyConnectionClosedError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::ProxyNotFoundError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::ProxyTimeoutError, QAbstractGrpcChannel::DeadlineExceeded },
-                                                                { QNetworkReply::ProxyAuthenticationRequiredError, QAbstractGrpcChannel::Unauthenticated },
-                                                                { QNetworkReply::UnknownProxyError, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::ContentAccessDenied, QAbstractGrpcChannel::PermissionDenied },
-                                                                { QNetworkReply::ContentOperationNotPermittedError, QAbstractGrpcChannel::PermissionDenied },
-                                                                { QNetworkReply::ContentNotFoundError, QAbstractGrpcChannel::NotFound },
-                                                                { QNetworkReply::AuthenticationRequiredError, QAbstractGrpcChannel::PermissionDenied },
-                                                                { QNetworkReply::ContentReSendError, QAbstractGrpcChannel::DataLoss },
-                                                                { QNetworkReply::ContentConflictError, QAbstractGrpcChannel::InvalidArgument },
-                                                                { QNetworkReply::ContentGoneError, QAbstractGrpcChannel::DataLoss },
-                                                                { QNetworkReply::UnknownContentError, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::ProtocolUnknownError, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::ProtocolInvalidOperationError, QAbstractGrpcChannel::Unimplemented },
-                                                                { QNetworkReply::ProtocolFailure, QAbstractGrpcChannel::Unknown },
-                                                                { QNetworkReply::InternalServerError, QAbstractGrpcChannel::Internal },
-                                                                { QNetworkReply::OperationNotImplementedError, QAbstractGrpcChannel::Unimplemented },
-                                                                { QNetworkReply::ServiceUnavailableError, QAbstractGrpcChannel::Unavailable },
-                                                                { QNetworkReply::UnknownServerError, QAbstractGrpcChannel::Unknown }};
+const static std::unordered_map<QNetworkReply::NetworkError, QGrpcStatus::StatusCode> StatusCodeMap = {
+                                                                { QNetworkReply::ConnectionRefusedError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::RemoteHostClosedError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::HostNotFoundError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::TimeoutError, QGrpcStatus::DeadlineExceeded },
+                                                                { QNetworkReply::OperationCanceledError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::SslHandshakeFailedError, QGrpcStatus::PermissionDenied },
+                                                                { QNetworkReply::TemporaryNetworkFailureError, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::NetworkSessionFailedError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::BackgroundRequestNotAllowedError, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::TooManyRedirectsError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::InsecureRedirectError, QGrpcStatus::PermissionDenied },
+                                                                { QNetworkReply::UnknownNetworkError, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::ProxyConnectionRefusedError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::ProxyConnectionClosedError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::ProxyNotFoundError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::ProxyTimeoutError, QGrpcStatus::DeadlineExceeded },
+                                                                { QNetworkReply::ProxyAuthenticationRequiredError, QGrpcStatus::Unauthenticated },
+                                                                { QNetworkReply::UnknownProxyError, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::ContentAccessDenied, QGrpcStatus::PermissionDenied },
+                                                                { QNetworkReply::ContentOperationNotPermittedError, QGrpcStatus::PermissionDenied },
+                                                                { QNetworkReply::ContentNotFoundError, QGrpcStatus::NotFound },
+                                                                { QNetworkReply::AuthenticationRequiredError, QGrpcStatus::PermissionDenied },
+                                                                { QNetworkReply::ContentReSendError, QGrpcStatus::DataLoss },
+                                                                { QNetworkReply::ContentConflictError, QGrpcStatus::InvalidArgument },
+                                                                { QNetworkReply::ContentGoneError, QGrpcStatus::DataLoss },
+                                                                { QNetworkReply::UnknownContentError, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::ProtocolUnknownError, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::ProtocolInvalidOperationError, QGrpcStatus::Unimplemented },
+                                                                { QNetworkReply::ProtocolFailure, QGrpcStatus::Unknown },
+                                                                { QNetworkReply::InternalServerError, QGrpcStatus::Internal },
+                                                                { QNetworkReply::OperationNotImplementedError, QGrpcStatus::Unimplemented },
+                                                                { QNetworkReply::ServiceUnavailableError, QGrpcStatus::Unavailable },
+                                                                { QNetworkReply::UnknownServerError, QGrpcStatus::Unknown }};
 
 const char *GrpcAcceptEncodingHeader = "grpc-accept-encoding";
 const char *AcceptEncodingHeader = "accept-encoding";
@@ -152,7 +152,7 @@ struct QGrpcHttp2ChannelPrivate {
         }
     }
 
-    static QByteArray processReply(QNetworkReply *networkReply, QAbstractGrpcChannel::StatusCode &statusCode) {
+    static QByteArray processReply(QNetworkReply *networkReply, QGrpcStatus::StatusCode &statusCode) {
         //Check if no network error occured
         if (networkReply->error() != QNetworkReply::NoError) {
             statusCode = StatusCodeMap.at(networkReply->error());
@@ -160,8 +160,8 @@ struct QGrpcHttp2ChannelPrivate {
         }
 
         //Check if server answer with error
-        statusCode = static_cast<QAbstractGrpcChannel::StatusCode>(networkReply->rawHeader(GrpcStatusHeader).toInt());
-        if (statusCode != QAbstractGrpcChannel::StatusCode::Ok) {
+        statusCode = static_cast<QGrpcStatus::StatusCode>(networkReply->rawHeader(GrpcStatusHeader).toInt());
+        if (statusCode != QGrpcStatus::StatusCode::Ok) {
             return {};
         }
 
@@ -196,7 +196,7 @@ QGrpcHttp2Channel::~QGrpcHttp2Channel()
     delete d;
 }
 
-QAbstractGrpcChannel::StatusCode QGrpcHttp2Channel::call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret)
+QGrpcStatus QGrpcHttp2Channel::call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret)
 {
     QEventLoop loop;
 
@@ -208,11 +208,11 @@ QAbstractGrpcChannel::StatusCode QGrpcHttp2Channel::call(const QString &method,
         loop.exec();
     }
 
-    StatusCode grpcStatus = StatusCode::Unknown;
+    QGrpcStatus::StatusCode grpcStatus = QGrpcStatus::StatusCode::Unknown;
     ret = d->processReply(networkReply, grpcStatus);
 
     qProtoDebug() << __func__ << "RECV: " << ret.toHex() << "grpcStatus" << grpcStatus;
-    return grpcStatus;
+    return {grpcStatus, QString::fromUtf8(networkReply->rawHeader(GrpcStatusMessage))};
 }
 
 void QGrpcHttp2Channel::call(const QString &method, const QString &service, const QByteArray &args, QtProtobuf::QGrpcAsyncReply *reply)
@@ -220,16 +220,16 @@ void QGrpcHttp2Channel::call(const QString &method, const QString &service, cons
     QNetworkReply *networkReply = d->post(method, service, args);
 
     auto connection = QObject::connect(networkReply, &QNetworkReply::finished, reply, [reply, networkReply]() {
-        StatusCode grpcStatus = StatusCode::Unknown;
+        QGrpcStatus::StatusCode grpcStatus = QGrpcStatus::StatusCode::Unknown;
         QByteArray data = QGrpcHttp2ChannelPrivate::processReply(networkReply, grpcStatus);
 
         qProtoDebug() << "RECV: " << data;
-        if (StatusCode::Ok == grpcStatus) {
+        if (QGrpcStatus::StatusCode::Ok == grpcStatus) {
             reply->setData(data);
             reply->finished();
         } else {
             reply->setData({});
-            reply->error(grpcStatus, QString::fromUtf8(networkReply->rawHeader(GrpcStatusMessage)));
+            reply->error({grpcStatus, QString::fromUtf8(networkReply->rawHeader(GrpcStatusMessage))});
         }
     });
 
@@ -295,5 +295,5 @@ void QGrpcHttp2Channel::abort(QGrpcAsyncReply *reply)
 {
     assert(reply != nullptr);
     reply->setData({});
-    reply->error(StatusCode::Aborted, QLatin1String("Call aborted by user or timeout"));
+    reply->error({QGrpcStatus::StatusCode::Aborted, QLatin1String("Call aborted by user or timeout")});
 }

+ 1 - 1
src/grpc/qgrpchttp2channel.h

@@ -43,7 +43,7 @@ public:
     QGrpcHttp2Channel(const QUrl &url, const AbstractCredentials &credentials);
     ~QGrpcHttp2Channel();
 
-    StatusCode call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret) override;
+    QGrpcStatus call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret) override;
     void call(const QString &method, const QString &service, const QByteArray &args, QtProtobuf::QGrpcAsyncReply *reply) override;
     void subscribe(const QString &method, const QString &service, const QByteArray &args, QAbstractGrpcClient *client, const std::function<void (const QByteArray &)> &handler) override;
 

+ 95 - 0
src/grpc/qgrpcstatus.cpp

@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#include "qgrpcstatus.h"
+
+namespace QtProtobuf {
+
+class QGrpcStatusPrivate {
+public:
+    QGrpcStatusPrivate(QGrpcStatus::StatusCode code, QString message) : m_code(code)
+      , m_message(message)
+    {}
+
+    QGrpcStatus::StatusCode m_code;
+    QString m_message;
+};
+
+QGrpcStatus::QGrpcStatus(StatusCode code, QString message) : d(new QGrpcStatusPrivate(code, message))
+{}
+
+QGrpcStatus::QGrpcStatus(const QGrpcStatus &other) : d(new QGrpcStatusPrivate(other.d->m_code, other.d->m_message))
+{}
+
+QGrpcStatus::QGrpcStatus(QGrpcStatus &&other) : d(other.d)
+{
+    other.d = nullptr;
+}
+
+QGrpcStatus &QGrpcStatus::operator =(const QGrpcStatus &other)
+{
+    d->m_code = other.d->m_code;
+    d->m_message = other.d->m_message;
+    return *this;
+}
+
+QGrpcStatus &QGrpcStatus::operator =(QGrpcStatus &&other)
+{
+    d = other.d;
+    other.d = nullptr;
+    return *this;
+}
+
+QGrpcStatus::~QGrpcStatus()
+{
+    delete d;
+}
+
+QString QGrpcStatus::message() const
+{
+    return d->m_message;
+}
+
+QGrpcStatus::StatusCode QGrpcStatus::code() const
+{
+    return d->m_code;
+}
+
+bool QGrpcStatus::operator ==(StatusCode code) const
+{
+    return d->m_code == code;
+}
+
+bool QGrpcStatus::operator ==(const QGrpcStatus &other) const
+{
+    return d->m_code == other.d->m_code;
+}
+
+}
+
+bool operator ==(QtProtobuf::QGrpcStatus::StatusCode code, const QtProtobuf::QGrpcStatus &status)
+{
+    return status == code;
+}

+ 96 - 0
src/grpc/qgrpcstatus.h

@@ -0,0 +1,96 @@
+/*
+ * 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
+
+#include <QString>
+
+#include "qtgrpcglobal.h"
+
+namespace QtProtobuf {
+
+/*!
+ * \ingroup QtGrpc
+ * \brief The QGrpcStatus class
+ */
+class QGrpcStatus final {
+public:
+    /*!
+     * \enum StatusCode
+     * \brief Channel's status codes
+     *
+     * \see <a href="https://github.com/grpc/grpc/blob/master/doc/statuscodes.md">gRPC status codes</a>
+     */
+    enum StatusCode {
+        Ok = 0,                 //!< No error
+        Cancelled = 1,          //!< The operation was cancelled, typically by the caller
+        Unknown = 2,            //!< Unknown error
+        InvalidArgument = 3,    //!< The client specified an invalid argument
+        DeadlineExceeded = 4,   //!< The deadline expired before the operation could complete
+        NotFound = 5,           //!< Some requested entity (e.g., file or directory) was not found
+        AlreadyExists = 6,      //!< The entity that a client attempted to create (e.g., file or directory) already exists. 	409 Conflict
+        PermissionDenied = 7,   //!< The caller does not have permission to execute the specified operation. PERMISSION_DENIED must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED instead for those errors). PERMISSION_DENIED must not be used if the caller can not be identified (use UNAUTHENTICATED instead for those errors). This error code does not imply the request is valid or the requested entity exists or satisfies other pre-conditions. 	403 Forbidden
+        Unauthenticated = 16,   //!< The request does not have valid authentication credentials for the operation
+        ResourceExhausted = 8,  //!< Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space
+        FailedPrecondition = 9, //!< The operation was rejected because the system is not in a state required for the operation's execution
+        Aborted = 10,           //!< The operation was aborted, typically due to a concurrency issue such as a sequencer check failure or transaction abort
+        OutOfRange = 11,        //!< The operation was attempted past the valid range
+        Unimplemented = 12,     //!< The operation is not implemented or is not supported/enabled in this service
+        Internal = 13,          //!< Internal errors. This means that some invariants expected by the underlying system have been broken.
+        Unavailable = 14,       //!< The service is currently unavailable
+        DataLoss = 15,          //!< Unrecoverable data loss or corruption
+    };
+
+    QGrpcStatus(StatusCode code = StatusCode::Ok, QString message = QString());
+    ~QGrpcStatus();
+
+    /*!
+     * \brief code
+     * \return
+     */
+    StatusCode code() const;
+
+    /*!
+     * \brief message
+     * \return
+     */
+    QString message() const;
+
+    bool operator ==(StatusCode code) const;
+    bool operator ==(const QGrpcStatus &other) const;
+
+    QGrpcStatus(const QGrpcStatus &other);
+    QGrpcStatus &operator =(const QGrpcStatus &other);
+
+    QGrpcStatus(QGrpcStatus &&other);
+    QGrpcStatus &operator =(QGrpcStatus &&other);
+
+private:
+    QGrpcStatus();
+    class QGrpcStatusPrivate *d;
+};
+}
+
+bool operator ==(QtProtobuf::QGrpcStatus::StatusCode code, const QtProtobuf::QGrpcStatus &status);

+ 22 - 22
tests/test_grpc/clienttest.cpp

@@ -130,23 +130,23 @@ TEST_F(ClientTest, StringEchoImmediateAsyncAbortTest)
         waiter.quit();
     });
 
-    QAbstractGrpcChannel::StatusCode asyncStatus = QAbstractGrpcChannel::StatusCode::Ok;
-    QObject::connect(reply, &QGrpcAsyncReply::error, reply, [&asyncStatus](QAbstractGrpcChannel::StatusCode code) {
-        asyncStatus = code;
+    QGrpcStatus::StatusCode asyncStatus = QGrpcStatus::StatusCode::Ok;
+    QObject::connect(reply, &QGrpcAsyncReply::error, reply, [&asyncStatus](const QGrpcStatus &status) {
+        asyncStatus = status.code();
     });
 
-    QAbstractGrpcChannel::StatusCode clientStatus = QAbstractGrpcChannel::StatusCode::Ok;
-    QObject::connect(&testClient, &TestServiceClient::error, reply, [&clientStatus](QAbstractGrpcChannel::StatusCode code, QString errMsg) {
-        clientStatus = code;
-        std::cerr << code << ":" << errMsg.toStdString();
+    QGrpcStatus::StatusCode clientStatus = QGrpcStatus::StatusCode::Ok;
+    QObject::connect(&testClient, &TestServiceClient::error, reply, [&clientStatus](const QGrpcStatus &status) {
+        clientStatus = status.code();
+        std::cerr << status.code() << ":" << status.message().toStdString();
     });
 
     QTimer::singleShot(5000, &waiter, &QEventLoop::quit);
     reply->abort();
     waiter.exec();
 
-    ASSERT_EQ(clientStatus, QAbstractGrpcChannel::StatusCode::Aborted);
-    ASSERT_EQ(asyncStatus, QAbstractGrpcChannel::StatusCode::Aborted);
+    ASSERT_EQ(clientStatus, QGrpcStatus::StatusCode::Aborted);
+    ASSERT_EQ(asyncStatus, QGrpcStatus::StatusCode::Aborted);
     ASSERT_STREQ(result.testFieldString().toStdString().c_str(), "Result not changed by echo");
 }
 
@@ -167,7 +167,7 @@ TEST_F(ClientTest, StringEchoDeferredAsyncAbortTest)
         result = reply->read<SimpleStringMessage>();
         waiter.quit();
     });
-    QObject::connect(reply, &QGrpcAsyncReply::error, reply, [&errorCalled](QAbstractGrpcChannel::StatusCode) {
+    QObject::connect(reply, &QGrpcAsyncReply::error, reply, [&errorCalled]() {
         errorCalled = true;
     });
 
@@ -268,14 +268,14 @@ TEST_F(ClientTest, StatusMessageAsyncTest)
     TestServiceClient testClient;
     testClient.attachChannel(std::make_shared<QGrpcHttp2Channel>(m_echoServerAddress, InsecureCredentials()));
     SimpleStringMessage request(QString{"Some status message"});
-    QAbstractGrpcChannel::StatusCode asyncStatus = QAbstractGrpcChannel::StatusCode::Ok;
+    QGrpcStatus::StatusCode asyncStatus = QGrpcStatus::StatusCode::Ok;
     QEventLoop waiter;
     QString statusMessage;
 
     QGrpcAsyncReply* reply = testClient.testMethodStatusMessage(request);
-    QObject::connect(reply, &QGrpcAsyncReply::error, reply, [&asyncStatus, &waiter, &statusMessage](QAbstractGrpcChannel::StatusCode code, const QString &msg) {
-        asyncStatus = code;
-        statusMessage = msg;
+    QObject::connect(reply, &QGrpcAsyncReply::error, reply, [&asyncStatus, &waiter, &statusMessage](const QGrpcStatus &status) {
+        asyncStatus = status.code();
+        statusMessage = status.message();
         waiter.quit();
     });
 
@@ -290,13 +290,13 @@ TEST_F(ClientTest, StatusMessageClientAsyncTest)
     TestServiceClient testClient;
     testClient.attachChannel(std::make_shared<QGrpcHttp2Channel>(m_echoServerAddress, InsecureCredentials()));
     SimpleStringMessage request(QString{"Some status message"});
-    QAbstractGrpcChannel::StatusCode asyncStatus = QAbstractGrpcChannel::StatusCode::Ok;
+    QGrpcStatus::StatusCode asyncStatus = QGrpcStatus::StatusCode::Ok;
     QEventLoop waiter;
     QString statusMessage;
 
-    QObject::connect(&testClient, &TestServiceClient::error, [&asyncStatus, &waiter, &statusMessage](QAbstractGrpcChannel::StatusCode code, const QString &msg) {
-        asyncStatus = code;
-        statusMessage = msg;
+    QObject::connect(&testClient, &TestServiceClient::error, [&asyncStatus, &waiter, &statusMessage](const QGrpcStatus &status) {
+        asyncStatus = status.code();
+        statusMessage = status.message();
         waiter.quit();
     });
 
@@ -314,13 +314,13 @@ TEST_F(ClientTest, DISABLED_StatusMessageClientSyncTest)
     testClient.attachChannel(std::make_shared<QGrpcHttp2Channel>(m_echoServerAddress, InsecureCredentials()));
     SimpleStringMessage request(QString{"Some status message"});
     QPointer<SimpleStringMessage> ret(new SimpleStringMessage);
-    QAbstractGrpcChannel::StatusCode asyncStatus = QAbstractGrpcChannel::StatusCode::Ok;
+    QGrpcStatus::StatusCode asyncStatus = QGrpcStatus::StatusCode::Ok;
     QEventLoop waiter;
     QString statusMessage;
 
-    QObject::connect(&testClient, &TestServiceClient::error, [&asyncStatus, &waiter, &statusMessage](QAbstractGrpcChannel::StatusCode code, const QString &msg) {
-        asyncStatus = code;
-        statusMessage = msg;
+    QObject::connect(&testClient, &TestServiceClient::error, [&asyncStatus, &waiter, &statusMessage](const QGrpcStatus &status) {
+        asyncStatus = status.code();
+        statusMessage = status.message();
         waiter.quit();
     });