The error objects framework consists of two main components:
Error
is a base type for actual errors, that code can generateStatusChain
is a holder object for those Errors.Error objects are default, copy and move constructible, and also provide an explicit constructor from a std::string
. They also have three customization points:
std::string ToString()
const - by default returns the message the Error
was constructed with (or std::string() in the case of default constructor). Can be overridden to provide a custom behaviour.void WrapTransfor(StatusChain<BaseErrorType>::const_iterator_range range)
- a method, that is invoked upon the error being wrapped. The argument of it is an iterable view of the stack the error intends to wrap. Can be overloaded to provide custom behaviour. Defaulted to a no-op.struct MakeStatusTrait
subtype. This trait allows to customize creation behaviour of the Error. Two pre-defined types are DefaultMakeStatus
and ForbidMakeStatus
, for the default behaviour and creation prohibition.BaseErrorType
allows to specify the base error type of the stack. Only errors with the exactle same BaseErrorType
can be in one chain. Defaulted to Error
.StatusChain
object is effectively a unique pointer to an Error
. It inherits StackablePointer
, and thus manages a stack of Error
objects. The access to the head of the stack can be done through get/operator->/operator*/error(), and via casting pair Is<T>
/Cast<T>
. The access to the elements of the stack can be done either through Find<T>
method which returns a pointer to the object of appropriate type in the stack, or nullptr if not found. Other way it to iterate over the stack with for (auto error_obj_ptr : stack.range())
or for (const auto error_obj_ptr : stack.const_range())
for the const view, or by using iterators manually, as in for (auto it = stack.range().begin(); it != stack.range().eng(); ++it)
error_obj_ptr
is a raw pointer, managed by the stack. it
is an iterator, where error_obj_ptr == *it
, and it->something
is equivalent to error_obj_ptr->something
.
The success is marked with either OkStatus<T>()
(preferred) object or nullptr
.
Error
.struct MakeStatusTrait
(see details in status/status_chain.h
)error_message
setting constructor of the base class is invoked.ToString
to customize printing behaviour.WrapTransform
to modify wrapping error during Wrap operation.BaseErrorType
to specify which type of iterable it will produce from the chain. Only errors with the same |BaseErrorType| can be stored in one chain.class CustomError : public Error { public: using MakeStatusTrait = hwsec::DefaultMakeStatus<CustomError>; explicit CustomError(const std::string& error_message) : Error(error_message) {} ~CustomErroj() override = default; std::string ToString() const override { return "AWESOME PREFIX: " + Error::ToString(); } void WrapTransform(StatusChain<Error>::const_iterator_range range) override { for (const auto error_obj_ptr : range) { if (Error::Is<const SomeOtherError>(error)) { const SomeOtherError* cast = Error::Cast<const SomeOtherError>(error); // do smthng with `cast` } } } };
StatusChainOr allows you to return either a value or a non-ok status.
Note: you should never convert an OkStatus
into the StatusChainOr.
return MakeStatus<CustomError>(<custom error ctor args>)
.return MakeStatus<CustomError>(<custom error ctor args>).Wrap(std::move(old_chain))
.To define a custom MakeStatus behaviour, the Error object needs to defaine MakeStatusTrait substruct, which define operator(). The return type is not enforced, but it should be StatusChain<ErrorType>
unless you have intermediate stub objects. Any intermediate stub object should implement Wrap to return StatusChain<CustomError>
type - Wrap should convert stub back to status. An important note:
MakeStatusTrait
, new StatusChain
should be created with a NewStatus
, instead of MakeStatus
. Using MakeStatus
will cause an infinite recursion.class CustomError : public Error { public: using MakeStatusTrait { struct Stub { StatusChain<CustomError> Wrap(StatusChain<Error> error) { return NewStatus(<ctor args>) } } auto operator()(<some args>) { return Stub(); } auto operator()(<some args>) { return NewStatus(<ctor args>); } }; };
To add additional actions upon Wrap, the object can override WrapTransform function, that receives an iterable range of the wrapped stack (excluding current error). Client code can iterate over the range and use the info from it. It can not modify the stack.
auto
? Similar to the style of unique_ptr.auto
when the error is created by MakeStatus.if (auto status = MakeStatus<TPM1Error>(0x99); !status.ok()) { /* Do some error handlings... */ }
if (StatusChain<TPM1Error> status = SomeMagicFunction(); !status.ok()) { /* Do some error handlings... */ }
StatusChain
in the if
expression:RETURN_IF_ERROR
if possible and readable.RETURN_IF_ERROR(SomeMagicFunction(), AsStatus<TPMErrpr>("some error"));
if (StatusChain<TPM1Error> status = SomeMagicFunction(); !status.ok()) { /* Do some error handlings... */ }
if (StatusChain<TPM1Error> status = SomeMagicFunction()) { /* Do some error handlings... */ }
if (SomeMagicFunction()) { /* Don’t do this. */ }Please use:
if (!SomeMagicFunction().ok()) { /* This it better */ /* But should think about why we need to drop the extra error information in this case. */ /* For example: Should we log more messages in this case? */ }