Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

199 lines
6.3 KiB
C++

// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
#pragma once
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <variant>
#include <hilti/rt/exception.h>
namespace hilti::rt {
namespace result {
/** Represents an error message. */
class Error {
public:
Error(std::string description = "<no description>", std::string context = "")
: _description(std::move(description)), _context(std::move(context)) {}
const auto& description() const { return _description; }
const auto& context() const { return _context; }
operator std::string() const { return _description; }
operator std::string_view() const { return _description; }
private:
std::string _description;
std::string _context;
};
inline std::ostream& operator<<(std::ostream& out, const Error& error) {
out << error.description();
return out;
}
inline bool operator==(const Error& a, const Error& b) { return a.description() == b.description(); }
inline bool operator!=(const Error& a, const Error& b) { return ! (a == b); }
/** Exception indicating that no result is available if though one was requested. */
class NoResult : public RuntimeError {
public:
NoResult(Error err) : RuntimeError(err.description()), _error(std::move(err)) {}
private:
Error _error;
};
/** Exception indicating that no error has been reported if though one was expected to be available. */
class NoError : public RuntimeError {
public:
NoError() : RuntimeError("<no error>") {}
};
} // namespace result
struct Nothing {};
inline bool operator==(const Nothing&, const Nothing&) { return true; }
inline bool operator!=(const Nothing&, const Nothing&) { return false; }
namespace detail::adl {
inline std::string to_string(const Nothing& n, adl::tag /*unused*/) { return "<nothing>"; };
} // namespace detail::adl
/**
* Represents either a successful result from function if it returned one, or
* reflects an error if the function was unsuccessful.
*/
template<typename T>
class Result {
public:
Result() : _value(std::in_place_type_t<result::Error>(), result::Error("<result not initialized>")) {}
/** Creates a successful result from a value. */
Result(const T& t) : _value(std::in_place_type_t<T>(), t) {}
/** Creates a successful result from a value. */
Result(T&& t) : _value(std::in_place_type_t<T>(), std::move(t)) {}
/** Creates an result reflecting an error. */
Result(const result::Error& e) : _value(std::in_place_type_t<result::Error>(), e) {}
/** Creates an result reflecting an error. */
Result(result::Error&& e) : _value(std::in_place_type_t<result::Error>(), std::move(e)) {}
Result(const Result& o) = default;
Result(Result&& o) = default; // NOLINT (hicpp-noexcept-move)
~Result() = default;
/**
* Returns the result's value, assuming it indicates success.
*
* @exception `std::bad_variant_access` if the result reflects an error
* state
*/
const T& value() const { return std::get<T>(_value); }
/**
* Returns the result's value, assuming it indicates success.
*
* @exception `std::bad_variant_access` if the result reflects an error
* state
*/
T& value() { return std::get<T>(_value); }
/**
* Returns the result's value if it indicates success, or throws an
* exception if not. By default, the exception thrown is `result::NoResult`.
*
* @tparam E type of the exception to throw
* @exception exception of type `E` if the result reflects an error state
*/
template<typename E = result::NoResult>
const T& valueOrThrow() const {
if ( ! hasValue() )
throw E(error());
return value();
}
/**
* Returns the result's value if it indicates success, or throws an
* exception if not.
*
* @tparam E type of the exception to throw
* @exception exception of type `E` if the result reflects an error state
*/
template<typename E = result::NoResult>
T& valueOrThrow() {
if ( ! hasValue() )
throw E(error());
return value();
}
/**
* Returns the result's error, assuming it reflect one.
*
* @exception `std::bad_variant_access` if the result doe not reflect an error
* state
*/
const result::Error& error() const { return std::get<result::Error>(_value); }
/**
* Returns the result's error if it indicates failure, or throws an
* exception if not.
*
* @exception `result::NoError` if the result does not reflect an error state
*/
const result::Error& errorOrThrow() const {
if ( hasValue() )
throw result::NoError();
return error();
}
/** Returns true if the result represents a successful return value. */
bool hasValue() const { return std::holds_alternative<T>(_value); }
/** Returns the result's value, assuming it indicates success. */
const T& operator*() const { return value(); }
/** Returns the result's value, assuming it indicates success. */
T& operator*() { return value(); }
/** Returns the result's value, assuming it indicates success. */
const T* operator->() const { return std::get_if<T>(&_value); }
/** Returns the result's value, assuming it indicates success. */
T* operator->() { return std::get_if<T>(&_value); }
/** Returns true if the result represents a successful return value. */
explicit operator bool() const { return hasValue(); }
/** Converts the result to an optional that's set if it represents a successful return value. */
operator std::optional<T>() const { return hasValue() ? std::make_optional(value()) : std::nullopt; }
Result& operator=(const Result& other) = default;
Result& operator=(Result&& other) = default; // NOLINT (hicpp-noexcept-move)
friend bool operator==(const Result& a, const Result& b) {
if ( a.hasValue() != b.hasValue() )
return false;
if ( a.hasValue() )
return a.value() == b.value();
else
return a.error() == b.error();
}
friend bool operator!=(const Result& a, const Result& b) { return ! (a == b); }
private:
std::variant<T, result::Error> _value;
};
/** Similar to `std::make_optional`, construct a result from a value. */
template<typename T>
Result<T> make_result(T&& t) {
return Result<T>(std::forward<T>(t));
}
} // namespace hilti::rt