Skip to content

Commit

Permalink
expected::Result fixes
Browse files Browse the repository at this point in the history
 - assumeError bug
 - seamless construction from derived types

Signed-off-by: Mikhail Boldyrev <miboldyrev@gmail.com>
  • Loading branch information
MBoldyrev committed Jan 20, 2020
1 parent 13bce5b commit 880b368
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 6 deletions.
63 changes: 57 additions & 6 deletions libs/common/result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ namespace iroha {
*/
template <typename V, typename E>
class Result : ResultBase, public boost::variant<Value<V>, Error<E>> {
template <typename OV, typename OE>
friend class Result;

using variant_type = boost::variant<Value<V>, Error<E>>;
using variant_type::variant_type; // inherit constructors

Expand All @@ -97,6 +100,24 @@ namespace iroha {
using ValueInnerType = V;
using ErrorInnerType = E;

Result() = default;

template <typename OV, typename OE>
Result(Result<OV, OE> r)
: Result(visit_in_place(std::move(r),
[](Value<OV> &v) -> Result<V, E> {
return ValueType{std::move(v.value)};
},
[](Value<OV> &&v) -> Result<V, E> {
return ValueType{std::move(v.value)};
},
[](Error<OE> &e) -> Result<V, E> {
return ErrorType{std::move(e.error)};
},
[](Error<OE> &&e) -> Result<V, E> {
return ErrorType{std::move(e.error)};
})) {}

/**
* match is a function which allows working with result's underlying
* types, you must provide 2 functions to cover success and failure cases.
Expand Down Expand Up @@ -186,6 +207,17 @@ namespace iroha {
void *,
ValueInnerType>;

/// @return value if present, otherwise throw ResultException
template <typename ReturnType = const AssumeValueHelper &>
std::enable_if_t<not std::is_void<ValueInnerType>::value, ReturnType>
assumeValue() const & {
const auto *val = boost::get<ValueType>(this);
if (val != nullptr) {
return val->value;
}
throw ResultException("Value expected, but got an Error.");
}

/// @return value if present, otherwise throw ResultException
template <typename ReturnType = AssumeValueHelper &>
std::enable_if_t<not std::is_void<ValueInnerType>::value, ReturnType>
Expand Down Expand Up @@ -213,13 +245,24 @@ namespace iroha {
void *,
ErrorInnerType>;

/// @return error if present, otherwise throw ResultException
template <typename ReturnType = const AssumeErrorHelper &>
std::enable_if_t<not std::is_void<ErrorInnerType>::value, ReturnType>
assumeError() const & {
const auto *err = boost::get<ErrorType>(this);
if (err != nullptr) {
return err->error;
}
throw ResultException("Error expected, but got a Value.");
}

/// @return error if present, otherwise throw ResultException
template <typename ReturnType = AssumeErrorHelper &>
std::enable_if_t<not std::is_void<ErrorInnerType>::value, ReturnType>
assumeError() & {
auto val = boost::get<ErrorType>(this);
if (val != nullptr) {
return val->value;
auto err = boost::get<ErrorType>(this);
if (err != nullptr) {
return err->error;
}
throw ResultException("Error expected, but got a Value.");
}
Expand All @@ -228,9 +271,9 @@ namespace iroha {
template <typename ReturnType = AssumeErrorHelper &&>
std::enable_if_t<not std::is_void<ErrorInnerType>::value, ReturnType>
assumeError() && {
auto val = boost::get<ErrorType>(this);
if (val != nullptr) {
return std::move(val->value);
auto err = boost::get<ErrorType>(this);
if (err != nullptr) {
return std::move(err->error);
}
throw ResultException("Error expected, but got a Value.");
}
Expand All @@ -257,11 +300,19 @@ namespace iroha {
}

// Factory methods for avoiding type specification
inline Value<void> makeValue() {
return Value<void>{};
}

template <typename T>
Value<T> makeValue(T &&value) {
return Value<T>{std::forward<T>(value)};
}

inline Error<void> makeError() {
return Error<void>{};
}

template <typename E>
Error<E> makeError(E &&error) {
return Error<E>{std::forward<E>(error)};
Expand Down
123 changes: 123 additions & 0 deletions test/module/libs/common/result_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "common/result.hpp"

#include <gtest/gtest.h>
#include "framework/result_gtest_checkers.hpp"

using namespace iroha::expected;

Expand Down Expand Up @@ -242,3 +243,125 @@ TEST(ResultTest, MapErrorBlank) {
.match([](Value<int> v) { ASSERT_EQ(5, v.value); },
makeFailCase<Error<int>>(kErrorCaseMessage));
}

using NonCopyable = std::unique_ptr<int>;

/**
* @given a result with a non-copyable object value reference
* @when assumeValue and assumeError are called
* @then assumeValue does not throw and returns same value object reference
* @and assumeError does throw a ResultException
*/
TEST(ResultTest, AssumeValueReference) {
NonCopyable value = std::make_unique<int>(18);
Result<NonCopyable &, int> result(value);
IROHA_ASSERT_RESULT_VALUE(result);
EXPECT_NO_THROW(EXPECT_EQ(result.assumeValue(), value));
EXPECT_THROW(result.assumeError(), ResultException);
}

/**
* @given a result with a non-copyable object error reference
* @when assumeError and assumeValue are called
* @then assumeError does not throw and returns same error object reference
* @and assumeValue does throw a ResultException
*/
TEST(ResultTest, AssumeErrorReference) {
NonCopyable value = std::make_unique<int>(18);
Result<int, NonCopyable &> result(value);
IROHA_ASSERT_RESULT_ERROR(result);
EXPECT_NO_THROW(EXPECT_EQ(result.assumeError(), value));
EXPECT_THROW(result.assumeValue(), ResultException);
}

/**
* @given a result with a non-copyable object value
* @when assumeValue and assumeError are called
* @then assumeValue does not throw and returns same value object
* @and assumeError does throw a ResultException
*/
TEST(ResultTest, AssumeValue) {
auto value = new NonCopyable::element_type(18);
Result<NonCopyable, int> result(value);
IROHA_ASSERT_RESULT_VALUE(result);
NonCopyable extracted_value;
EXPECT_NO_THROW(extracted_value = std::move(result).assumeValue());
EXPECT_EQ(extracted_value.get(), value);
int extracted_error = 23;
EXPECT_THROW(extracted_error = std::move(result).assumeError(),
ResultException);
EXPECT_EQ(extracted_error, 23);
}

/**
* @given a result with a non-copyable object error
* @when assumeError and assumeValue are called
* @then assumeError does not throw and returns same error object
* @and assumeValue does throw a ResultException
*/
TEST(ResultTest, AssumeError) {
auto error = new NonCopyable::element_type(18);
Result<int, NonCopyable> result(error);
IROHA_ASSERT_RESULT_ERROR(result);
NonCopyable extracted_error;
EXPECT_NO_THROW(extracted_error = std::move(result).assumeError());
EXPECT_EQ(extracted_error.get(), error);
int extracted_value = 23;
EXPECT_THROW(extracted_value = std::move(result).assumeValue(),
ResultException);
EXPECT_EQ(extracted_value, 23);
}

struct A {};

struct ConstructibleFromA {
ConstructibleFromA(A &&) {}
};

struct NotConstructibleFromA {};

/**
* @given an object a of type A
* @when construct a Result<ConstructibleFromA, NotConstructibleFromA> from a
* @then result contains value constructed from a
*/
TEST(ResultTest, AutoValueConstruction) {
IROHA_ASSERT_RESULT_VALUE(
(Result<ConstructibleFromA, NotConstructibleFromA>{A{}}));
}

/**
* @given an object a of type A
* @when construct a Result<NotConstructibleFromA, ConstructibleFromA> from a
* @then result contains error constructed from a
*/
TEST(ResultTest, AutoErrorConstruction) {
IROHA_ASSERT_RESULT_ERROR(
(Result<NotConstructibleFromA, ConstructibleFromA>{A{}}));
}

/**
* @given a result object of type Result<A, NotConstructibleFromA> containing a
* value
* @when construct a Result<ConstructibleFromA, NotConstructibleFromA> from it
* @then result contains value constructed from initial result
*/
TEST(ResultTest, AutoValueConstructionFromResult) {
Result<A, NotConstructibleFromA> result(A{});
IROHA_ASSERT_RESULT_VALUE(result);
IROHA_ASSERT_RESULT_VALUE(
(Result<ConstructibleFromA, NotConstructibleFromA>{result}));
}

/**
* @given a result object of type Result<NotConstructibleFromA, A> containing an
* error
* @when construct a Result<NotConstructibleFromA, ConstructibleFromA> from it
* @then result contains error constructed from initial result
*/
TEST(ResultTest, AutoErrorConstructionFromResult) {
Result<NotConstructibleFromA, A> result(A{});
IROHA_ASSERT_RESULT_ERROR(result);
IROHA_ASSERT_RESULT_ERROR(
(Result<NotConstructibleFromA, ConstructibleFromA>{result}));
}

0 comments on commit 880b368

Please sign in to comment.