Skip to content

Commit dd91f29

Browse files
committed
BResult improvements
This is a draft commit trying to make some improvements to the `BResult` class to make it more usable. Adds a `util::Result` class with the following improvements (and implements `BResult` in terms of it so existing code doesn't have to change): - Supports returning error strings and error information at the same time. This functionality is needed by the `LoadChainstate` function in #25308 or any function that can fail in multiple ways which need to be handled differently. And it means Result class is compatible with rust-style error handling and pattern matching, unlike BResult. - Makes Result class provide same operators and methods as std::optional, so it doesn't require calling less familiar `HasRes`/`GetObj`/`ReleaseObj` methods, and so error reporting functionality can be easily added or dropped from existing code by switching between util::Result and std::optional. - Supports `Result<void>` return values, so it is possible to return error reporting information from functions in a uniform way even if they don't have other return values. - Supports `Result<bilingual_str>` return values. Drops ambiguous and potentially misleading `BResult` constructor that treats any bilingual string as an error, and any other type as a non-error. Adds `util::Error` constructor so errors have to be explicitly constructed and not any `bilingual_str` will be turned into one. - Puts `src/util/` code in the `util::` namespace so naming reflects code organization and it is obvious where the class is coming from. Drops "B" from name because it is undocumented what it stands for (bilingual?) - Adds unit tests.
1 parent 948f5ba commit dd91f29

File tree

5 files changed

+225
-26
lines changed

5 files changed

+225
-26
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ libbitcoin_util_a_SOURCES = \
679679
util/moneystr.cpp \
680680
util/rbf.cpp \
681681
util/readwritefile.cpp \
682+
util/result.cpp \
682683
util/settings.cpp \
683684
util/thread.cpp \
684685
util/threadnames.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ BITCOIN_TESTS =\
118118
test/raii_event_tests.cpp \
119119
test/random_tests.cpp \
120120
test/rest_tests.cpp \
121+
test/result_tests.cpp \
121122
test/reverselock_tests.cpp \
122123
test/rpc_tests.cpp \
123124
test/sanity_tests.cpp \

src/test/result_tests.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <util/result.h>
6+
7+
#include <boost/test/unit_test.hpp>
8+
9+
BOOST_AUTO_TEST_SUITE(result_tests)
10+
11+
util::Result<void> VoidSuccessFn()
12+
{
13+
return {};
14+
}
15+
16+
util::Result<void> VoidFailFn()
17+
{
18+
return {util::Error{}, Untranslated("void fail")};
19+
}
20+
21+
util::Result<int> IntSuccessFn(int ret)
22+
{
23+
return {ret};
24+
}
25+
26+
util::Result<int> IntFailFn()
27+
{
28+
return {util::Error{}, Untranslated("int fail")};
29+
}
30+
31+
enum FnStatus { SUCCESS, ERR1, ERR2 };
32+
33+
util::Result<FnStatus> StatusSuccessFn(FnStatus ret)
34+
{
35+
return {ret};
36+
}
37+
38+
util::Result<FnStatus> StatusFailFn(FnStatus ret)
39+
{
40+
return {util::Error{}, Untranslated("status fail"), ret};
41+
}
42+
43+
util::Result<int> ChainedFailFn(FnStatus arg, int ret)
44+
{
45+
return {util::ErrorChain{}, Untranslated("chained fail"), StatusFailFn(arg), ret};
46+
}
47+
48+
template<typename T>
49+
void ExpectSuccess(const util::Result<T>& result)
50+
{
51+
BOOST_CHECK(result);
52+
}
53+
54+
template<typename T>
55+
void ExpectFail(const util::Result<T>& result, bilingual_str str)
56+
{
57+
BOOST_CHECK(!result);
58+
BOOST_CHECK_EQUAL(ErrorDescription(result).original, str.original);
59+
BOOST_CHECK_EQUAL(ErrorDescription(result).translated, str.translated);
60+
}
61+
62+
template<typename T, typename... Args>
63+
void ExpectValue(const util::Result<T>& result, bool has_value, Args&&... args)
64+
{
65+
BOOST_CHECK_EQUAL(result.value(), T{std::forward<Args>(args)...});
66+
BOOST_CHECK_EQUAL(result.has_value(), has_value);
67+
BOOST_CHECK_EQUAL(&result.value(), &*result);
68+
}
69+
70+
template<typename T, typename... Args>
71+
void ExpectSuccessValue(const util::Result<T>& result, Args&&... args)
72+
{
73+
ExpectSuccess(result);
74+
ExpectValue(result, true, std::forward<Args>(args)...);
75+
}
76+
77+
template<typename T, typename... Args>
78+
void ExpectFailValue(const util::Result<T>& result, bilingual_str str, Args&&... args)
79+
{
80+
ExpectFail(result, str);
81+
ExpectValue(result, false, std::forward<Args>(args)...);
82+
}
83+
84+
BOOST_AUTO_TEST_CASE(check_returned)
85+
{
86+
ExpectSuccess(VoidSuccessFn());
87+
ExpectFail(VoidFailFn(), Untranslated("void fail"));
88+
ExpectSuccessValue(IntSuccessFn(5), 5);
89+
ExpectFail(IntFailFn(), Untranslated("int fail"));
90+
ExpectSuccessValue(StatusSuccessFn(SUCCESS), SUCCESS);
91+
ExpectFailValue(StatusFailFn(ERR2), Untranslated("status fail"), ERR2);
92+
ExpectFailValue(ChainedFailFn(ERR1, 5), Untranslated("status fail, chained fail"), 5);
93+
}
94+
95+
BOOST_AUTO_TEST_CASE(check_dereference_operators)
96+
{
97+
util::Result<std::pair<int, std::string>> mutable_result;
98+
const auto& const_result{mutable_result};
99+
mutable_result.value() = {1, "23"};
100+
BOOST_CHECK_EQUAL(mutable_result->first, 1);
101+
BOOST_CHECK_EQUAL(const_result->second, "23");
102+
(*mutable_result).first = 5;
103+
BOOST_CHECK_EQUAL((*const_result).first, 5);
104+
}
105+
106+
BOOST_AUTO_TEST_SUITE_END()

src/util/result.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <util/result.h>
6+
#include <util/string.h>
7+
8+
namespace util {
9+
bilingual_str ErrorDescription(const Result<void>& result)
10+
{
11+
return Join(result.GetErrors(), Untranslated(", "));
12+
}
13+
} // namespace util
14+

src/util/result.h

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,119 @@
77

88
#include <util/translation.h>
99

10-
#include <variant>
10+
#include <optional>
1111

12-
/*
13-
* 'BResult' is a generic class useful for wrapping a return object
14-
* (in case of success) or propagating the error cause.
15-
*/
16-
template<class T>
17-
class BResult {
18-
private:
19-
std::variant<bilingual_str, T> m_variant;
12+
namespace util {
2013

21-
public:
22-
BResult() : m_variant{Untranslated("")} {}
23-
BResult(T obj) : m_variant{std::move(obj)} {}
24-
BResult(bilingual_str error) : m_variant{std::move(error)} {}
14+
//! Function result type intended for high-level functions that return error and
15+
//! warning strings in addition to normal result types.
16+
//!
17+
//! The Result<T> class is meant to be a drop-in replacement for
18+
//! std::optional<T> except it has additional methods to return error and
19+
//! warning strings for error reporting. Also, unlike std::optional, in order to
20+
//! support error handling in cases where callees need to pass additional
21+
//! information about failures back to callers, Result<T> objects can always be
22+
//! dereferenced regardless of the function's success or failure.
23+
//!
24+
//! This class is not intended to be used by low-level functions that do not
25+
//! return error or warning strings. These functions should use plain
26+
//! std::optional or std::variant types instead.
27+
//!
28+
//! See unit tests in result_tests.cpp for example usages.
29+
template<typename T>
30+
class Result;
31+
32+
//! Tag types for result constructors.
33+
struct Error{};
34+
struct ErrorChain{};
2535

26-
/* Whether the function succeeded or not */
27-
bool HasRes() const { return std::holds_alternative<T>(m_variant); }
36+
//! Result<void> specialization. Only returns errors and warnings, no values.
37+
template<>
38+
class Result<void>
39+
{
40+
protected:
41+
std::vector<bilingual_str> m_errors;
42+
std::vector<bilingual_str> m_warnings;
2843

29-
/* In case of success, the result object */
30-
const T& GetObj() const {
31-
assert(HasRes());
32-
return std::get<T>(m_variant);
44+
public:
45+
//! Success case constructor.
46+
Result() = default;
47+
48+
//! Error case constructor for a single error.
49+
Result(Error, bilingual_str error)
50+
{
51+
m_errors.emplace_back(std::move(error));
3352
}
34-
T ReleaseObj()
53+
54+
//! Error case constructor for a chained error.
55+
Result(ErrorChain, bilingual_str error, Result<void>&& previous) : m_errors{std::move(previous.m_errors)}, m_warnings{std::move(previous.m_warnings)}
3556
{
36-
assert(HasRes());
37-
return std::move(std::get<T>(m_variant));
57+
m_errors.emplace_back(std::move(error));
3858
}
3959

40-
/* In case of failure, the error cause */
41-
const bilingual_str& GetError() const {
42-
assert(!HasRes());
43-
return std::get<bilingual_str>(m_variant);
60+
//! Success check.
61+
operator bool() const { return m_errors.empty(); }
62+
63+
//! Error retrieval.
64+
const std::vector<bilingual_str>& GetErrors() const { return m_errors; }
65+
std::tuple<const std::vector<bilingual_str>&, const std::vector<bilingual_str>&> GetErrorsAndWarnings() const { return {m_errors, m_warnings}; }
66+
};
67+
68+
template<typename T>
69+
class Result : public Result<void>
70+
{
71+
protected:
72+
T m_result;
73+
74+
public:
75+
//! Constructors that forward to the base class and pass additional arguments to m_result.
76+
template<typename... Args>
77+
Result(Args&&... args) : m_result{std::forward<Args>(args)...} {}
78+
template<typename Str, typename...Args>
79+
Result(Error, Str&& str, Args&&... args) : Result<void>{Error{}, std::forward<Str>(str)}, m_result{std::forward<Args>(args)...} {};
80+
template<typename Str, typename Prev, typename...Args>
81+
Result(ErrorChain, Str&& str, Prev&& prev, Args&&... args) : Result<void>{ErrorChain{}, std::forward<Str>(str), std::forward<Prev>(prev)}, m_result{std::forward<Args>(args)...} {};
82+
83+
//! std::optional methods, so Result<T> can be easily swapped for
84+
//! std::optional<T> to add error reporting to existing code or remove it if
85+
//! it is no longer needed.
86+
bool has_value() const { return m_errors.empty(); }
87+
const T& value() const { return m_result; }
88+
T& value() { return m_result; }
89+
template<typename U> T value_or(const U& default_value) const
90+
{
91+
return has_value() ? value() : default_value;
4492
}
93+
const T* operator->() const { return &value(); }
94+
const T& operator*() const { return value(); }
95+
T* operator->() { return &value(); }
96+
T& operator*() { return value(); }
97+
};
98+
99+
//! Helper method to retrieve a simple error string from Result<T> or
100+
//! Result<void>.
101+
bilingual_str ErrorDescription(const Result<void>& result);
102+
} // namespace util
45103

104+
/**
105+
* Backwards-compatible interface for util::Result class. New code should prefer
106+
* util::Result class which supports returning error information along with
107+
* result information and supports returing `void` and `bilingual_str` results.
108+
*/
109+
template<class T>
110+
class BResult {
111+
private:
112+
util::Result<std::optional<T>> m_result;
113+
114+
public:
115+
BResult() : m_result{util::Error{}, Untranslated("")} {};
116+
BResult(const T& value) : m_result{value} {}
117+
BResult(T&& value) : m_result{std::move(value)} {}
118+
BResult(const bilingual_str& error) : m_result{util::Error{}, error} {}
119+
bool HasRes() const { return m_result.has_value(); }
120+
const T& GetObj() const { assert(HasRes()); return *m_result.value(); }
121+
T ReleaseObj() { assert(HasRes()); return std::move(*m_result.value()); }
122+
const bilingual_str& GetError() const { assert(!HasRes()); return m_result.GetErrors().back(); }
46123
explicit operator bool() const { return HasRes(); }
47124
};
48125

0 commit comments

Comments
 (0)