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:
Madeorsk 2023-02-12 17:38:57 +01:00
parent 70c666f460
commit c6b3fde6d4
3 changed files with 253 additions and 0 deletions

150
include/ksr/errors.h Normal file
View 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))

View File

@ -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_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_ksrerrors = executable('test_ksrerrors', [ 'tests/ksrerrors.c' ], dependencies : deps, include_directories : include_dirs, install : false)
# tests.
test('ksrarrays', test_ksrarrays)
@ -44,3 +45,4 @@ test('ksregex', test_ksregex)
test('ksrfiles', test_ksrfiles)
test('ksrstring', test_ksrstring)
test('ksrlogging', test_ksrlogging)
test('ksrerrors', test_ksrerrors)

101
tests/ksrerrors.c Normal file
View 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;
}