Pure formatting, no behaviour change. PKG landed .clang-format (Google base, 4-space indent, 100 cols); this brings util/ and the test harness into conformance so `clang-format --dry-run -Werror` is clean. Build and all six test binaries unchanged and green. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
161 lines
5.6 KiB
C++
161 lines
5.6 KiB
C++
// vdm/util/result.hpp — the return-based error channel for the transfer path.
|
|
//
|
|
// Errors are returned, never thrown, on anything that runs during a download
|
|
// (AGENT-CORE brief). Result<T> is a thin wrapper over std::expected<T, ErrorInfo>
|
|
// so we keep a stable spelling and a few helpers; the monadic surface forwards to
|
|
// std::expected.
|
|
//
|
|
// This header compiles standalone.
|
|
|
|
#ifndef VDM_UTIL_RESULT_HPP
|
|
#define VDM_UTIL_RESULT_HPP
|
|
|
|
#include <expected>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "vdm/util/error.hpp"
|
|
|
|
namespace vdm {
|
|
|
|
// Alias for the "failure" side, so call sites read `return Err{Error::timeout, "..."}`.
|
|
using Err = ErrorInfo;
|
|
|
|
template <class T>
|
|
class [[nodiscard]] Result {
|
|
using storage = std::expected<T, ErrorInfo>;
|
|
storage exp_;
|
|
|
|
public:
|
|
using value_type = T;
|
|
using error_type = ErrorInfo;
|
|
|
|
// Success construction: implicit from a T (or anything convertible to T).
|
|
template <class U = T,
|
|
class = std::enable_if_t<std::is_constructible_v<T, U &&> &&
|
|
!std::is_same_v<std::remove_cvref_t<U>, Result>>>
|
|
Result(U &&value) : exp_(std::in_place, std::forward<U>(value)) {}
|
|
|
|
Result()
|
|
requires std::is_default_constructible_v<T>
|
|
: exp_(std::in_place) {}
|
|
|
|
// Failure construction: implicit from an ErrorInfo/Err.
|
|
Result(ErrorInfo error) : exp_(std::unexpected(std::move(error))) {}
|
|
Result(Error code) : exp_(std::unexpected(ErrorInfo(code))) {}
|
|
|
|
[[nodiscard]] bool has_value() const noexcept { return exp_.has_value(); }
|
|
explicit operator bool() const noexcept { return exp_.has_value(); }
|
|
|
|
T &value() & { return exp_.value(); }
|
|
const T &value() const & { return exp_.value(); }
|
|
T &&value() && { return std::move(exp_).value(); }
|
|
|
|
T *operator->() noexcept { return &*exp_; }
|
|
const T *operator->() const noexcept { return &*exp_; }
|
|
T &operator*() & noexcept { return *exp_; }
|
|
const T &operator*() const & noexcept { return *exp_; }
|
|
T &&operator*() && noexcept { return *std::move(exp_); }
|
|
|
|
const ErrorInfo &error() const & { return exp_.error(); }
|
|
ErrorInfo &&error() && { return std::move(exp_).error(); }
|
|
|
|
[[nodiscard]] Error code() const noexcept {
|
|
return exp_.has_value() ? Error::ok : exp_.error().code;
|
|
}
|
|
|
|
template <class U>
|
|
T value_or(U &&fallback) const & {
|
|
return exp_.value_or(std::forward<U>(fallback));
|
|
}
|
|
|
|
// Monadic forwarding — see std::expected. `and_then` chains Result-returning
|
|
// callables; `transform` maps the value; `transform_error` rewrites the failure.
|
|
template <class F>
|
|
auto and_then(F &&f) & {
|
|
return exp_.and_then(std::forward<F>(f));
|
|
}
|
|
template <class F>
|
|
auto and_then(F &&f) const & {
|
|
return exp_.and_then(std::forward<F>(f));
|
|
}
|
|
template <class F>
|
|
auto and_then(F &&f) && {
|
|
return std::move(exp_).and_then(std::forward<F>(f));
|
|
}
|
|
|
|
template <class F>
|
|
auto transform(F &&f) & {
|
|
return exp_.transform(std::forward<F>(f));
|
|
}
|
|
template <class F>
|
|
auto transform(F &&f) const & {
|
|
return exp_.transform(std::forward<F>(f));
|
|
}
|
|
template <class F>
|
|
auto transform(F &&f) && {
|
|
return std::move(exp_).transform(std::forward<F>(f));
|
|
}
|
|
|
|
template <class F>
|
|
auto transform_error(F &&f) const & {
|
|
return exp_.transform_error(std::forward<F>(f));
|
|
}
|
|
};
|
|
|
|
// Result<void> — success carries nothing; failure still carries ErrorInfo.
|
|
template <>
|
|
class [[nodiscard]] Result<void> {
|
|
std::expected<void, ErrorInfo> exp_;
|
|
|
|
public:
|
|
using value_type = void;
|
|
using error_type = ErrorInfo;
|
|
|
|
Result() : exp_() {}
|
|
Result(ErrorInfo error) : exp_(std::unexpected(std::move(error))) {}
|
|
Result(Error code) : exp_(std::unexpected(ErrorInfo(code))) {}
|
|
|
|
[[nodiscard]] bool has_value() const noexcept { return exp_.has_value(); }
|
|
explicit operator bool() const noexcept { return exp_.has_value(); }
|
|
void value() const { exp_.value(); }
|
|
|
|
const ErrorInfo &error() const & { return exp_.error(); }
|
|
ErrorInfo &&error() && { return std::move(exp_).error(); }
|
|
|
|
[[nodiscard]] Error code() const noexcept {
|
|
return exp_.has_value() ? Error::ok : exp_.error().code;
|
|
}
|
|
};
|
|
|
|
// Explicit success sentinel for Result<void> returns that reads better than `return {}`.
|
|
inline Result<void> ok() {
|
|
return {};
|
|
}
|
|
|
|
} // namespace vdm
|
|
|
|
#define VDM_DETAIL_CAT_(a, b) a##b
|
|
#define VDM_DETAIL_CAT(a, b) VDM_DETAIL_CAT_(a, b)
|
|
|
|
// VDM_TRY(expr): evaluate a Result-returning expression; on failure, return its error
|
|
// from the enclosing function (which must itself return a Result<...>). On success the
|
|
// macro yields nothing — use VDM_TRY_ASSIGN to bind the value.
|
|
#define VDM_TRY(expr) \
|
|
do { \
|
|
auto _vdm_r = (expr); \
|
|
if (!_vdm_r.has_value()) \
|
|
return ::vdm::ErrorInfo(std::move(_vdm_r).error()); \
|
|
} while (0)
|
|
|
|
// VDM_TRY_ASSIGN(decl, expr): bind `decl` to the value of a successful Result, else
|
|
// return its error. Usage: VDM_TRY_ASSIGN(auto n, read_some());
|
|
#define VDM_TRY_ASSIGN(decl, expr) \
|
|
auto VDM_DETAIL_CAT(_vdm_tmp_, __LINE__) = (expr); \
|
|
if (!VDM_DETAIL_CAT(_vdm_tmp_, __LINE__).has_value()) \
|
|
return ::vdm::ErrorInfo(std::move(VDM_DETAIL_CAT(_vdm_tmp_, __LINE__)).error()); \
|
|
decl = *std::move(VDM_DETAIL_CAT(_vdm_tmp_, __LINE__))
|
|
|
|
#endif // VDM_UTIL_RESULT_HPP
|