# Library Architecture ## Philosophy of Use This library has 6 guiding principles: * Manually checking every possible return code for every possible meaning of that return code is tedious and prone to miss unpredicted failure cases * Functions should return rich descriptive error contexts, not values * Uncaught errors should cause program termination with a stacktrace * Dynamic memory allocation is the source of many errors and should be avoided if possible * Manipulating the call stack directly is error prone and dangerous * Declaring, capturing, and reacting to errors should be intuitive and no more difficult than managing return codes ## Lifecycle of an error in the AKError library TL;DR - `akerr_ErrorContext` objects are filled with error context information and bubbled up through nested control structures until they are handled or reach the top level, where an unhandled error halts program termination with a stack trace 1. At the point where an error occurs, an `akerr_ErrorContext` object is initialized and populated with information regarding the failure 2. The akerr_ErrorContext is returned from the scope where the error was detected 3. The akerr_ErrorContext enters a control structure provided by the AKError library through a series of macros that examine `akerr_ErrorContext` objects as they pass through 4. The control structure checks to see if the `akerr_ErrorContext` has an error set, and if so, if there are any handlers in the current control structure that can handle it 5. If the current control structure can handle the `akerr_ErrorContext`, it does so 6. If the current control structure can not handle the `akerr_ErrorContext`, then the current control structure's cleanup code (if any) is executed, and the `akerr_ErrorContext` object is passed out of the current control structure to the parent control structure 7. Steps 2-6 are repeated through as many control structures as are necessary to reach the first level of the control structure 8. When the first level of the control structure is reached, if the `akerr_ErrorContext` has an error set in it, then the stack trace information in the `akerr_ErrorContext` object is used to print a stack trace using the configured logging function, and program termination is halted ## What is in an Error Context The Error Context object is a simple object which contains a few things: * A numeric error code * The name of the file in which the error occurred * The name of the function in which the error occurred * The line number in the file at which the error occurred * A character buffer containing a message about the error in question The structure also contains housekeeping information for the library which are of no specific interest to the user. See [include/akerror.tmpl.h](../include/akerror.tmpl.h) for more details. ## What are the control structures The library is structured around a series of macros that construct `switch` statements that perform logic against an `akerr_ErrorContext` which exists in the current scope and has been initialized. These macros must be assembled in a specific order to produce a syntactically correct `switch` statement which performs correct operations against the `akerr_ErrorContext` to attempt operations, detect failures, perform cleanup operations, handle errors, and then exit a given scope in a success or failure state. ## Functions and Return Codes This library can catch errors from any function or expression that returns an integer value, or from functions that return `akerr_ErrorContext *`. Any function which uses the `PREPARE_ERROR` macro should have a return type of `akerr_ErrorContext *`. The macros within this library, when they detect an unhandled error, will attempt to pass up the unhandled error to the context of the previous function in the call stack. This allows for errors to propagate up through the call stack in the same way as exceptions. (For example, if you use traditional C error handling in a call stack of `a() -> b() -> c()`, and `c()` fails because it runs out of memory, `b()` will likely detect that error and return some error to `a()`, but it may or may not return the context of what failed and why. With this, you get that context all the way up in `a()` without knowing anything about `c()`.