use result in entity_query_builder

This commit is contained in:
Sascha Kühl 2024-04-10 16:13:37 +02:00
parent b7c12d8217
commit 25bcc362f2
9 changed files with 331 additions and 124 deletions

View File

@ -51,9 +51,46 @@ TEST_CASE_METHOD(SessionFixture, "Use session to find object with id", "[session
ses.create_schema();
auto a380 = ses.insert<airplane>(1, "Boeing", "A380");
auto result = ses.find<airplane>(1);
auto result = ses.find<airplane>(2);
REQUIRE(!result.is_ok());
REQUIRE((result.err() == sql::session_error::FailedToFindObject));
result = ses.find<airplane>(1);
REQUIRE(result);
auto read_a380 = result.value();
REQUIRE(a380->id == read_a380->id);
}
TEST_CASE_METHOD(SessionFixture, "Use session to find all objects", "[session][find]") {
using namespace matador::test;
ses.attach<airplane>("airplane");
ses.create_schema();
std::vector<std::unique_ptr<airplane>> planes;
planes.emplace_back(new airplane(1, "Airbus", "A380"));
planes.emplace_back(new airplane(2, "Boeing", "707"));
planes.emplace_back(new airplane(3, "Boeing", "747"));
for (auto &&plane: planes) {
ses.insert(plane.release());
}
auto result = ses.find<airplane>();
std::vector<std::tuple<int, std::string, std::string>> expected_result {
{1, "Airbus", "A380"},
{2, "Boeing", "707"},
{3, "Boeing", "747"}
};
REQUIRE(result);
auto all_planes = result.release();
size_t index {0};
for (const auto &i: all_planes) {
REQUIRE(i.id == std::get<0>(expected_result[index]));
REQUIRE(i.brand == std::get<1>(expected_result[index]));
REQUIRE(i.model == std::get<2>(expected_result[index]));
++index;
}
}

View File

@ -8,6 +8,8 @@
#include "matador/sql/query_intermediates.hpp"
#include "matador/sql/value.hpp"
#include "matador/utils/result.hpp"
#include <iostream>
namespace matador::sql {
@ -25,6 +27,24 @@ struct entity_query_data {
std::unique_ptr<basic_condition> where_clause;
};
enum class query_build_error : std::uint8_t {
Ok = 0,
UnknownType,
MissingPrimaryKey,
UnexpectedError
};
class query_builder_exception : public std::exception
{
public:
explicit query_builder_exception(query_build_error error) : error_(error) {}
[[nodiscard]] query_build_error error() const { return error_; }
private:
const query_build_error error_;
};
class entity_query_builder
{
public:
@ -32,18 +52,45 @@ public:
: schema_(scm) {}
template<class EntityType, typename PrimaryKeyType>
std::optional<entity_query_data> build(const PrimaryKeyType &pk) {
utils::result<entity_query_data, query_build_error> build(const PrimaryKeyType &pk) {
const auto info = schema_.info<EntityType>();
if (!info) {
return std::nullopt;
return utils::error(query_build_error::UnknownType);
}
pk_ = pk;
table_info_stack_.push(info.value());
entity_query_data_ = { info->name };
EntityType obj;
matador::utils::access::process(*this, obj);
try {
matador::utils::access::process(*this, obj);
return std::move(entity_query_data_);
return {utils::ok(std::move(entity_query_data_))};
} catch (const query_builder_exception &ex) {
return {utils::error(ex.error())};
} catch (...) {
return {utils::error(query_build_error::UnexpectedError)};
}
}
template<class EntityType>
utils::result<entity_query_data, query_build_error> build() {
const auto info = schema_.info<EntityType>();
if (!info) {
return utils::error(query_build_error::UnknownType);
}
pk_ = nullptr;
table_info_stack_.push(info.value());
entity_query_data_ = { info->name };
EntityType obj;
try {
matador::utils::access::process(*this, obj);
return {utils::ok(std::move(entity_query_data_))};
} catch (const query_builder_exception &ex) {
return {utils::error(ex.error())};
} catch (...) {
return {utils::error(query_build_error::UnexpectedError)};
}
}
template < class V >
@ -65,51 +112,15 @@ public:
}
template<class Pointer>
void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr)
void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr)
{
if (attr.fetch() == utils::fetch_type::EAGER) {
const auto info = schema_.info<typename Pointer::value_type>();
if (!info) {
return;
}
table_info_stack_.push(info.value());
typename Pointer::value_type obj;
matador::utils::access::process(*this , obj);
table_info_stack_.pop();
auto pk = info->prototype.primary_key();
if (!pk) {
// error, prototype doesn't has a pk
return;
}
append_join({table_info_stack_.top().name, id}, {info->name, pk->name()});
} else {
push(id);
}
on_foreign_object(id, obj, attr);
}
template<class Pointer>
void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr)
void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr)
{
if (attr.fetch() == utils::fetch_type::EAGER) {
const auto info = schema_.info<typename Pointer::value_type>();
if (!info) {
return;
}
table_info_stack_.push(info.value());
typename Pointer::value_type obj;
matador::utils::access::process(*this, obj);
table_info_stack_.pop();
auto pk = info->prototype.primary_key();
if (!pk) {
// error, prototype doesn't has a pk
return;
}
append_join({table_info_stack_.top().name, id}, {info->name, pk->name()});
} else {
push(id);
}
on_foreign_object(id, obj, attr);
}
template<class ContainerType>
@ -118,7 +129,7 @@ public:
if (attr.fetch() == utils::fetch_type::EAGER) {
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
if (!info) {
return;
throw query_builder_exception{query_build_error::UnknownType};
}
table_info_stack_.push(info.value());
typename ContainerType::value_type::value_type obj;
@ -127,8 +138,7 @@ public:
auto pk = info->prototype.primary_key();
if (!pk) {
// error, prototype doesn't has a pk
return;
throw query_builder_exception{query_build_error::MissingPrimaryKey};
}
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {info->name, join_column});
@ -141,7 +151,7 @@ public:
if (attr.fetch() == utils::fetch_type::EAGER) {
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
if (!info) {
return;
throw query_builder_exception{query_build_error::UnknownType};
}
table_info_stack_.push(info.value());
typename ContainerType::value_type::value_type obj;
@ -150,8 +160,7 @@ public:
auto pk = info->prototype.primary_key();
if (!pk) {
// error, prototype doesn't has a pk
return;
throw query_builder_exception{query_build_error::MissingPrimaryKey};
}
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_column});
@ -163,6 +172,8 @@ public:
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
private:
template<class Pointer>
void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr);
void push(const std::string &column_name);
[[nodiscard]] bool is_root_entity() const;
void append_join(const column &left, const column &right);
@ -175,5 +186,28 @@ private:
int column_index{0};
};
template<class Pointer>
void entity_query_builder::on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr)
{
if (attr.fetch() == utils::fetch_type::EAGER) {
const auto info = schema_.info<typename Pointer::value_type>();
if (!info) {
throw query_builder_exception{query_build_error::UnknownType};
}
table_info_stack_.push(info.value());
typename Pointer::value_type obj;
matador::utils::access::process(*this, obj);
table_info_stack_.pop();
auto pk = info->prototype.primary_key();
if (!pk) {
throw query_builder_exception{query_build_error::MissingPrimaryKey};
}
append_join({table_info_stack_.top().name, id}, {info->name, pk->name()});
} else {
push(id);
}
}
}
#endif //QUERY_ENTITY_QUERY_BUILDER_HPP

View File

@ -40,7 +40,7 @@ protected:
std::shared_ptr<query_data> data_;
};
class query_execute_finish : public query_intermediate
class query_execute : public query_intermediate
{
public:
using query_intermediate::query_intermediate;
@ -50,7 +50,7 @@ public:
[[nodiscard]] query_context build() const;
};
class query_select_finish : public query_intermediate
class query_select : public query_intermediate
{
protected:
using query_intermediate::query_intermediate;
@ -96,36 +96,36 @@ private:
class query_offset_intermediate;
class query_limit_intermediate : public query_select_finish
class query_limit_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_offset_intermediate offset(size_t offset);
};
class query_offset_intermediate : public query_select_finish
class query_offset_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_limit_intermediate limit(size_t limit);
};
class query_order_direction_intermediate : public query_select_finish
class query_order_direction_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_limit_intermediate limit(size_t limit);
};
class query_order_by_intermediate;
class query_group_by_intermediate : public query_select_finish
class query_group_by_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_order_by_intermediate order_by(const column &col);
};
@ -139,10 +139,10 @@ public:
query_order_direction_intermediate desc();
};
class query_where_intermediate : public query_select_finish
class query_where_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_group_by_intermediate group_by(const column &col);
query_order_by_intermediate order_by(const column &col);
@ -150,10 +150,10 @@ public:
class query_join_intermediate;
class query_on_intermediate : public query_select_finish
class query_on_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_join_intermediate join_left(const table &t);
template<class Condition>
@ -191,10 +191,10 @@ private:
query_on_intermediate on_clause(std::unique_ptr<basic_condition> &&cond);
};
class query_from_intermediate : public query_select_finish
class query_from_intermediate : public query_select
{
public:
using query_select_finish::query_select_finish;
using query_select::query_select;
query_join_intermediate join_left(const table &t);
template<class Condition>
@ -244,16 +244,16 @@ class query_into_intermediate : public query_intermediate
public:
using query_intermediate::query_intermediate;
query_execute_finish values(std::initializer_list<any_type> values);
query_execute_finish values(std::vector<any_type> &&values);
query_execute values(std::initializer_list<any_type> values);
query_execute values(std::vector<any_type> &&values);
template<class Type>
query_execute_finish values()
query_execute values()
{
Type obj;
return values(std::move(as_placeholder(obj)));
}
template<class Type>
query_execute_finish values(const Type &obj)
query_execute values(const Type &obj)
{
return values(std::move(value_extractor::extract(obj)));
}
@ -264,10 +264,10 @@ class query_create_intermediate : public query_start_intermediate
public:
explicit query_create_intermediate(connection &db, const sql::schema &schema);
query_execute_finish table(const sql::table &table, std::initializer_list<column_definition> columns);
query_execute_finish table(const sql::table &table, const std::vector<column_definition> &columns);
query_execute table(const sql::table &table, std::initializer_list<column_definition> columns);
query_execute table(const sql::table &table, const std::vector<column_definition> &columns);
template<class Type>
query_execute_finish table(const sql::table &table)
query_execute table(const sql::table &table)
{
return this->table(table, column_definition_generator::generate<Type>(schema_));
}
@ -278,7 +278,7 @@ class query_drop_intermediate : query_start_intermediate
public:
explicit query_drop_intermediate(connection &db, const sql::schema &schema);
query_execute_finish table(const sql::table &table);
query_execute table(const sql::table &table);
};
class query_insert_intermediate : public query_start_intermediate
@ -291,18 +291,18 @@ public:
query_into_intermediate into(const sql::table &table);
};
class query_execute_where_intermediate : public query_execute_finish
class query_execute_where_intermediate : public query_execute
{
public:
using query_execute_finish::query_execute_finish;
using query_execute::query_execute;
query_order_by_intermediate order_by(const column &col);
};
class query_set_intermediate : public query_execute_finish
class query_set_intermediate : public query_execute
{
public:
using query_execute_finish::query_execute_finish;
using query_execute::query_execute;
template<class Condition>
query_execute_where_intermediate where(const Condition &cond)
@ -328,10 +328,10 @@ public:
}
};
class query_delete_from_intermediate : public query_execute_finish
class query_delete_from_intermediate : public query_execute
{
public:
using query_execute_finish::query_execute_finish;
using query_execute::query_execute;
template<class Condition>
query_execute_where_intermediate where(const Condition &cond)

View File

@ -14,6 +14,14 @@ namespace matador::sql {
class dialect;
enum class session_error {
Ok = 0,
NoConnectionAvailable,
UnknownType,
FailedToBuildQuery,
FailedToFindObject
};
class session
{
public:
@ -33,35 +41,68 @@ public:
}
template<typename Type, typename PrimaryKeyType>
std::optional<entity<Type>> find(const PrimaryKeyType &pk) {
utils::result<entity<Type>, session_error> find(const PrimaryKeyType &pk) {
auto c = pool_.acquire();
if (!c.valid()) {
throw std::logic_error("no database connection available");
return utils::error(session_error::NoConnectionAvailable);
}
auto info = schema_->info<Type>();
if (!info) {
return {};
return utils::error(session_error::UnknownType);
}
entity_query_builder eqb(*schema_);
auto data = eqb.build<Type>(pk);
auto q = c->query(*schema_)
.select(data->columns)
.from(data->root_table_name);
for (auto &jd : data->joins) {
q.join_left(jd.join_table)
.on(std::move(jd.condition));
if (!data.is_ok()) {
return utils::error(session_error::FailedToBuildQuery);
}
auto e = q
.where(std::move(data->where_clause))
.template fetch_one<Type>();
if (!e) {
return std::nullopt;
auto obj = build_select_query(c, data.release()).template fetch_one<Type>();
if (!obj) {
return utils::error(session_error::FailedToFindObject);
}
return entity<Type>{ e.release() };
return utils::ok(entity<Type>{ obj.release() });
}
template<typename Type>
utils::result<query_result<Type>, session_error> find() {
auto c = pool_.acquire();
if (!c.valid()) {
return utils::error(session_error::NoConnectionAvailable);
}
auto info = schema_->info<Type>();
if (!info) {
return utils::error(session_error::UnknownType);
}
entity_query_builder eqb(*schema_);
auto data = eqb.build<Type>();
if (!data.is_ok()) {
return utils::error(session_error::FailedToBuildQuery);
}
return utils::ok(build_select_query(c, data.release()).template fetch_all<Type>());
}
template<typename Type>
utils::result<query_from_intermediate, session_error> select() {
auto c = pool_.acquire();
if (!c.valid()) {
return utils::error(session_error::NoConnectionAvailable);
}
auto info = schema_->info<Type>();
if (!info) {
return utils::error(session_error::UnknownType);
}
entity_query_builder eqb(*schema_);
auto data = eqb.build<Type>();
if (!data.is_ok()) {
return utils::error(session_error::FailedToBuildQuery);
}
return utils::ok(build_select_query(c, data.release()).template fetch_all<Type>());
}
template<typename Type>
@ -79,10 +120,12 @@ public:
const class dialect& dialect() const;
private:
friend class query_select_finish;
friend class query_select;
[[nodiscard]] std::unique_ptr<query_result_impl> fetch(const std::string &sql) const;
query_select build_select_query(connection_ptr<connection> &conn, entity_query_data &&data) const;
private:
connection_pool<connection> &pool_;
const class dialect &dialect_;

View File

@ -18,11 +18,13 @@ template < typename ValueType >
class ok
{
public:
using value_type = ValueType;
explicit constexpr ok(const ValueType &value) : value_(value) {}
explicit constexpr ok(ValueType &&value) : value_(std::move(value)) {}
constexpr ValueType&& release() { return std::move(value_); }
const ValueType& value() const { return value_; }
ValueType& value() { return value_; }
private:
ValueType value_;
@ -32,11 +34,14 @@ template < typename ErrorType >
class error
{
public:
using value_type = ErrorType;
explicit constexpr error(const ErrorType &error) : error_(error) {}
explicit constexpr error(ErrorType &&error) : error_(std::move(error)) {}
constexpr ErrorType&& release() { return std::move(error_); }
const ErrorType& value() const { return error_; }
ErrorType value() { return error_; }
private:
ErrorType error_;
@ -49,8 +54,13 @@ public:
using value_type = ok<ValueType>;
using error_type = error<ErrorType>;
result(value_type value) : result_(value) {} // NOLINT(*-explicit-constructor)
result(error_type error) : result_(error) {} // NOLINT(*-explicit-constructor)
result() : result_(ValueType{}) {}
result(value_type value) : result_(std::move(value)) {} // NOLINT(*-explicit-constructor)
result(error_type error) : result_(std::move(error)) {} // NOLINT(*-explicit-constructor)
result(const result<ValueType, ErrorType> &x) = default;
result& operator=(const result<ValueType, ErrorType> &x) = default;
result(result<ValueType, ErrorType> &&x) = default;
result& operator=(result<ValueType, ErrorType> &&x) = default;
operator bool() const { return is_ok(); } // NOLINT(*-explicit-constructor)
@ -65,15 +75,33 @@ public:
ErrorType err() { return std::get<error_type>(result_).value(); }
constexpr const ValueType* operator->() const { return &value(); }
constexpr ValueType* operator->() { return &value(); }
constexpr ValueType* operator->() { return &std::get<value_type>(result_).value(); }
template<typename SecondValueType, typename SecondErrorType, typename Func>
result<SecondValueType, SecondErrorType> and_then(Func f) {
template<typename Func, typename SecondValueType = typename std::invoke_result_t<Func, ValueType >>
result<SecondValueType, ErrorType> transform(Func &&f) {
if (is_ok()) {
return f(value());
return result<SecondValueType, ErrorType>(ok(f(release())));
}
return *this;
return result<SecondValueType, ErrorType>(error(release_error()));
}
template<typename Func, typename SecondValueType = typename std::invoke_result_t<Func, ValueType >::value_type::value_type>
result<SecondValueType, ErrorType> and_then(Func &&f) {
if (is_ok()) {
return f(release());
}
return result<SecondValueType, ErrorType>(error(release_error()));
}
template<typename Func, typename SecondErrorType = typename std::invoke_result_t<Func, ErrorType >::error_type::value_type>
result<ValueType, SecondErrorType> or_else(Func &&f) {
if (is_error()) {
return f(err());
}
return result<ValueType, SecondErrorType>(ok(release()));
}
private:

View File

@ -8,13 +8,13 @@ basic_query_intermediate::basic_query_intermediate(connection &db, const sql::sc
: connection_(db)
, schema_(schema) {}
query_result<record> query_select_finish::fetch_all()
query_result<record> query_select::fetch_all()
{
query_compiler compiler(connection_.dialect());
return connection_.fetch(compiler.compile(data_.get()));
}
std::optional<record> query_select_finish::fetch_one()
std::optional<record> query_select::fetch_one()
{
query_compiler compiler(connection_.dialect());
auto result = connection_.fetch(compiler.compile(data_.get()));
@ -26,19 +26,19 @@ std::optional<record> query_select_finish::fetch_one()
return *first.get();
}
query_context query_select_finish::build() const
query_context query_select::build() const
{
query_compiler compiler(connection_.dialect());
return compiler.compile(data_.get());
}
std::unique_ptr<query_result_impl> query_select_finish::fetch()
std::unique_ptr<query_result_impl> query_select::fetch()
{
query_compiler compiler(connection_.dialect());
return connection_.fetch(compiler.compile(data_.get()).sql);
}
statement query_select_finish::prepare()
statement query_select::prepare()
{
query_compiler compiler(connection_.dialect());
return connection_.prepare(compiler.compile(data_.get()));
@ -184,30 +184,30 @@ query_into_intermediate query_insert_intermediate::into(const table &table)
return {connection_, schema_, data_};
}
size_t query_execute_finish::execute()
size_t query_execute::execute()
{
query_compiler compiler(connection_.dialect());
return connection_.execute(compiler.compile(data_.get()).sql);
}
statement query_execute_finish::prepare()
statement query_execute::prepare()
{
query_compiler compiler(connection_.dialect());
return connection_.prepare(compiler.compile(data_.get()));
}
query_context query_execute_finish::build() const
query_context query_execute::build() const
{
query_compiler compiler(connection_.dialect());
return compiler.compile(data_.get());
}
query_execute_finish query_into_intermediate::values(std::initializer_list<any_type> values)
query_execute query_into_intermediate::values(std::initializer_list<any_type> values)
{
return this->values(std::vector<any_type>(values));
}
query_execute_finish query_into_intermediate::values(std::vector<any_type> &&values)
query_execute query_into_intermediate::values(std::vector<any_type> &&values)
{
data_->parts.push_back(std::make_unique<query_values_part>(std::move(values)));
return {connection_, schema_, data_};
@ -218,12 +218,12 @@ query_create_intermediate::query_create_intermediate(connection &db, const sql::
data_->parts.push_back(std::make_unique<query_create_part>());
}
query_execute_finish query_create_intermediate::table(const sql::table &table, std::initializer_list<column_definition> columns)
query_execute query_create_intermediate::table(const sql::table &table, std::initializer_list<column_definition> columns)
{
return this->table(table, std::vector<column_definition>{columns});
}
query_execute_finish query_create_intermediate::table(const sql::table &table, const std::vector<column_definition> &columns)
query_execute query_create_intermediate::table(const sql::table &table, const std::vector<column_definition> &columns)
{
data_->parts.push_back(std::make_unique<query_create_table_part>(table, columns));
return {connection_, schema_, data_};
@ -235,7 +235,7 @@ query_drop_intermediate::query_drop_intermediate(connection &db, const sql::sche
data_->parts.push_back(std::make_unique<query_drop_part>());
}
query_execute_finish query_drop_intermediate::table(const sql::table &table)
query_execute query_drop_intermediate::table(const sql::table &table)
{
data_->parts.push_back(std::make_unique<query_drop_table_part>(table));
return {connection_, schema_, data_};

View File

@ -103,4 +103,26 @@ std::unique_ptr<query_result_impl> session::fetch(const std::string &sql) const
return c->fetch(sql);
}
query_select session::build_select_query(connection_ptr<connection> &conn, entity_query_data &&data) const
{
auto q = conn->query(*schema_)
.select(data.columns)
.from(data.root_table_name);
auto it = data.joins.begin();
if (it != data.joins.end()) {
auto qj = q.join_left(it->join_table).on(std::move(it->condition));
while (++it != data.joins.end()) {
qj.join_left(it->join_table).on(std::move(it->condition));
}
if (data.where_clause) {
return qj.where(std::move(data.where_clause));
}
return qj;
} else if (data.where_clause) {
return q.where(std::move(data.where_clause));
} else {
return q;
}
}
}

View File

@ -21,7 +21,7 @@ TEST_CASE("Create sql query data for entity with eager belongs to", "[query][ent
auto data = eqb.build<book>(17);
REQUIRE(data.has_value());
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "books");
REQUIRE(data->joins.size() == 1);
const std::vector<column> expected_columns {
@ -81,7 +81,7 @@ TEST_CASE("Create sql query data for entity with eager has many belongs to", "[q
auto data = eqb.build<order>(17);
REQUIRE(data.has_value());
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "orders");
REQUIRE(data->joins.size() == 1);
const std::vector<column> expected_columns = {
@ -135,7 +135,7 @@ TEST_CASE("Create sql query data for entity with eager many to many", "[query][e
auto data = eqb.build<ingredient>(17);
REQUIRE(data.has_value());
REQUIRE(data.is_ok());
REQUIRE(data->root_table_name == "ingredients");
REQUIRE(data->joins.size() == 2);
const std::vector<column> expected_columns {

View File

@ -20,6 +20,14 @@ utils::result<float, math_error>multiply(int x, int y) {
return utils::ok(float(x) * y);
}
utils::result<float, math_error>plus(int x, int y) {
return utils::ok(float(x) + y);
}
utils::result<float, std::string>error_to_string(math_error err) {
return utils::error(std::string("error"));
}
}
using namespace matador;
@ -41,7 +49,42 @@ TEST_CASE("Result tests", "[result]") {
REQUIRE((res.err() == test::math_error::DIVISION_BY_ZERO));
res = test::divide(4, 2)
.and_then([](const auto &val) { return test::multiply(val, 5); })
.and_then([](const auto &val) { return test::plus(val, 10); });
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 20.0));
res = test::divide(4, 0)
.and_then([](const auto &val) {
return test::multiply(val, 5);
});
REQUIRE(!res);
REQUIRE(!res.is_ok());
REQUIRE(res.is_error());
REQUIRE((res.err() == test::math_error::DIVISION_BY_ZERO));
auto res2 = test::divide(4, 0)
.or_else([](const auto &err) {
return test::error_to_string(err);
});
REQUIRE(!res2);
REQUIRE(!res2.is_ok());
REQUIRE(res2.is_error());
REQUIRE((res2.err() == "error"));
res = test::divide(4, 2)
.and_then([](const auto &val) { return test::multiply(val, 5); })
.transform([](const auto &val) { return val + 10; });
REQUIRE(res);
REQUIRE(res.is_ok());
REQUIRE(!res.is_error());
REQUIRE((res.value() == 20.0));
// res = test::divide(4, 2)
// .and_then<>()
}