Crates.io | simple_detailed_error |
lib.rs | simple_detailed_error |
version | 0.1.0 |
source | src |
created_at | 2024-09-08 22:50:58.879028 |
updated_at | 2024-09-08 22:50:58.879028 |
description | Stack and specify errors explainations saying what happened, why, how, where, how to solve it and its causes |
homepage | |
repository | https://github.com/JorgeRicoVivas/simple_detailed_error |
max_upload_size | |
id | 1368600 |
size | 87,395 |
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.
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... }
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:
#[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):
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
}
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:
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'.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].CompilationError::MissingFunction
, as for the function at
, this time
we will reference 'return missing_function(missing_variable);'.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.
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
}
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.
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'..explanation(format!("Variable {variable_name} doesn't exists."))
for
.explanation(format!("Variable {} doesn't exists.", variable_name.red().bold()))
.SimpleErrorExplanation::new() .colorization_marker(variable_name, foreground::Red + style::Bold)
, then 'if missing_variable >
0' would look like 'if missing_variable > 0'.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.
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.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:
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
}
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.