-
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.
- Loading branch information
Showing
1 changed file
with
105 additions
and
38 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 way 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 | ||
## Getting started | ||
|
||
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. | ||
To use the library the one can clone the sources and add `include` folder into | ||
project include paths. | ||
|
||
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: | ||
|
||
2 bits for domain, 2 bits for sub-domain. 4 bits for error code | ||
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. | ||
|
||
Then the project specific error code type layout can be defined. | ||
The error type in the foloowing example uses 8-bit integral type as low-level | ||
storage and has 2 bits for category, 2 bits for sub-category and 4 bits for error code. | ||
|
||
```c++ | ||
namespace application | ||
{ | ||
//project-wide definition of error types | ||
|
||
//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); | ||
// project-wide definitions of error types | ||
|
||
// definition of error categories and their widths in bits | ||
// (here 2 bits allowing maximum of 4 categories on each level) | ||
MAKE_RESULT_CATEGORY(Category, 2); | ||
MAKE_RESULT_CATEGORY(SubCategory, 2); | ||
// 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); | ||
MAKE_RESULT_TYPE(Result, uint8_t, Category, SubCategory); | ||
// and aggregate result capable of containing 32/8 = 4 errors | ||
MAKE_AGGREGATE_RESULT_TYPE(AggregateResult, uint32_t, Result); | ||
// local alias for the positive result can be defined | ||
constexpr auto Ok = Result::success; | ||
// application error categories | ||
constexpr auto Ui = Category{1}; | ||
constexpr auto Backend = Category{2}; | ||
} //namespace application | ||
``` | ||
After the structure is defined categories specified: | ||
```c++ | ||
namespace backend_access | ||
{ | ||
// subcategories defined for DB, Rpc and more common errors | ||
constexpr auto Db = SubCategory{1}; | ||
constexpr auto Rpc = SubCategory{2}; | ||
constexpr auto DataAccess = SubCategory{3}; | ||
// .... | ||
} // namespace backend_access | ||
``` | ||
|
||
The error definitions can then be defined globally, in the corresponding modules, or nested in the class declarations: | ||
```c++ | ||
constexpr auto backendAccessError | ||
= Result::make(Backend, DataAccess, 1); | ||
// sample RPC client for backend access | ||
class BackendClient { | ||
public: | ||
// localy defined error code | ||
static constexpr Result rpcError = Result::make(Backend, Rpc, 1); | ||
static constexpr Result wrongQuery = Result::make(Backend, Db, 1); | ||
// .... | ||
// method return single result | ||
Result executeRemoteQuery() { | ||
return rpcError; // RPC failed | ||
} | ||
}; | ||
``` | ||
//definition of the error domains and subdomains | ||
The client code can then analyze, modify and forward errors: | ||
```c++ | ||
// method returns aggregated result | ||
AggregateResult retrieveRemoteData() { | ||
backend_access::BackendClient client; | ||
Result const result = client.executeRemoteQuery(); | ||
// handling error | ||
if (!respp::is_success(result)) { | ||
if (respp::get_category<SubCategory>(result) == Rpc) { | ||
// in case of RPC error we return more common error and nest | ||
// the error from lower layer | ||
return AggregateResult{backendAccessError, result}; | ||
} else { | ||
// ignore other types of error | ||
// aggregated result can be constructed from single result | ||
return Ok; | ||
} | ||
} | ||
return result; | ||
} | ||
``` | ||
|
||
// here the error can occure in the application layer | ||
// domain having value 3 is defined | ||
constexpr auto Application = Domain{3}; | ||
The aggregated errors can then be iterated | ||
|
||
// two subdomains defined | ||
constexpr auto Server = SubDomain{1}; | ||
constexpr auto Client = SubDomain{2}; | ||
```c++ | ||
auto const result = retrieveRemoteData(); | ||
for (auto const it : result.iterate_errors()) { | ||
if (it == application::backend_access::BackendClient::rpcError) { | ||
// some logic or reporting | ||
} | ||
} | ||
``` | ||
For more complete example please refer to `examples/example.cpp` and unit-tests. | ||
|
||
//error codes for concrete errors | ||
constexpr uint8_t rpcErrorCode = 1; | ||
constexpr uint8_t backendAccessErrorCode = 2; | ||
## Build | ||
|
||
//local alias for the positive result | ||
constexpr auto Ok = TestResult::success; | ||
The library is header-only and requires C++17. | ||
|
||
//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); | ||
Makefile in the project root can be used to build and execute unit-tests. | ||
|
||
} // namespace application | ||
To build and execute tests `make test` target can be used (`make` without parameters to build only). | ||
|
||
``` | ||
The executable containing unit-test can then be found in the `bin` subfolder. | ||
|