-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revisited description in README.md (#8)
- Loading branch information
Showing
1 changed file
with
107 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,131 @@ | ||
The built-in integral types are used as a low-level result storage that should provide efficient access and manipulation via bit operations. | ||
The storage type can be specified to achieve portability and efficiency for various platforms. | ||
# The minimalist error handling framework | ||
|
||
## Motivation | ||
|
||
The framework makes an attempt to provide resource efficient ways to define error types and facilities to analyze them. | ||
In a resource constrained environment it would be convenient to have facilities to represent errors and ability to combine several of them | ||
without overhead of memory allocation/deallocation. | ||
The framework makes an attempt to provide resource efficient ways to define | ||
error types and facilities to analyze them. In a resource-constrained | ||
environment it would be convenient to have facilities to represent errors and | ||
ability to combine several of them without overhead of memory | ||
allocation/deallocation and access indirection. | ||
|
||
The type is supposed to be project defined and concrete result values can be declared closer to the usage. That will enable clients to use unified methods to analyze and forward | ||
errors up to the call stack while providing some encapsulation. | ||
The built-in integral types are used as a low-level result storage that should | ||
provide efficient access and manipulation via bit operations. The storage type | ||
can be specified to achieve portability and efficiency for various platforms. | ||
|
||
The result type is supposed to be project defined and concrete result values | ||
can be declared closer to the usage. That will enable clients to use unified | ||
methods to analyze and forward errors up to the call stack while providing some | ||
encapsulation. | ||
|
||
## Introduction | ||
## Build | ||
|
||
The framework allows clients to specify result code along with a set of error categories that can be used to describe the possible error in a more precise way. The underlying type can be specified | ||
as a template parameter. | ||
The library requires C++17. | ||
Makefile in the project root can be used to build and execute unit-tests. | ||
|
||
In the example the type to store the result code is defined using 8-bit integral type. | ||
The error type is supposed to have the following structure: | ||
To build and execute tests the following make target can be used: | ||
|
||
2 bits for domain, 2 bits for sub-domain. 4 bits for error code | ||
```sh | ||
make test | ||
``` | ||
|
||
And to build the example: | ||
|
||
```sh | ||
make example | ||
``` | ||
|
||
The executables containing unit-tests and examples can then be found in the | ||
`bin` subfolder. | ||
|
||
## Getting started | ||
|
||
As the library is header-only, to be used in the project it is sufficient to add | ||
the `include` folder to the project include path. | ||
|
||
The project-specific layout for result type should be defined. | ||
It can have several layers of categories pointing at project submodules where | ||
error can occur. In the result type declaration they are identified via | ||
types that work as names for the layer. | ||
|
||
In the example two levels of categories are defined each having 2 bits of size | ||
(capable of representing 4 values each). | ||
```c++ | ||
namespace application | ||
{ | ||
//project-wide definition of error types | ||
MAKE_RESULT_CATEGORY(Category, 2); | ||
MAKE_RESULT_CATEGORY(SubCategory, 2); | ||
``` | ||
//definition of some categories and their widths in bits | ||
//(here 2 bits allowing maximum of 4 categories on each level) | ||
MAKE_RESULT_CATEGORY(Domain, 2); | ||
MAKE_RESULT_CATEGORY(SubDomain, 2); | ||
The result type declaration should contain name, specification of the | ||
underlying type and a list of categories to incorporate. Here the definition | ||
uses 8-bit integral type as a low-level storage and has 2 bits for category, 2 | ||
bits for subcategory and 4 remaining bits for an error code. | ||
The result type is defined with the name `Result`. | ||
// definition of the result type (contains two categories | ||
// defined above and based on 8 bit integer | ||
// the rest 4 bits are used for result code) | ||
MAKE_RESULT_TYPE(TestResult, uint8_t, Domain, SubDomain); | ||
```c++ | ||
MAKE_RESULT_TYPE(Result, uint8_t, Category, SubCategory); | ||
``` | ||
The aggregate result can be defined to combine several single results. | ||
In the example it is capable of containing 32/8 bit = 4 result objects. | ||
|
||
//definition of the error domains and subdomains | ||
```c++ | ||
MAKE_AGGREGATE_RESULT_TYPE(AggregateResult, uint32_t, Result); | ||
``` | ||
// here the error can occure in the application layer | ||
// domain having value 3 is defined | ||
constexpr auto Application = Domain{3}; | ||
Objects of different categories can be created to identify errors that can | ||
occur in different program modules. They can be placed in corresponding | ||
namespaces. | ||
// two subdomains defined | ||
constexpr auto Server = SubDomain{1}; | ||
constexpr auto Client = SubDomain{2}; | ||
```c++ | ||
constexpr auto Ui = Category{1}; | ||
constexpr auto DataModel = SubCategory{1}; | ||
//error codes for concrete errors | ||
constexpr uint8_t rpcErrorCode = 1; | ||
constexpr uint8_t backendAccessErrorCode = 2; | ||
constexpr auto Backend = Category{2}; | ||
constexpr auto Db = SubCategory{1}; | ||
constexpr auto Rpc = SubCategory{2}; | ||
``` | ||
|
||
The error definitions can be placed globally, in the corresponding modules | ||
or nested in the class declarations. | ||
|
||
```c++ | ||
constexpr auto dataRetrievalError = Result::make(Ui, DataModel, 1); | ||
|
||
constexpr auto backendAccessErrorCode = 1; | ||
constexpr auto backendAccessError | ||
= Result::make(Backend, DataAccess, backendAccessErrorCode); | ||
|
||
class BackendClient { | ||
public: | ||
static constexpr Result rpcError = Result::make(Backend, Rpc, 1); | ||
static constexpr Result wrongQuery = Result::make(Backend, Db, 1); | ||
// ... | ||
}; | ||
``` | ||
The client code can analyze returned result objects. | ||
//local alias for the positive result | ||
constexpr auto Ok = TestResult::success; | ||
```c++ | ||
Result const result = ...; | ||
if (respp::is_success(result)) { | ||
} else if (respp::get_category<SubCategory>(result) == Rpc) { | ||
} else if (respp::get_code(result) == backendAccessErrorCode) {} | ||
``` | ||
|
||
Several errors can be combined (nested) in aggregate result: | ||
|
||
//error definitions consisting of categories (Domain, SubDomain) and error codes | ||
constexpr auto rpcClientError = TestResult::make(Application, Client, rpcErrorCode); | ||
constexpr auto backendAccessError = TestResult::make(Application, Client, backendAccessErrorCode); | ||
```c++ | ||
AggregateResult result {backendAccessError, wrongQuery}; | ||
return result << dataRetrievalError; | ||
``` | ||
} // namespace application | ||
The aggregated errors can be iterated to traverse 'error stack'. | ||
```c++ | ||
AggregateResult const result = ...; | ||
for (auto const it : result.iterate_errors()) { | ||
if (it == application::backend_access::BackendClient::rpcError) { | ||
} | ||
} | ||
``` | ||
|
||
For more complete examples please refer to `examples/example.cpp` | ||
and unit-tests `test/result_test.cpp`. |