blob: bb86cf68815580a901256b57ff534830a033256b [file] [log] [blame] [view] [edit]
# Error and StatusChain
The error objects framework consists of two main components:
* `Error` is a base type for actual errors, that code can generate
* `StatusChain` 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:
1. `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.
2. `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.
3. `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.
4. `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().
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 `OkStatus<T>()` object.
## How to add a new error type?
1. The custom error object has to inherit `Error`.
2. It needs to specify public `struct MakeStatusTrait` (see details in
`status/status_chain.h`)
3. It needs to implement a constructor. Ideally it has to excerxise the fully
qualified base class constructor, but it is not necessary. It is recommended
that at least `error_message` setting constructor of the base class is
invoked.
4. It needs to override the destructor.
5. It can override `ToString` to customize printing behaviour.
6. It can overload `WrapTransform` to modify wrapping error during Wrap
operation.
7. It can overload `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.
```C++
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) {
// do something with the base error.
}
}
};
```
## StatusChainOr
StatusChainOr allows you to return either a value or a non-ok status.
Note: you should never convert an `OkStatus` into the StatusChainOr.
## How to return an error
1. If you need to interact with non-libhwsec errors - define MakeStatusTrait
for your custom error to convert external error to a StatusChain object.
2. If you need to return a new error, use
`return MakeStatus<CustomError>(<custom error ctor args>)`.
3. If you need to wrap a previously returned error with a new one
`return MakeStatus<CustomError>(<custom error ctor args>).Wrap(std::move(old_chain))`.
## Customizing MakeStatus
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:
* Within `MakeStatusTrait`, new `StatusChain` should be created with a
`NewStatus`, instead of `MakeStatus`. Using `MakeStatus` will cause an
infinite recursion.
```C++
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>);
}
};
};
```
## Customizing Wrap behaviour
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.
# Recommended coding style
1. When should we use `auto`? Similar to [the style of unique_ptr](https://google.github.io/styleguide/cppguide.html#Type_deduction).
1. Use `auto` when the error is created by MakeStatus.
```C++
if (auto status = MakeStatus<TPM1Error>(0x99); !status.ok()) {
/* Do some error handlings... */
}
```
2. Specify the error type in other cases.
```C++
if (StatusChain<TPM1Error> status = SomeMagicFunction(); !status.ok()) {
/* Do some error handlings... */
}
```
2. Use of `StatusChain` in the `if` expression:
1. Prefer using `RETURN_IF_ERROR` if possible and readable.
```C++
RETURN_IF_ERROR(SomeMagicFunction(), AsStatus<TPMErrpr>("some error"));
```
2. Otherwise prefer explicitly checking the status of the return object.
```C++
if (StatusChain<TPM1Error> status = SomeMagicFunction(); !status.ok()) {
/* Do some error handlings... */
}
```
3. It's acceptable to do implicit bool conversions when the error is declared inside the if expression.
Note, that eventually the implicit conversion might be deprecated.
```C++
if (StatusChain<TPM1Error> status = SomeMagicFunction()) {
/* Do some error handlings... */
}
```
4. We should prevent doing implicit bool conversions when the error isn’t declared in the if expression.
```C++
if (SomeMagicFunction()) {
/* Don’t do this. */
}
```
Please use:
```C++
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? */
}
```