Skip to content
Snippets Groups Projects
Verified Commit fd979176 authored by Timm Fitschen's avatar Timm Fitschen
Browse files

WIP: error handling

parent 6c149c8b
No related branches found
No related tags found
1 merge request!3Better Error Handling and Logging
......@@ -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)
......
......@@ -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"
......
......@@ -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;
};
/**
......
......@@ -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
......
Subproject commit 12f072263c05208464b80c0124bde0396b100d86
Subproject commit 3f867687cc71e42e964051b35f856abf6b1c8f09
......@@ -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
......
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment