Add an error system, with its function result system that can carry a value and an error at the same time.
This commit is contained in:
parent
70c666f460
commit
c6b3fde6d4
150
include/ksr/errors.h
Normal file
150
include/ksr/errors.h
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jemalloc/jemalloc.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ksr/logging.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of an error code.
|
||||||
|
*/
|
||||||
|
typedef int ksrerrorcode_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of an error.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
ksrerrorcode_t code;
|
||||||
|
char *message;
|
||||||
|
void *data;
|
||||||
|
} ksrerror;
|
||||||
|
|
||||||
|
#define KSRERROR_NOERROR_CODE 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error struct that indicates no error.
|
||||||
|
*/
|
||||||
|
#define ksrerror_none ((ksrerror) { KSRERROR_NOERROR_CODE, NULL, NULL })
|
||||||
|
/**
|
||||||
|
* Create a new error structure with given code, message and custom error data.
|
||||||
|
*/
|
||||||
|
#define ksrerror_new_d(error_code, error_message, error_data) ((ksrerror) { error_code, strdup(error_message), error_data })
|
||||||
|
/**
|
||||||
|
* Create a new error structure with given code and message.
|
||||||
|
*/
|
||||||
|
#define ksrerror_new(error_code, error_message) ksrerror_new_d(error_code, error_message, NULL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw a new error structure with given code, message and custom error data.
|
||||||
|
*/
|
||||||
|
#define ksrerror_throw_d(error_code, error_message, error_data) return ksrerror_new_d(error_code, error_message, error_data)
|
||||||
|
/**
|
||||||
|
* Throw a new error structure with given code and message.
|
||||||
|
*/
|
||||||
|
#define ksrerror_throw(error_code, error_message) return ksrerror_new(error_code, error_message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free an error and its custom data, with the given free function.
|
||||||
|
*/
|
||||||
|
static inline void ksrerror_free_d(ksrerror error, void (*free_data_func)(void*))
|
||||||
|
{
|
||||||
|
if (error.message) free(error.message);
|
||||||
|
if (error.data) free_data_func(error.data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Free an error without custom data.
|
||||||
|
*/
|
||||||
|
static inline void ksrerror_free(ksrerror error)
|
||||||
|
{
|
||||||
|
ksrerror_free_d(error, free);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore an error and free its custom data.
|
||||||
|
*/
|
||||||
|
#define ksrerror_ignore_d(error, free_data_func) ksrerror_free_d(error, free_data_func)
|
||||||
|
/**
|
||||||
|
* Ignore an error without custom data.
|
||||||
|
*/
|
||||||
|
#define ksrerror_ignore(error) ksrerror_free(error)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the structure indicates an error.
|
||||||
|
* Return true if there is no error, false if there is one.
|
||||||
|
*/
|
||||||
|
static inline bool ksrerror_check(ksrerror error)
|
||||||
|
{
|
||||||
|
return error.code == KSRERROR_NOERROR_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate the error, if there is one.
|
||||||
|
*/
|
||||||
|
#define ksrerror_propagate(error) { ksrerror __ksrerror__cache__ = error; if (!ksrerror_check(__ksrerror__cache__)) return __ksrerror__cache__; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Panic! Show error message and exit app.
|
||||||
|
*/
|
||||||
|
#define ksrerror_panic(error) { \
|
||||||
|
ksrerror __ksrerror__cache__ = error; \
|
||||||
|
if (__ksrerror__cache__.code != KSRERROR_NOERROR_CODE) { \
|
||||||
|
char *__ksrerror__error_message__ = malloc(5 + 64 + strlen(__ksrerror__cache__.message) + 1); \
|
||||||
|
sprintf(__ksrerror__error_message__, "code %d: %s", __ksrerror__cache__.code, __ksrerror__cache__.message); \
|
||||||
|
ksrlog_error(__ksrerror__error_message__); exit(__ksrerror__cache__.code); \
|
||||||
|
} }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of a result that can be an error.
|
||||||
|
* This structure can be used as a return type for functions that can encounter an error.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
void *result;
|
||||||
|
ksrerror error;
|
||||||
|
} ksresult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a result of a specific type.
|
||||||
|
*/
|
||||||
|
#define ksresult_of_type(result_type) ksresult
|
||||||
|
/**
|
||||||
|
* Create a new result.
|
||||||
|
*/
|
||||||
|
#define ksresult_new(_result) ((ksresult) { _result, ksrerror_none })
|
||||||
|
/**
|
||||||
|
* Create a result that contains an error.
|
||||||
|
*/
|
||||||
|
#define ksresult_new_error(error) ((ksresult) { NULL, (error) })
|
||||||
|
/**
|
||||||
|
* Throw an error as a result.
|
||||||
|
*/
|
||||||
|
#define ksresult_throw(error) return ksresult_new_error(error)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get a result and put it in the variable called varname.
|
||||||
|
* You must specify the type of the result you want to get.
|
||||||
|
* If there is an error, it is thrown as a result. You cannot use this function if your function does not return a ksresult.
|
||||||
|
*/
|
||||||
|
#define ksresult_get(varname, result_type, _result) NULL; { \
|
||||||
|
ksresult __ksresult_cache__ = (_result); \
|
||||||
|
if (!ksresult_check(__ksresult_cache__)) return __ksresult_cache__; \
|
||||||
|
varname = ((result_type) __ksresult_cache__.result); \
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Try to get a result while ignoring the error if there is one.
|
||||||
|
* You must specify the type of the result you want to get.
|
||||||
|
* Directly return the result data.
|
||||||
|
*/
|
||||||
|
#define ksresult_get_ignore(result_type, _result) ((result_type) (_result).result)
|
||||||
|
/**
|
||||||
|
* Get the error of a result.
|
||||||
|
*/
|
||||||
|
#define ksresult_error(_result) (_result).error
|
||||||
|
/**
|
||||||
|
* Check if there is an error in the given result.
|
||||||
|
*/
|
||||||
|
#define ksresult_check(_result) ksrerror_check(ksresult_error(_result))
|
||||||
|
|
||||||
|
|
@ -35,6 +35,7 @@ test_ksregex = executable('test_ksregex', [ 'tests/ksregex.c' ], dependencies :
|
|||||||
test_ksrstring = executable('test_ksrstring', [ 'tests/ksrstring.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
test_ksrstring = executable('test_ksrstring', [ 'tests/ksrstring.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
||||||
test_ksrfiles = executable('test_ksrfiles', [ 'tests/ksrfiles.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
test_ksrfiles = executable('test_ksrfiles', [ 'tests/ksrfiles.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
||||||
test_ksrlogging = executable('test_ksrlogging', [ 'tests/ksrlogging.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
test_ksrlogging = executable('test_ksrlogging', [ 'tests/ksrlogging.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
||||||
|
test_ksrerrors = executable('test_ksrerrors', [ 'tests/ksrerrors.c' ], dependencies : deps, include_directories : include_dirs, install : false)
|
||||||
|
|
||||||
# tests.
|
# tests.
|
||||||
test('ksrarrays', test_ksrarrays)
|
test('ksrarrays', test_ksrarrays)
|
||||||
@ -44,3 +45,4 @@ test('ksregex', test_ksregex)
|
|||||||
test('ksrfiles', test_ksrfiles)
|
test('ksrfiles', test_ksrfiles)
|
||||||
test('ksrstring', test_ksrstring)
|
test('ksrstring', test_ksrstring)
|
||||||
test('ksrlogging', test_ksrlogging)
|
test('ksrlogging', test_ksrlogging)
|
||||||
|
test('ksrerrors', test_ksrerrors)
|
||||||
|
101
tests/ksrerrors.c
Normal file
101
tests/ksrerrors.c
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#include <jemalloc/jemalloc.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ksr/errors.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
ksrerror err_throwsomething(int var)
|
||||||
|
{
|
||||||
|
if (var == 1)
|
||||||
|
// we consider that if the value of var is one, there is an error.
|
||||||
|
ksrerror_throw(1, "something.");
|
||||||
|
|
||||||
|
return ksrerror_none; // no error happened.
|
||||||
|
}
|
||||||
|
|
||||||
|
ksrerror err_propagation(void)
|
||||||
|
{
|
||||||
|
// calling throwsomething and propagating error if there is one.
|
||||||
|
ksrerror_propagate(err_throwsomething(1));
|
||||||
|
|
||||||
|
return ksrerror_none; // no error happened.
|
||||||
|
}
|
||||||
|
|
||||||
|
ksresult_of_type(char*) res_returnok(void)
|
||||||
|
{
|
||||||
|
// creating a new result and returning it.
|
||||||
|
return ksresult_new("testing something.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ksresult_of_type(char*) res_returnerr(int var)
|
||||||
|
{
|
||||||
|
if (var != 10)
|
||||||
|
// we consider that if the value of var is ten, there is an error.
|
||||||
|
ksresult_throw(ksrerror_new(6, "test error."));
|
||||||
|
|
||||||
|
// creating a new result and returning it.
|
||||||
|
return ksresult_new("another test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ksresult_of_type(int*) res_propagate_err(void)
|
||||||
|
{
|
||||||
|
int *returnval = malloc(sizeof(int));
|
||||||
|
(*returnval) = 5;
|
||||||
|
|
||||||
|
char *test = ksresult_get(test, char*, res_returnerr(*returnval));
|
||||||
|
printf("propagation failed? %s\n", test);
|
||||||
|
|
||||||
|
return ksresult_new(returnval);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
/* * Testing simple errors * */
|
||||||
|
|
||||||
|
|
||||||
|
// testing that ksrerror_none is not an error.
|
||||||
|
assert(ksrerror_check(ksrerror_none));
|
||||||
|
|
||||||
|
// testing that an error is correctly thrown.
|
||||||
|
assert(ksrerror_check(err_throwsomething(0)));
|
||||||
|
assert(!ksrerror_check(err_throwsomething(1)));
|
||||||
|
ksrerror_ignore(err_throwsomething(1));
|
||||||
|
|
||||||
|
// testing that propagation works.
|
||||||
|
assert(!ksrerror_check(err_propagation()));
|
||||||
|
assert(err_propagation().code == 1);
|
||||||
|
assert(strcmp(err_propagation().message, "something.") == 0);
|
||||||
|
|
||||||
|
// testing panic when no error happen.
|
||||||
|
ksrerror_panic(err_throwsomething(0));
|
||||||
|
|
||||||
|
|
||||||
|
/* * Testing ksresults * */
|
||||||
|
|
||||||
|
|
||||||
|
// test1: no error, valid return value.
|
||||||
|
ksresult res1 = res_returnok();
|
||||||
|
assert(ksresult_check(res1)); // checking that there is no error.
|
||||||
|
assert(strcmp(ksresult_get_ignore(char*, res1), "testing something.") == 0);
|
||||||
|
|
||||||
|
|
||||||
|
// test2: error.
|
||||||
|
ksresult res2 = res_returnerr(2);
|
||||||
|
assert(!ksresult_check(res2)); // checking that there is an error.
|
||||||
|
// checking that the error has been correctly passed.
|
||||||
|
assert(ksresult_error(res2).code == 6);
|
||||||
|
assert(strcmp(ksresult_error(res2).message, "test error.") == 0);
|
||||||
|
ksrerror_free(res2.error); // free the thrown error.
|
||||||
|
|
||||||
|
|
||||||
|
// test3: error propagation.
|
||||||
|
ksresult res3 = res_propagate_err();
|
||||||
|
assert(!ksresult_check(res3)); // checking that there is an error.
|
||||||
|
// checking that the error has been correctly passed.
|
||||||
|
assert(ksresult_error(res3).code == 6);
|
||||||
|
assert(strcmp(ksresult_error(res3).message, "test error.") == 0);
|
||||||
|
ksrerror_free(res3.error); // free the thrown error.
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user