failure_stack 0.1.3 failure_stack: ^0.1.3 copied to clipboard
A error handling library inspired by rust and error-stack, to prevent unpredicted errors.
A error handling library inspired by rust and error-stack, to prevent unpredicted errors.
Why use failure_stack ? #
The origin error handle method: throw catch, may cause unpredicted errors and behaviors,
by using the result type as the return value. You are forced to handle every failure that might
occur, making your program less likely to cause errors.
You may say, I get it , Result and Either types are great, but dartz and fpdart already that
has these, why create another library? The above mentioned are great libraries for dart functional programming,
but when it comes to error handling , they might not be ideal when your program becomes larger and contains a lot nested function calls.
So above the normal Either type, this package has some additional features.
- encourage the user to provide a new error type if the scope is changed, usually by crossing layers in apps or 3rd party libraries(for example,
ApiError
for infrastructure layer errors andInvalidInputError
for application layers.) - be able to attach any extra data to failures
- be able to push failures to the stack and handle them later while still keeping track of them.
Usage #
Let's say we have a function that parses a String
to int
, and it may fail when the input is not a number.
class ParsingFailure{} // The failure representing that the parsing failed
Result<int,ParsingFailure> parse(String numString);
When we use the function, we have 3 ways to handle the result.
- When you don't care about the failure that might occur.
// .ok returns the contained ok value, since the result might fail, it is a nullable type.
int? result = parse(targetString).ok;
- Exhaustive matching.
switch(parse(targetString)){
case Ok<int,ParsingFailure> ok: {
print("success: ${ok.value}");
},
case Fail<int,ParsingFailure> fail: {
print("Failed: ${fail.failure}");
}
}
- When you are in a function that returns a Result type too,
use
resultHandleEnvironment
instead. Warning: Do not unwrap results that do not match the failure type, useResult.mapFail
orResult.pushFail
to change failure type.
Result<int, FormatException> parseString(String s) {
try {
return Ok(int.parse(s));
} on FormatException catch (e) {
Result<int, FormatException> r = e.intoFailure();
return r.attach("Failed parsing $s to int");
}
}
class ParseExperimentFailure {
const ParseExperimentFailure();
@override
String toString() {
return "ParseExperimentFailure: invalid experiment input";
}
}
Result<List<int>, ParseExperimentFailure> parseExperiment(String input) {
return resultHandleEnvironment(() {
List<int> values = input
.split(" ")
.map((String s) => parseString(s)) // Result<int, FormatException>
.map((Result<int,FormatException> result) => result.pushFail(const ParseExperimentFailure())) // Result<int, ParseExperimentFailure>
//when the result is Ok, it unwraps to int,
//otherwise it throws ParseExperimentFailure and get catches by the
//resultHandleEnvironment and returns as Fail(ParseExperimentFailure)
.map((Result<int,ParseExperimentFailure> result) => result.unwrap()) //int
.toList(growable: false);
return Ok(values);
});
}
Converting Exceptions and Errors to Failure #
Use the intoFailure()
extension
Future<Result<(), DioException>> callApi() async {
try{
await dio.post(/*some code*/);
}
on DioException catch(e){
return e.intoFailure();
}
}