[![crates.io](https://img.shields.io/crates/v/simple_detailed_error.svg)](https://crates.io/crates/simple_detailed_error) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JorgeRicoVivas/simple_detailed_error/rust.yml)](https://github.com/JorgeRicoVivas/simple_detailed_error/actions) [![docs.rs](https://img.shields.io/docs.rs/simple_detailed_error)](https://docs.rs/simple_detailed_error/latest/simple_detailed_error/) [![GitHub License](https://img.shields.io/github/license/JorgeRicoVivas/simple_detailed_error)](https://github.com/JorgeRicoVivas/simple_detailed_error?tab=CC0-1.0-1-ov-file) > *You are reading the documentation for simple_detailed_error version 1.0.0* This crate helps you creating errors by giving you the [SimpleErrorDetail] trait where you give text indicating why an error happens and how to solve it, while still using a pattern that easily allows you to tell the user information about said error, such as what happened, why, how, where, how to solve it and its causes.
# Guided and deep example with parsing errors of a scripting language Say we are creating a scripting language inside rust where we receive a script like ```if missing_variable > 0 { return missing_function(missing_variable); }```, this script has two errors: The variable ***missing_variable*** doesn't exists, and the function ***missing_function*** doesn't exist either, in this situation, we would like to show the user an error message like this:
- Error: Couldn't compile code.
- Has: 2 explained causes.
- Causes:
    - Cause nº 1 -
    - At: if missing_variable > 0
    - Error: Variable missing_variable doesn't exists.
    - Solution: Declare it before using it, like this:
                         let missing_variable = your value

    - Cause nº 2 -
    - At: return missing_function(missing_variable);
    - Error: Function missing_function doesn't exists.
    - Solution: Implement an missing_function function, like this:
                         fn missing_function(...) { ...your code here... }

### Defining structs that explain errors For now, let's declare an enum that would represent all the errors we want (They can be different structs, but to make it comfortable, we are going to use three variants), for example: ``` rust #[derive(Debug)] enum CompilationError<'code_input>{ MissingVariable { variable_name : &'code_input str }, // This represents when a non-existing // variable is referenced MissingFunction { function_name : &'code_input str }, // This represents when a non-declared // function tries to get called RootCompilationError // This isn't really an error, is just one representation for saying // 'hey, there are errors in your compilation' } ``` Now let's just implement [SimpleErrorDetail] on this enum, this makes it so we have to implement a function where we return a [SimpleErrorExplanation] where we can give an explanation and a solution for the error using the functions [SimpleErrorExplanation::solution] and [SimpleErrorExplanation::explanation] (Note: You are not forced to give neither the solution nor the explanation, but is is highly advised, as they should help your users): ``` rust use simple_detailed_error::{SimpleErrorExplanation, SimpleErrorDetail}; impl <'code_input> SimpleErrorDetail for CompilationError<'code_input>{ fn explain_error(&self) -> SimpleErrorExplanation { match self{ CompilationError::MissingVariable{ variable_name } => { SimpleErrorExplanation::new() .explanation(format!("Variable {variable_name} doesn't exists.")) .solution(format!("Declare it before using it, like this:\nlet {variable_name} = *your value*")) } CompilationError::MissingFunction{ function_name } => { SimpleErrorExplanation::new() .explanation(format!("Function {function_name} doesn't exists.")) .solution(format!("Implement an is_odd function, like this:\nfn {function_name}(...) {{ ...your code here... }}")) } CompilationError::RootCompilationError => { SimpleErrorExplanation::new().explanation("Couldn't compile code.") } } } } #[derive(Debug)] enum CompilationError<'code_input>{ MissingVariable {variable_name : &'code_input str}, MissingFunction {function_name : &'code_input str}, RootCompilationError } ```
### Creating error values and displaying them Perfect! With this, our enum representing our errors now can use functions like [SimpleErrorDetail::to_parsing_error], which turns our variant into a struct of [SimpleError] containing said variant and using is as a representation of an error whose explanation and solutions are those said when we implemented [SimpleErrorDetail::explain_error]. The [SimpleError] struct is one that holds information about an error, such as why it happened, how to solve it, or where it happened, it can also hold other [SimpleError]s inside, this represents an error being caused by another error, or even by multiple errors, for example, we can add an error using [SimpleError::add_cause] or [SimpleError::with_cause]. For now, we are going to create a variant of `CompilationError::RootCompilationError` which will hold our errors, and then we are going to stack it with the missing variable and the missing function errors: - Creating the missing variable error: This is simply constructing a `CompilationError::MissingVariable` variant, where the variable name is just a reference to where it says 'missing_variable' in the original input, and since this is a parsing error, we can also use the [SimpleError::at] to indicate a bigger string where the error is happening, we will use it to reference 'if missing_variable > 0'.

...Wait, we are using the `at` function, but that's implemented for [SimpleError], why is it working then? Well, the trait [SimpleErrorDetail] implements plenty of functions of [SimpleError], this is so you can use your struct (In this case `CompilationError`) as you were using a value of type [SimpleError].

- Creating the missing function error: This isn't very different from our previous case, we just constructing a `CompilationError::MissingFunction`, as for the function `at`, this time we will reference 'return missing_function(missing_variable);'.

- Creating the Error root: This is something you'll perhaps never do in a real project, but we are going to use a base error, in this case `CompilationError::RootCompilationError` where we will stack the errors using the [SimpleError::with_cause] function, stacking the missing variable and missing function errors, although this is just for showing it's functionality, you should just stack real causes. Once done, we can just can print our [SimpleError] and it will result in the error stack shown earlier. ``` rust use simple_detailed_error::{SimpleErrorExplanation, SimpleErrorDetail}; let code_to_compile = "if missing_variable > 0 { return missing_function(missing_variable); }"; let missing_variable_error = CompilationError::MissingVariable {variable_name: &code_to_compile[3..19] } .at(&code_to_compile[0..23]); let missing_function_error = CompilationError::MissingFunction {function_name: &code_to_compile[33..49] } .at(&code_to_compile[26..68]); let errors_stacker = CompilationError::RootCompilationError .with_cause(missing_variable_error).with_cause(missing_function_error); assert_eq!(format!("{errors_stacker}"), "Error: Couldn't compile code.\nHas: 2 explained causes.\nCauses: \n - Cause nº 1 -\n - At: if missing_variable > 0\n - Error: Variable missing_variable doesn't exists.\n - Solution: Declare it before using it, like this:\n let missing_variable = *your value*\n \n - Cause nº 2 -\n - At: return missing_function(missing_variable);\n - Error: Function missing_function doesn't exists.\n - Solution: Implement an is_odd function, like this:\n fn missing_function(...) { ...your code here... }"); impl <'code_input> SimpleErrorDetail for CompilationError<'code_input>{ fn explain_error(&self) -> SimpleErrorExplanation { match self{ CompilationError::MissingVariable{ variable_name } => { SimpleErrorExplanation::new() .explanation(format!("Variable {variable_name} doesn't exists.")) .solution(format!("Declare it before using it, like this:\nlet {variable_name} = *your value*")) } CompilationError::MissingFunction{ function_name } => { SimpleErrorExplanation::new() .explanation(format!("Function {function_name} doesn't exists.")) .solution(format!("Implement an is_odd function, like this:\nfn {function_name}(...) {{ ...your code here... }}")) } CompilationError::RootCompilationError => { SimpleErrorExplanation::new().explanation("Couldn't compile code.") } } } } #[derive(Debug)] enum CompilationError<'code_input>{ MissingVariable {variable_name : &'code_input str}, MissingFunction {function_name : &'code_input str}, RootCompilationError } ``` ### Feature 'colorization': Adding color and emphasis on errors That was quite alright! But... the output could benefit from some colorization, what if the result looked more like this? - Error: Couldn't compile code.
- Has: 2 explained causes.
- Causes:
    - Cause nº 1 -
    
- At: if **missing_variable** > 0
    - Error: Variable **missing_variable** doesn't exists.
    - Solution: Declare it before using it, like this:
                         let missing_variable = *your value*

    - Cause nº 2 -
    
- At: return **missing_function**(missing_variable);
    - Error: Function **missing_function** doesn't exists.
    - Solution: Declare it before using it, like this:
                         fn missing_function (...) { ...*your code here*... }
By dimming irrelevant parts and making the parts where the errors happen to be easier to see might direct the attention of the user to the error, this makes them to read less in plenty of times while they still all the relevant information. - Colorizing explanations and solutions: With the [colored] crate you can colorize the explanations and solutions, for example, in `Variable {variable_name} doesn't exist` it would be great if we could turn the variable name into a bold and red name, taking the user's attention directly to it, like 'Variable **missing variable** doesn't exist'.

For this, we can just replace `.explanation(format!("Variable {variable_name} doesn't exists."))` for `.explanation(format!("Variable {} doesn't exists.", variable_name.red().bold()))`.

- Colorizing the input: The [string_colorization::colorize] takes an &str and then applies some colors and stylizations to other &str of it that we tell, this means that if we had a &str that is part of the &str taken as input, then we could colorize it!
For example, in out missing variable error, we used [SimpleError::at], where the substring referenced is 'if missing_variable > 0', thing is, our missing variable variant has another substring whose value is 'missing_variable', this means that if we used [SimpleErrorExplanation::colorization_marker] like this `SimpleErrorExplanation::new() .colorization_marker(variable_name, foreground::Red + style::Bold)`, then 'if missing_variable > 0' would look like 'if **missing_variable** > 0'.

The down-side of this is that your struct or enum must have references to the original source, although this is often the case for many situations, like parsing values as in this situation.

We can also use [SimpleErrorExplanation::whole_input_colorization] to colorize everything written inside `at`, for example, `SimpleErrorExplanation::new() .complete_input_colorization(foreground::Blue + style::Italic + style::Dimmed)` will make it so `if missing_variable > 0` looks blue, italic and dimmed, telling the user it is not important.

But... now 'if missing_variable > 0' looks like if **missing_variable ** > 0, not like if **missing_variable ** > 0, why is 'missing_variable' also dimmed? This is because the complete_input_colorization set it to dim, while the colorization_marker didn't override this, to avoid this, we can use [string_colorization::style::Clear] to remove all the stylization from complete_input_colorization, this makes it so `SimpleErrorExplanation::new() .colorization_marker(variable_name, style::Clear + foreground::Red + style::Bold) .complete_input_colorization(foreground::Blue + style::Italic + style::Dimmed)` while turn 'if missing_variable > 0' into the desired if **missing_variable ** > 0. ``` rust use colored::Colorize; use string_colorization::{foreground, style}; use simple_detailed_error::{SimpleErrorDetail, SimpleErrorExplanation}; impl <'code_input> SimpleErrorDetail for CompilationError<'code_input>{ fn explain_error(&self) -> SimpleErrorExplanation { match self{ CompilationError::MissingVariable{ variable_name } => { SimpleErrorExplanation::new() .colorization_marker(variable_name, style::Clear + foreground::Red + style::Bold) .whole_input_colorization(foreground::Blue + style::Italic + style::Dimmed) .explanation(format!("Variable {} doesn't exists.", variable_name.red().bold())) .solution(format!("Declare it before using it, like this:\nlet {} = {}", variable_name.green(), "*your value*".italic())) } CompilationError::MissingFunction{ function_name } => { SimpleErrorExplanation::new() .colorization_marker(function_name, style::Clear + foreground::Red + style::Bold) .whole_input_colorization(foreground::Blue + style::Italic + style::Dimmed) .explanation(format!("Function {} doesn't exists.", function_name.red().bold())) .solution(format!("Implement an {function_name} function, like this:\nfn {}(...) {{ ...{}... }}", function_name.green(), "*your code here*".italic() )) } CompilationError::RootCompilationError => { SimpleErrorExplanation::new().explanation("Couldn't compile code.") } } } } #[derive(Debug)] enum CompilationError<'code_input>{ MissingVariable {variable_name : &'code_input str}, MissingFunction {function_name : &'code_input str}, RootCompilationError, } ``` Great! Now everything is finished! This is the resulting code: ``` rust use colored::Colorize; use string_colorization::{foreground, style}; use simple_detailed_error::{SimpleErrorExplanation, SimpleErrorDetail}; colored::control::set_override(true); // This forces the colorization to be applied, this should // not appear in your code, is written here to force it // for testing purposes to show you this code is correct. let code_to_compile = "if missing_variable > 0 { return missing_function(missing_variable); }"; let missing_variable_error = CompilationError::MissingVariable {variable_name: &code_to_compile[3..19] } .at(&code_to_compile[0..23]); let missing_function_error = CompilationError::MissingFunction {function_name: &code_to_compile[33..49] } .at(&code_to_compile[26..68]); let errors_stacker = CompilationError::RootCompilationError .with_cause(missing_variable_error).with_cause(missing_function_error); assert_eq!(format!("{errors_stacker}"), "Error: Couldn't compile code.\nHas: 2 explained causes.\nCauses: \n - Cause nº 1 -\n - At: \u{1b}[34m\u{1b}[3m\u{1b}[2mif \u{1b}[0m\u{1b}[34m\u{1b}[3m\u{1b}[0m\u{1b}[34m\u{1b}[0m\u{1b}[31m\u{1b}[3m\u{1b}[2m\u{1b}[1mmissing_variable\u{1b}[0m\u{1b}[31m\u{1b}[3m\u{1b}[2m\u{1b}[0m\u{1b}[31m\u{1b}[3m\u{1b}[0m\u{1b}[31m\u{1b}[0m\u{1b}[34m\u{1b}[3m\u{1b}[2m > 0\u{1b}[0m\u{1b}[34m\u{1b}[3m\u{1b}[0m\u{1b}[34m\u{1b}[0m\n - Error: Variable \u{1b}[1;31mmissing_variable\u{1b}[0m doesn't exists.\n - Solution: Declare it before using it, like this:\n let \u{1b}[32mmissing_variable\u{1b}[0m = \u{1b}[3m*your value*\u{1b}[0m\n \n - Cause nº 2 -\n - At: \u{1b}[34m\u{1b}[3m\u{1b}[2mreturn \u{1b}[0m\u{1b}[34m\u{1b}[3m\u{1b}[0m\u{1b}[34m\u{1b}[0m\u{1b}[31m\u{1b}[3m\u{1b}[2m\u{1b}[1mmissing_function\u{1b}[0m\u{1b}[31m\u{1b}[3m\u{1b}[2m\u{1b}[0m\u{1b}[31m\u{1b}[3m\u{1b}[0m\u{1b}[31m\u{1b}[0m\u{1b}[34m\u{1b}[3m\u{1b}[2m(missing_variable);\u{1b}[0m\u{1b}[34m\u{1b}[3m\u{1b}[0m\u{1b}[34m\u{1b}[0m\n - Error: Function \u{1b}[1;31mmissing_function\u{1b}[0m doesn't exists.\n - Solution: Implement an missing_function function, like this:\n fn \u{1b}[32mmissing_function\u{1b}[0m(...) { ...\u{1b}[3m*your code here*\u{1b}[0m... }"); impl <'code_input> SimpleErrorDetail for CompilationError<'code_input>{ fn explain_error(&self) -> SimpleErrorExplanation { match self{ CompilationError::MissingVariable{ variable_name } => { SimpleErrorExplanation::new() .colorization_marker(variable_name, style::Clear + foreground::Red + style::Bold) .whole_input_colorization(foreground::Blue + style::Italic + style::Dimmed) .explanation(format!("Variable {} doesn't exists.", variable_name.red().bold())) .solution(format!("Declare it before using it, like this:\nlet {} = {}", variable_name.green(), "*your value*".italic())) } CompilationError::MissingFunction{ function_name } => { SimpleErrorExplanation::new() .colorization_marker(function_name, style::Clear + foreground::Red + style::Bold) .whole_input_colorization(foreground::Blue + style::Italic + style::Dimmed) .explanation(format!("Function {} doesn't exists.", function_name.red().bold())) .solution(format!("Implement an {function_name} function, like this:\nfn {}(...) {{ ...{}... }}", function_name.green(), "*your code here*".italic() )) } CompilationError::RootCompilationError => { SimpleErrorExplanation::new().explanation("Couldn't compile code.") } } } } #[derive(Debug)] enum CompilationError<'code_input>{ MissingVariable {variable_name : &'code_input str}, MissingFunction {function_name : &'code_input str}, RootCompilationError } ``` # Features - ``std``: Implements the Error trait for SimpleError, it might also be used for future implementations that might require targeting std. - ``colorization``: Allows the colorization markers functions to be used on SimpleErrorExplanation, helping you to create beautiful colored error message to direct your user's attention. - ``serde``: Implements Serialize and Deserialize on SimpleErrorDisplayInfo, this is useful for storing logs of errors, especially for auditing. Currently, the ``std`` and ``colorization`` are enabled by default.