From fd979176589e76693d6a10080c1a20082817b1d1 Mon Sep 17 00:00:00 2001 From: Timm Fitschen <t.fitschen@indiscale.com> Date: Thu, 22 Jul 2021 10:59:34 +0200 Subject: [PATCH] WIP: error handling --- CMakeLists.txt | 2 +- conanfile.py | 2 +- include/caosdb/connection.h | 61 +++++++++--- include/caosdb/transaction.h | 182 ++++++++++++++++++++++++++++++++++- proto | 2 +- src/caosdb/connection.cpp | 48 ++++----- src/caosdb/transaction.cpp | 23 +++-- 7 files changed, 263 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91c0423..bb4ddc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ cmake_minimum_required(VERSION 3.14) -set(libcaosdb_VERSION 0.0.5) +set(libcaosdb_VERSION 0.0.6) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MAJOR 0) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MINOR 5) set(libcaosdb_COMPATIBLE_SERVER_VERSION_PATCH 0) diff --git a/conanfile.py b/conanfile.py index 1ed1abd..4ae59d5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -3,7 +3,7 @@ from conans import ConanFile, CMake, tools class CaosdbConan(ConanFile): name = "caosdb" - version = "0.0.5" + version = "0.0.6" license = "AGPL-3.0-or-later" author = "Timm C. Fitschen <t.fitschen@indiscale.com>" url = "https://gitlab.indiscale.com/caosdb/src/caosdb-cpplib.git" diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h index 2c58d57..abe53fb 100644 --- a/include/caosdb/connection.h +++ b/include/caosdb/connection.h @@ -45,6 +45,7 @@ using caosdb::entity::v1alpha1::EntityTransactionService; using caosdb::info::VersionInfo; using caosdb::info::v1alpha1::GeneralInfoService; using caosdb::transaction::Transaction; +using caosdb::transaction::TransactionStatus; using grpc::ChannelCredentials; class CertificateProvider { @@ -82,11 +83,7 @@ private: public: ConnectionConfiguration(const std::string &host, int port); virtual ~ConnectionConfiguration() = default; - friend auto operator<<(std::ostream &out, - const ConnectionConfiguration &configuration) - -> std::ostream &; - [[nodiscard]] auto virtual ToString() const -> std::string = 0; [[nodiscard]] auto GetHost() const -> std::string; [[nodiscard]] auto GetPort() const -> int; [[nodiscard]] auto virtual GetChannelCredentials() const @@ -101,7 +98,6 @@ public: InsecureConnectionConfiguration(const std::string &host, int port); [[nodiscard]] auto GetChannelCredentials() const -> std::shared_ptr<ChannelCredentials> override; - [[nodiscard]] auto ToString() const -> std::string override; }; class TlsConnectionConfiguration : public ConnectionConfiguration { @@ -120,23 +116,62 @@ public: const Authenticator &authenticator); [[nodiscard]] auto GetChannelCredentials() const -> std::shared_ptr<ChannelCredentials> override; - [[nodiscard]] auto ToString() const -> std::string override; }; /** * @brief A reusable connection to a CaosDBServer. */ class Connection { - std::shared_ptr<grpc::Channel> channel; - std::unique_ptr<GeneralInfoService::Stub> general_info_service; - std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service; - public: explicit Connection(const ConnectionConfiguration &configuration); - friend auto operator<<(std::ostream &out, const Connection &connection) - -> std::ostream &; - [[nodiscard]] auto GetVersionInfo() const -> std::unique_ptr<VersionInfo>; + + /** + * Request the server's version and return the status of this request after + * termination.. + * + * The version is stored in the connection object and may be retrieved via + * GetVersionInfo() if the request was successful. + * + * This method does not throw any exceptions. Errors are indicated in the + * return value instead. + */ + auto RetrieveVersionInfoNoExceptions() const noexcept -> TransactionStatus; + + /** + * Request and return the server's version. + * + * If the request terminated unsuccessfully, a corresponding exception is + * being thrown. + */ + auto RetrieveVersionInfo() const -> const VersionInfo &; + + /** + * Return the server's version. + * + * Clients need to call RetrieveVersionInfo() or + * RetrieveVersionInfoNoExceptions() before the version info is locally + * available. Otherwise an empty instance is being returned. + */ + [[nodiscard]] inline auto GetVersionInfo() const noexcept + -> const VersionInfo & { + return this->version_info; + }; + [[nodiscard]] auto CreateTransaction() const -> std::unique_ptr<Transaction>; + +private: + /// GRPC-Channel (HTTP/2 Connection plus Authentication). We use a shared + /// pointer because Transaction instances also own the channel. + std::shared_ptr<grpc::Channel> channel; + /// Service for retrieving the server's version. We use a unique pointer + /// because only this connection owns and uses this service. + std::unique_ptr<GeneralInfoService::Stub> general_info_service; + /// The server's version. It's mutable because it is rather a cache than a + /// data member which is subject to change. + mutable VersionInfo version_info; + /// Service for entity transactions. We use a shared pointer because + /// Transaction instances also own this service stub. + std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service; }; /** diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index 8989951..0f97495 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -26,8 +26,9 @@ #ifndef CAOSDB_TRANSACTION_H #define CAOSDB_TRANSACTION_H -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string +#include "caosdb/localization.h" #include "caosdb/entity.h" // for Entity #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe... #include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RetrieveReq... @@ -54,7 +55,146 @@ private: std::unique_ptr<Entity> entity; }; -enum TransactionState { INIT = 10, EXECUTING = 20, SUCCESS = 30, ERROR = 40 }; +enum StatusCode { + INITIAL = -2, + EXECUTING = -1, + SUCCESS = 0, + UNAUTHENTICATED = 16, + UNAVAILABLE = 14, + GENERIC_RPC_ERROR = 20, + GENERIC_TRANSACTION_ERROR = 21, +}; + +/** + * State of a transaction instance. + */ +class TransactionStatus { +public: + inline static const auto INITIAL = TransactionStatus( + 0, StatusCode::INITIAL, + _("The transaction has not been executed yet and clients can still " + "change it.")); + static const auto EXECUTING = + TransactionStatus(10, StatusCode::EXECUTING, + _("The transaction is currently being executed.")); + static const auto SUCCESS = TransactionStatus( + 20, StatusCode::SUCCESS, + _("The transaction has been executed and it terminated successfully.")); + static const auto + RPC_ERROR = TransactionStatus( + 100, StatusCode::GENERIC_RPC_ERROR, + _("The attempt to execute this transaction was not successful because an " + "error occured in the transport or RPC protocol layer.")), + static const auto CONNECTION_ERROR = TransactionStatus( + 200, StatusCode::UNAVAILABLE, + _("The attempt to execute this transaction was not successful because " + "the connection to the server could not be established.")); + static const auto AUTHENTICATION_ERROR = TransactionStatus( + 300, StatusCode::UNAUTHENTICATED, + _("The attempt to execute this transaction has not been executed at all " + "because the authentication did not succeed.")); + static const auto TRANSACTION_ERROR = TransactionStatus( + 1000, StatusCode::GENERIC_TRANSACTION_ERROR, + _("The transaction terminated unsuccessfully with transaction errors.")); + + /** + * Return true if this TransactionStatus represents a terminated state. + */ + inline auto IsTerminated() const -> bool { + return this->numeric_value >= 20; + }; + + /** + * Return true if this TransactionStatus represents an erroneous state. + */ + inline auto IsError() const -> bool { return this->numeric_value >= 100; }; + + /** + * Equals operator is overloaded so we can use this class is if it were an + * Enum. + */ + inline int operator==(const TransactionStatus &other) { + return this->numeric_value == other.numeric_value; + }; + + /** + * Instantiate and return a new instance with the given code and description. + * + * Instances with a code and a description may be used to describe an + * erroneous state in more detail. + * + * This method should not be called on non-error states. + */ + inline auto Instantiate(StatusCode code, const std::string &details) const + -> const TransactionStatus { + return TransactionStatus(this->numeric_value, code, + this->description + " " + detail); + }; + + /** + * Instantiate and return a new instance with the given code. + * + * Instances with a code may be used to describe an erroneous state in more + * detail. + * + * This method should not be called on non-error states. + */ + inline auto Instantiate(StatusCode code) const -> const TransactionStatus { + return TransactionStatus(this->numeric_value, code, this->description); + } + + /** + * Instantiate and return a new instance with the given code and description. + * + * Instances descriptions may be used to describe an erroneous state in more + * detail. + * + * This method should no be called on non-error states. + */ + inline auto Instantiate(const std::string &details) const + -> const TransactionStatus { + return TransactionStatus(this->numeric_value, this->code, + this->description + " " + details); + } + + /** + * Return a description of the erroneous state. + * + * Returns an empty string if there is no description. + */ + inline auto GetDescription() const -> const std::string & { + return this->description; + } + + /** + * Return the status code of the state. + */ + inline auto GetCode() const -> StatusCode { return this->code; } + +private: + /** + * The numeric_value is for class-internal use only. + * + * We use it to group errors with different error codes together, e.g. all + * transaction errors. + */ + const int numeric_value; + + /** + * The code is an identifier of errors and should not be confused with the + * class-internal numeric_value. + */ + const StatusCode code; + + /** + * Description of the error + */ + const std::string description; + + TransactionStatus(int numeric_value, StatusCode code, + const std::string &description) + : numeric_value(numeric_value), code(code), description(description){}; +}; /** * @brief Create a transaction via `CaosDBConnection.createTransaction()` @@ -62,18 +202,50 @@ enum TransactionState { INIT = 10, EXECUTING = 20, SUCCESS = 30, ERROR = 40 }; class Transaction { private: std::unique_ptr<ResultSet> result_set; - TransactionState state = TransactionState::INIT; + TransactionStatus state = TransactionStatus::INITIAL; std::shared_ptr<EntityTransactionService::Stub> service_stub; RetrieveRequest request; // TODO(tf) + std::string error_message; public: Transaction(std::shared_ptr<EntityTransactionService::Stub> service_stub); auto RetrieveById(const std::string &id) -> void; - auto Execute() -> void; + + /** + * Execute this transaction in blocking mode and return the state. + */ + auto Execute() -> TransactionStatus; + + /** + * Execute this transaction in non-blocking mode and return immediately. + * + * A client may request the current state at any time via GetState(). + * + * Use WaitForIt() to join the back-ground execution of this transaction. + */ + auto ExecuteAsynchronously() -> void; + + /** + * Join the background execution and return the state when the execution + * terminates. + * + * Use this after ExecuteAsynchronously(). + */ + auto WaitForIt() -> TransactionStatus; + + /** + * Return the current state of the transaction. + */ + [[nodiscard]] inline auto GetState() const -> TransactionStatus { + return this->state; + } + [[nodiscard]] inline auto GetResultSet() const -> const ResultSet & { const ResultSet *result_set = this->result_set.get(); return *result_set; } + + auto GetErrors }; } // namespace caosdb::transaction diff --git a/proto b/proto index 12f0722..3f86768 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 12f072263c05208464b80c0124bde0396b100d86 +Subproject commit 3f867687cc71e42e964051b35f856abf6b1c8f09 diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index d2a2ae1..483b365 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -84,12 +84,6 @@ auto ConnectionConfiguration::GetHost() const -> std::string { auto ConnectionConfiguration::GetPort() const -> int { return this->port; } -auto operator<<(std::ostream &out, const ConnectionConfiguration &configuration) - -> std::ostream & { - out << configuration.ToString(); - return out; -} - InsecureConnectionConfiguration::InsecureConnectionConfiguration( const std::string &host, int port) : ConnectionConfiguration(host, port) { @@ -101,11 +95,6 @@ auto InsecureConnectionConfiguration::GetChannelCredentials() const return this->credentials; } -auto InsecureConnectionConfiguration::ToString() const -> std::string { - return "InsecureConnectionConfiguration(" + this->GetHost() + "," + - std::to_string(this->GetPort()) + ")"; -} - TlsConnectionConfiguration::TlsConnectionConfiguration(const std::string &host, int port) : ConnectionConfiguration(host, port) { @@ -148,12 +137,6 @@ auto TlsConnectionConfiguration::GetChannelCredentials() const return this->credentials; } -auto TlsConnectionConfiguration::ToString() const -> std::string { - return "TlsConnectionConfiguration(" + this->GetHost() + "," + - std::to_string(this->GetPort()) + "," + this->certificate_provider + - ")"; -} - Connection::Connection(const ConnectionConfiguration &configuration) { const std::string target = configuration.GetHost() + ":" + std::to_string(configuration.GetPort()); @@ -164,33 +147,38 @@ Connection::Connection(const ConnectionConfiguration &configuration) { std::make_shared<EntityTransactionService::Stub>(this->channel); } -auto operator<<(std::ostream &out, const Connection & /*connection*/) - -> std::ostream & { - out << "Connection()"; - return out; -} +auto Connection::RetrieveVersionInfoNoExceptions() const noexcept + -> TransactionStatus { -[[nodiscard]] auto Connection::GetVersionInfo() const - -> std::unique_ptr<VersionInfo> { const GetVersionInfoRequest request; GetVersionInfoResponse response; grpc::ClientContext context; const grpc::Status status = this->general_info_service->GetVersionInfo(&context, request, &response); + TransactionStatus &status = TransactionStatus::SUCCESS; if (!status.ok()) { switch (status.error_code()) { case grpc::StatusCode::UNAUTHENTICATED: - throw AuthenticationError(status.error_message()); + status = TransactionStatus::AUTHENTICATION_ERROR; case grpc::StatusCode::UNAVAILABLE: - throw ConnectionError(status.error_message()); + status = TransactionStatus::CONNECTION_ERROR; default: - throw std::runtime_error("Status Code " + - std::to_string(status.error_code()) + " - " + - status.error_message()); + status = TransactionStatus::RPC_ERROR.Instantiate( + std::to_string(status.error_code()) + " - " _(status.error_message())); } } - return std::make_unique<VersionInfo>(response.release_version_info()); + + return status; +} + +auto Connection::RetrieveVersionInfo() const -> const VersionInfo & { + + TransactionStatus status = RetrieveVersionInfoNoExceptions(); + if (status.IsError()) { + throw status.CreateException(); + } + return GetVersionInfo(); } [[nodiscard]] auto Connection::CreateTransaction() const diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index bf2e884..317d205 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -57,9 +57,9 @@ auto Transaction::RetrieveById(const std::string &id) -> void { this->request = request; } -auto Transaction::Execute() -> void { +auto Transaction::Execute() -> TransactionStatus { // TODO(tf) throw error if not int INIT state - this->state = TransactionState::EXECUTING; + this->state = TransactionStatus::EXECUTING; RetrieveResponse response; grpc::ClientContext context; @@ -67,20 +67,31 @@ auto Transaction::Execute() -> void { this->service_stub->Retrieve(&context, this->request, &response); if (!status.ok()) { + // TODO switch (status.error_code()) { case grpc::StatusCode::UNAUTHENTICATED: - throw AuthenticationError(status.error_message()); + this->state = TransactionStatus::ERROR.Instantiate( + StatusCode::UNAUTHENTICATED, status.error_message()); case grpc::StatusCode::UNAVAILABLE: - throw ConnectionError(status.error_message()); + this->state = TransactionStatus::ERROR.Instantiate( + StatusCode::UNAVAILABLE, status.error_message()); default: - std::cout << status.error_code() << "\n"; - throw std::runtime_error(status.error_message()); + this->state = TransactionStatus::ERROR.Instantiate( + StatusCode::GENERIC_RPC_ERROR, status.error_message()); } + } else { + this->state = TransactionStatus::SUCCESS; } auto *entity = response.release_entity(); auto result_set = std::make_unique<UniqueResult>(entity); this->result_set = std::move(result_set); + + return this->state; } +auto Transaction::ExecuteAsynchronously() -> TransactionStatus {} + +auto Transaction::WaitForIt() -> TransactionStatus {} + } // namespace caosdb::transaction -- GitLab