Skip to content

Commit 3615b98

Browse files
committed
refactor: establish canonical error ownership
1 parent 75132f1 commit 3615b98

2 files changed

Lines changed: 118 additions & 1 deletion

File tree

‎include/vix/error/Result.hpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ namespace vix::error
2929
* @class Result
3030
* @brief Represents either a success value of type T or an Error.
3131
*
32-
* Result<T> is the preferred explicit error-handling abstraction in Vix.
32+
* Result<T> is the canonical Vix3 explicit error-handling abstraction.
33+
* New Vix3 APIs use this Result<T> with vix::error::Error rather than
34+
* historical Vix2 compatibility result types.
3335
* It allows APIs to return either:
3436
* - a success value of type T
3537
* - a failure represented by vix::error::Error

‎test/error_test.cpp‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include <cstdlib>
1717
#include <iostream>
1818
#include <string>
19+
#include <type_traits>
20+
#include <utility>
21+
#include <variant>
1922

2023
#include <vix/error/Error.hpp>
2124
#include <vix/error/ErrorCategory.hpp>
@@ -158,6 +161,113 @@ namespace
158161
"Stored error message should match.");
159162
}
160163

164+
void test_result_rejects_success_error()
165+
{
166+
bool rejected = false;
167+
try
168+
{
169+
[[maybe_unused]] vix::error::Result<int> result(vix::error::Error{});
170+
}
171+
catch (const std::invalid_argument &)
172+
{
173+
rejected = true;
174+
}
175+
176+
assert_true(rejected,
177+
"Result<int> should reject an Error that represents success.");
178+
}
179+
180+
void test_result_invalid_access()
181+
{
182+
vix::error::Result<int> success(42);
183+
bool value_error_threw = false;
184+
try
185+
{
186+
[[maybe_unused]] const auto &error = success.error();
187+
}
188+
catch (const std::bad_variant_access &)
189+
{
190+
value_error_threw = true;
191+
}
192+
assert_true(value_error_threw,
193+
"error() on a successful Result should throw bad_variant_access.");
194+
195+
vix::error::Result<int> failure(vix::error::Error(
196+
vix::error::ErrorCode::InvalidArgument,
197+
vix::error::ErrorCategory::validation(),
198+
"invalid value"));
199+
bool error_value_threw = false;
200+
try
201+
{
202+
[[maybe_unused]] const auto &value = failure.value();
203+
}
204+
catch (const std::bad_variant_access &)
205+
{
206+
error_value_threw = true;
207+
}
208+
assert_true(error_value_threw,
209+
"value() on a failed Result should throw bad_variant_access.");
210+
}
211+
212+
void test_result_rvalue_access()
213+
{
214+
vix::error::Result<std::string> success(std::string("moved value"));
215+
const auto value = std::move(success).value();
216+
assert_true(value == "moved value",
217+
"rvalue value() should move the stored value out.");
218+
219+
vix::error::Result<int> failure(vix::error::Error(
220+
vix::error::ErrorCode::IoError,
221+
vix::error::ErrorCategory::io(),
222+
"moved error"));
223+
const auto error = std::move(failure).error();
224+
assert_true(error.message() == "moved error",
225+
"rvalue error() should move the stored error out.");
226+
}
227+
228+
void test_result_map_and_then()
229+
{
230+
using vix::error::Error;
231+
using vix::error::ErrorCategory;
232+
using vix::error::ErrorCode;
233+
using vix::error::Result;
234+
235+
const Result<int> success(21);
236+
const auto mapped = success.map([](int value) { return value * 2; });
237+
assert_true(mapped.ok() && mapped.value() == 42,
238+
"map should transform a successful value.");
239+
240+
const Result<int> failed(Error(
241+
ErrorCode::InvalidArgument,
242+
ErrorCategory::validation(),
243+
"invalid input"));
244+
const auto mapped_failure = failed.map([](int value) { return value * 2; });
245+
assert_true(mapped_failure.has_error() &&
246+
mapped_failure.error().message() == "invalid input",
247+
"map should propagate a failure unchanged.");
248+
249+
const auto chained = success.and_then([](int value) {
250+
return Result<std::string>(std::to_string(value));
251+
});
252+
assert_true(chained.ok() && chained.value() == "21",
253+
"and_then should return the operation result for success.");
254+
255+
const auto chained_failure = failed.and_then([](int value) {
256+
return Result<std::string>(std::to_string(value));
257+
});
258+
assert_true(chained_failure.has_error() &&
259+
chained_failure.error().message() == "invalid input",
260+
"and_then should propagate a failure unchanged.");
261+
}
262+
263+
void test_result_supported_type_contract()
264+
{
265+
static_assert(std::is_same_v<vix::error::Result<int>::value_type, int>);
266+
static_assert(std::is_same_v<vix::error::Result<int>::error_type,
267+
vix::error::Error>);
268+
static_assert(!std::is_default_constructible_v<vix::error::Result<int>>);
269+
}
270+
161271
void test_exception_wraps_error()
162272
{
163273
using vix::error::Error;
@@ -191,6 +301,11 @@ int main()
191301
test_error_equality();
192302
test_result_success();
193303
test_result_failure();
304+
test_result_rejects_success_error();
305+
test_result_invalid_access();
306+
test_result_rvalue_access();
307+
test_result_map_and_then();
308+
test_result_supported_type_contract();
194309
test_exception_wraps_error();
195310

196311
std::cout << "[PASS] vix_error_test\n";

0 commit comments

Comments
 (0)