From 25bcc362f2bb06466328ee3ea44a8ee382b8cf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Wed, 10 Apr 2024 16:13:37 +0200 Subject: [PATCH] use result in entity_query_builder --- backends/tests/SessionTest.cpp | 39 +++++- include/matador/sql/entity_query_builder.hpp | 134 ++++++++++++------- include/matador/sql/query_intermediates.hpp | 60 ++++----- include/matador/sql/session.hpp | 79 ++++++++--- include/matador/utils/result.hpp | 42 +++++- src/sql/query_intermediates.cpp | 26 ++-- src/sql/session.cpp | 22 +++ test/EntityQueryBuilderTest.cpp | 6 +- test/ResultTest.cpp | 47 ++++++- 9 files changed, 331 insertions(+), 124 deletions(-) diff --git a/backends/tests/SessionTest.cpp b/backends/tests/SessionTest.cpp index ceccf68..abd9740 100644 --- a/backends/tests/SessionTest.cpp +++ b/backends/tests/SessionTest.cpp @@ -51,9 +51,46 @@ TEST_CASE_METHOD(SessionFixture, "Use session to find object with id", "[session ses.create_schema(); auto a380 = ses.insert(1, "Boeing", "A380"); - auto result = ses.find(1); + auto result = ses.find(2); + REQUIRE(!result.is_ok()); + REQUIRE((result.err() == sql::session_error::FailedToFindObject)); + + result = ses.find(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"); + ses.create_schema(); + + std::vector> 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(); + + std::vector> 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; + } + } \ No newline at end of file diff --git a/include/matador/sql/entity_query_builder.hpp b/include/matador/sql/entity_query_builder.hpp index f6a1c29..914fe09 100644 --- a/include/matador/sql/entity_query_builder.hpp +++ b/include/matador/sql/entity_query_builder.hpp @@ -8,6 +8,8 @@ #include "matador/sql/query_intermediates.hpp" #include "matador/sql/value.hpp" +#include "matador/utils/result.hpp" + #include namespace matador::sql { @@ -25,6 +27,24 @@ struct entity_query_data { std::unique_ptr 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 - std::optional build(const PrimaryKeyType &pk) { + utils::result build(const PrimaryKeyType &pk) { const auto info = schema_.info(); 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 + utils::result build() { + const auto info = schema_.info(); + 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 - 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(); - 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 - 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(); - 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 @@ -118,7 +129,7 @@ public: if (attr.fetch() == utils::fetch_type::EAGER) { const auto info = schema_.info(); 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(); 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 + 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 +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(); + 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 diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 9f51d29..c23a9b6 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -40,7 +40,7 @@ protected: std::shared_ptr 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 @@ -191,10 +191,10 @@ private: query_on_intermediate on_clause(std::unique_ptr &&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 @@ -244,16 +244,16 @@ class query_into_intermediate : public query_intermediate public: using query_intermediate::query_intermediate; - query_execute_finish values(std::initializer_list values); - query_execute_finish values(std::vector &&values); + query_execute values(std::initializer_list values); + query_execute values(std::vector &&values); template - query_execute_finish values() + query_execute values() { Type obj; return values(std::move(as_placeholder(obj))); } template - 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 columns); - query_execute_finish table(const sql::table &table, const std::vector &columns); + query_execute table(const sql::table &table, std::initializer_list columns); + query_execute table(const sql::table &table, const std::vector &columns); template - query_execute_finish table(const sql::table &table) + query_execute table(const sql::table &table) { return this->table(table, column_definition_generator::generate(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 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 query_execute_where_intermediate where(const Condition &cond) diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index 8c41639..a653930 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -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 - std::optional> find(const PrimaryKeyType &pk) { + utils::result, 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(); if (!info) { - return {}; + return utils::error(session_error::UnknownType); } entity_query_builder eqb(*schema_); auto data = eqb.build(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(); - if (!e) { - return std::nullopt; + auto obj = build_select_query(c, data.release()).template fetch_one(); + + if (!obj) { + return utils::error(session_error::FailedToFindObject); } - return entity{ e.release() }; + return utils::ok(entity{ obj.release() }); + } + + template + utils::result, session_error> find() { + auto c = pool_.acquire(); + if (!c.valid()) { + return utils::error(session_error::NoConnectionAvailable); + } + auto info = schema_->info(); + if (!info) { + return utils::error(session_error::UnknownType); + } + + entity_query_builder eqb(*schema_); + auto data = eqb.build(); + if (!data.is_ok()) { + return utils::error(session_error::FailedToBuildQuery); + } + + return utils::ok(build_select_query(c, data.release()).template fetch_all()); + } + + template + utils::result select() { + auto c = pool_.acquire(); + if (!c.valid()) { + return utils::error(session_error::NoConnectionAvailable); + } + auto info = schema_->info(); + if (!info) { + return utils::error(session_error::UnknownType); + } + + entity_query_builder eqb(*schema_); + auto data = eqb.build(); + if (!data.is_ok()) { + return utils::error(session_error::FailedToBuildQuery); + } + + return utils::ok(build_select_query(c, data.release()).template fetch_all()); } template @@ -79,10 +120,12 @@ public: const class dialect& dialect() const; private: - friend class query_select_finish; + friend class query_select; [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; + query_select build_select_query(connection_ptr &conn, entity_query_data &&data) const; + private: connection_pool &pool_; const class dialect &dialect_; diff --git a/include/matador/utils/result.hpp b/include/matador/utils/result.hpp index e723ec4..0776e61 100644 --- a/include/matador/utils/result.hpp +++ b/include/matador/utils/result.hpp @@ -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; using error_type = error; - 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 &x) = default; + result& operator=(const result &x) = default; + result(result &&x) = default; + result& operator=(result &&x) = default; operator bool() const { return is_ok(); } // NOLINT(*-explicit-constructor) @@ -65,15 +75,33 @@ public: ErrorType err() { return std::get(result_).value(); } constexpr const ValueType* operator->() const { return &value(); } - constexpr ValueType* operator->() { return &value(); } + constexpr ValueType* operator->() { return &std::get(result_).value(); } - template - result and_then(Func f) { + template> + result transform(Func &&f) { if (is_ok()) { - return f(value()); + return result(ok(f(release()))); } - return *this; + return result(error(release_error())); + } + + template::value_type::value_type> + result and_then(Func &&f) { + if (is_ok()) { + return f(release()); + } + + return result(error(release_error())); + } + + template::error_type::value_type> + result or_else(Func &&f) { + if (is_error()) { + return f(err()); + } + + return result(ok(release())); } private: diff --git a/src/sql/query_intermediates.cpp b/src/sql/query_intermediates.cpp index 11a0dd6..bcb46d0 100644 --- a/src/sql/query_intermediates.cpp +++ b/src/sql/query_intermediates.cpp @@ -8,13 +8,13 @@ basic_query_intermediate::basic_query_intermediate(connection &db, const sql::sc : connection_(db) , schema_(schema) {} -query_result query_select_finish::fetch_all() +query_result query_select::fetch_all() { query_compiler compiler(connection_.dialect()); return connection_.fetch(compiler.compile(data_.get())); } -std::optional query_select_finish::fetch_one() +std::optional query_select::fetch_one() { query_compiler compiler(connection_.dialect()); auto result = connection_.fetch(compiler.compile(data_.get())); @@ -26,19 +26,19 @@ std::optional 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_select_finish::fetch() +std::unique_ptr 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 values) +query_execute query_into_intermediate::values(std::initializer_list values) { return this->values(std::vector(values)); } -query_execute_finish query_into_intermediate::values(std::vector &&values) +query_execute query_into_intermediate::values(std::vector &&values) { data_->parts.push_back(std::make_unique(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_execute_finish query_create_intermediate::table(const sql::table &table, std::initializer_list columns) +query_execute query_create_intermediate::table(const sql::table &table, std::initializer_list columns) { return this->table(table, std::vector{columns}); } -query_execute_finish query_create_intermediate::table(const sql::table &table, const std::vector &columns) +query_execute query_create_intermediate::table(const sql::table &table, const std::vector &columns) { data_->parts.push_back(std::make_unique(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_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(table)); return {connection_, schema_, data_}; diff --git a/src/sql/session.cpp b/src/sql/session.cpp index e2e9db8..ce0af11 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -103,4 +103,26 @@ std::unique_ptr session::fetch(const std::string &sql) const return c->fetch(sql); } +query_select session::build_select_query(connection_ptr &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; + } +} } \ No newline at end of file diff --git a/test/EntityQueryBuilderTest.cpp b/test/EntityQueryBuilderTest.cpp index de1998d..71fc1ae 100644 --- a/test/EntityQueryBuilderTest.cpp +++ b/test/EntityQueryBuilderTest.cpp @@ -21,7 +21,7 @@ TEST_CASE("Create sql query data for entity with eager belongs to", "[query][ent auto data = eqb.build(17); - REQUIRE(data.has_value()); + REQUIRE(data.is_ok()); REQUIRE(data->root_table_name == "books"); REQUIRE(data->joins.size() == 1); const std::vector 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(17); - REQUIRE(data.has_value()); + REQUIRE(data.is_ok()); REQUIRE(data->root_table_name == "orders"); REQUIRE(data->joins.size() == 1); const std::vector 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(17); - REQUIRE(data.has_value()); + REQUIRE(data.is_ok()); REQUIRE(data->root_table_name == "ingredients"); REQUIRE(data->joins.size() == 2); const std::vector expected_columns { diff --git a/test/ResultTest.cpp b/test/ResultTest.cpp index 7dcd7a5..43e9f35 100644 --- a/test/ResultTest.cpp +++ b/test/ResultTest.cpp @@ -20,6 +20,14 @@ utils::resultmultiply(int x, int y) { return utils::ok(float(x) * y); } +utils::resultplus(int x, int y) { + return utils::ok(float(x) + y); +} + +utils::resulterror_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<>() } \ No newline at end of file