Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Error Handling related BBEs #5709

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions examples/check-expression/check_expression.bal
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import ballerina/io;

// Convert `bytes` to a `string` value and then to an `int` value.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
function intFromBytes(byte[] bytes) returns int|error {

// Use `check` with an expression that may return `error`.
// If `string:fromBytes(bytes)` returns an `error` value, `check`
// makes the function return the `error` value here.
// If not, the returned `string` value is used as the value of the `str` variable.
function intFromBytesWithCheck(byte[] bytes) returns int|error {
string str = check string:fromBytes(bytes);

return int:fromString(str);
}

// Same as `intFromBytesWithCheck` but with explicit error handling.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
function intFromBytesExplicit(byte[] bytes) returns int|error {
string|error res = string:fromBytes(bytes);
// Handling the error explicitly.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
if res is error {
return res;
}
return int:fromString(res);
}

public function main() {
int|error res = intFromBytes([104, 101, 108, 108, 111]);
io:println(res);
int|error res1 = intFromBytesWithCheck([104, 101, 108, 108, 111]);
io:println(res1);
int|error res2 = intFromBytesExplicit([104, 101, 108, 108, 111]);
io:println(res2);
}
6 changes: 2 additions & 4 deletions examples/check-expression/check_expression.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Check expression

`check E` is used with an expression `E` that might result in an `error` value. If `E` results in an `error` value , then, `check` makes the function return that `error` value immediately.

The type of `check E` does not include `error`. The control flow remains explicit.
In Ballerina, it is common to write an expression that may result in an error, such as calling a function that could return an error value, checking if the result belongs to the `error` type and immediately returning that value. You can use the `check` expression to simplify this pattern.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

::: code check_expression.bal :::

::: out check_expression.out :::
::: out check_expression.out :::
1 change: 1 addition & 0 deletions examples/check-expression/check_expression.out
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
$ bal run check_expression.bal
error("{ballerina/lang.int}NumberParsingError",message="'string' value 'hello' cannot be converted to 'int'")
error("{ballerina/lang.int}NumberParsingError",message="'string' value 'hello' cannot be converted to 'int'")
91 changes: 70 additions & 21 deletions examples/error-reporting/error_reporting.bal
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,31 +1,80 @@
import ballerina/io;

// Parses a `string` value to convert to an `int` value. This function may return error values.
// The return type is a union with `error`.
function parse(string s) returns int|error {

int n = 0;
int[] cps = s.toCodePointInts();
foreach int cp in cps {
int p = cp - 0x30;
if p < 0 || p > 9 {
// If `p` is not a digit construct, it returns an `error` value with `not a digit` as the error message.
return error("not a digit");
function parseInt(string s) returns int|error {
if s.length() == 0 {
return error("empty string"); // Create error with only a message.
}
int pow = 0;
int val = 0;
foreach string:Char char in s {
int|error digit = parseDigit(char);
if digit is error {
// Create a new error value that include the error from parsing as
// the cause.
return error("failed to parse digit", digit, stringValue = s);
}
val += val * 10 + digit;
pow += 1;
}
return val;
}

function parseDigit(string:Char s) returns int|error {
match s {
"1" => {
return 1;
}
"2" => {
return 2;
}
"3" => {
return 3;
}
"4" => {
return 4;
}
"5" => {
return 5;
}
"6" => {
return 6;
}
"7" => {
return 7;
}
"8" => {
return 8;
}
"9" => {
return 9;
}
"0" => {
return 0;
}
_ => {
// Create an error value with field `charValue` in it's detail.
return error("unexpected char for digit value", charValue = s);
}
n = n * 10 + p;
}
return n;
}

public function main() {
// An `int` value is returned when the argument is a `string` value, which can be parsed as an integer.
int|error x = parse("123");

io:println(x);

// An `error` value is returned when the argument is a `string` value, which has a character that is not a digit.
int|error y = parse("1h");
int|error result = parseInt("1x3");
if result is error {
printError(result);
}
}

io:println(y);
// Helper function to print internals of error value.
function printError(error err, int depth = 0) {
string indent = "".join(...from int _ in 0 ..< depth
select " ");
io:println(indent + "message: ", err.message());
io:println(indent + "details ", err.detail());
io:println(indent + "stack trace: ", err.stackTrace());
error? cause = err.cause();
if cause != () {
io:println(indent + "cause: ", cause);
printError(cause, depth + 1);
}
}
17 changes: 13 additions & 4 deletions examples/error-reporting/error_reporting.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Errors
In Ballerina invalid states are represented by `error` values. Each error value has,
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
1. Message, a human-readable `string` describing the error.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
2. Cause, which is an `error` value if this error was caused by another error. Otherwise `nil`.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
3. Detail, a mapping value which can be used to provide additional information about the error.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
4. Stack trace, a snapshot of the state of the execution stack when the error value was created.

Ballerina does not have exceptions. Errors are reported by functions returning `error` values.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
`error` is its own basic type. The return type of a function that may return an `error` value will be a union with `error`.
Error values are immutable.

An `error` value includes a `string` message. An `error` value includes the stack trace from the point at which the error is constructed (i.e., `error(msg)` is called). Error values are immutable.
You can create new error values by calling the error constructor. As the first argument to the error constructor, it expects the `message` string. As the second argument, you can optionally pass in an `error?` value for cause. The remaining named arguments will be used to create the detail record. The stack trace is provided by the runtime.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

::: code error_reporting.bal :::

::: out error_reporting.out :::
::: out error_reporting.out :::

## Related links
- [Error subtyping](https://ballerina.io/learn/by-example/error-subtyping/)
- [Error cause](https://ballerina.io/learn/by-example/error-cause/)
- [Error detail](https://ballerina.io/learn/by-example/error-detail/)
9 changes: 7 additions & 2 deletions examples/error-reporting/error_reporting.out
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
$ bal run error_reporting.bal
123
error("not a digit")
message: failed to parse digit
details {"stringValue":"1x3"}
stack trace: [callableName: parseInt fileName: error_reporting.bal lineNumber: 14,callableName: main fileName: error_reporting.bal lineNumber: 62]
cause: error("unexpected char for digit value",charValue="x")
message: unexpected char for digit value
details {"charValue":"x"}
stack trace: [callableName: parseDigit fileName: error_reporting.bal lineNumber: 56,callableName: parseInt fileName: error_reporting.bal lineNumber: 10,callableName: main fileName: error_reporting.bal lineNumber: 62]
47 changes: 36 additions & 11 deletions examples/error-subtyping/error_subtyping.bal
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
import ballerina/io;

// `distinct` creates a new subtype.
type XErr distinct error;
type YErr distinct error;
type InvalidIntDetail record {|
int value;
|};

type Err XErr|YErr;
type InvalidI32Detail record {|
int:Signed32 value;
|};

// The name of the distinct type can be used with the `error` constructor to create an error value
// of that type. `err` holds an `error` value of type `XErr`.
Err err = error XErr("Whoops!");
type InvalidIntError error<InvalidIntDetail>;

function desc(Err err) returns string {
// The `is` operator can be used to distinguish distinct subtypes.
return err is XErr ? "X" : "Y";
type InvalidI32Error error<InvalidI32Detail>;

type DistinctIntError distinct error<InvalidIntDetail>;

type AnotherDistinctIntError distinct error<InvalidIntDetail>;
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

function createInvalidIntError(int value) returns InvalidIntError {
return error("Invalid int", value = value);
}

function createDistinctInvalidIntError(int value) returns DistinctIntError {
return error("Invalid int", value = value);
}

function createInvalidI32Error(int:Signed32 value) returns InvalidI32Error {
return error("Invalid i32", value = value);
}

public function main() {
io:println(desc(err));
InvalidI32Error e1 = createInvalidI32Error(5);
// This is true because `InvalidI32Detail` is a subtype of `InvalidIntDetail`.
io:println(e1 is InvalidIntError);

InvalidIntError e2 = createInvalidIntError(5);
// This is false because `e2` don't have the type id corresponding to `DistinctIntError`.
io:println(e2 is DistinctIntError);

DistinctIntError e3 = createDistinctInvalidIntError(5);
// This is true because `InvalidInt` is not a distinct type, thus it ignores the type id of `e3`.
io:println(e3 is InvalidIntError);

// This is false because `DistinctIntError` and `AnotherDistinctIntError` have different type ids.
io:println(e3 is AnotherDistinctIntError);
}
9 changes: 6 additions & 3 deletions examples/error-subtyping/error_subtyping.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Error subtyping

`distinct` creates a new subtype and can be used to define subtypes of `error`. The name of the distinct `error` type can be used with the error constructor to create an `error` value of that type.
A given non-distinct `error` type (such as `InvalidIntDetail` in the example) is a supertype of another error type (say `InvalidI32Error`) if and only if the latter's detail record type (`InvalidIntDetail`) is super type of the former's detail type (`InvalidI32Detail`).
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

Works like a nominal type. The `is` operator can be used to distinguish distinct subtypes. Each occurrence of `distinct` has a unique identifier that is used to tag instances of the type.
If more explicit control over the error type relations is desired you can use `distinct` error types. Each declaration of a distinct error type has a unique type ID. For instance, the `DistinctIntError` and `AnotherDistinctIntError` have different type IDs. If the supertype is a `distinct` error type then there is the additional requirement that it must contain all the type IDs of the subtype.

::: code error_subtyping.bal :::

::: out error_subtyping.out :::
::: out error_subtyping.out :::

## Related links
- [Type intersection for error types](https://ballerina.io/learn/by-example/error-type-intersection/)
5 changes: 4 additions & 1 deletion examples/error-subtyping/error_subtyping.out
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
$ bal run error_subtyping.bal
X
true
false
true
false
66 changes: 40 additions & 26 deletions examples/error-type-intersection/error_type_intersection.bal
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
import ballerina/io;

type IOError distinct error;
type InputErrorDetail record {|
int|string value;
|};

type FileErrorDetail record {
string filename;
};
type NumericErrorDetail record {|
int|float value;
|};

// The `FileIOError` type is defined as an intersection type using the `&` notation.
// It is the intersection of two error types: `IOError` and `error<FileErrorDetail>`.
// An error value belongs to this type if and only if it belongs to both `IOError`
// and `error<FileErrorDetail>`.
type FileIOError IOError & error<FileErrorDetail>;
type InputError error<InputErrorDetail>;

type NumericError error<NumericErrorDetail>;

type DistinctInputError distinct error<InputErrorDetail>;

type DistinctNumericError distinct error<NumericErrorDetail>;

// `NumericInputError` has detail type, `record {| int value |}`.
type NumericInputError InputError & NumericError;
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

// `DistinctNumericInputError` has type ids of both `DistinctInputError` and `DistinctNumericError`.
type DistinctNumericInputError DistinctInputError & DistinctNumericError;

function createNumericInputError(int value) returns NumericInputError {
return error("Numeric input error", value = value);
}

function createDistinctNumericInputError(int value) returns DistinctNumericInputError {
return error("Distinct numeric input error", value = value);
}
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

public function main() {
// In order to create an error value that belongs to `FileIOError`, the `filename`
// detail field must be provided.
FileIOError fileIOError = error("file not found", filename = "test.txt");

// `fileIOError` belongs to both `IOError` and `error<FileErrorDetail>`.
io:println(fileIOError is IOError);
io:println(fileIOError is error<FileErrorDetail>);

// An `IOError` value will not belong to `FileIOError` if it doesn't belong to
// `error<FileErrorDetail>`.
IOError ioError = error("invalid input");
io:println(ioError is FileIOError);

// Similarly, an error value belonging to `error<FileErrorDetail>` will not belong
// to `FileIOError` if it doesn't belong to `IOError`.
error<FileErrorDetail> fileError = error("cannot remove file", filename = "test.txt");
io:println(fileError is FileIOError);
NumericInputError e1 = createNumericInputError(5);
// `e1` belong to `InputError` since it's detail type is a subtype of `InputErrorDetail`.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
io:println(e1 is InputError);

// `e1` doesn't belong to `DistinctInputError` since it doesn't have the type id of `DistinctInputError`.
io:println(e1 is DistinctInputError);

DistinctNumericInputError e2 = createDistinctNumericInputError(5);
// `e2` belong to `InputError` since it's detail type is a subtype of `InputErrorDetail`.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved
io:println(e2 is InputError);

// `e2` belong to `DistinctInputError` since it's type id set include the type id of `DistinctInputError`.
io:println(e2 is DistinctInputError);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Type intersection for error types

You can define an error type that is both a subtype of a `distinct` error type and has additional constraints on the detail fields using intersection types.
If you intersect two `error` types, the resulting type's detail type is the intersection of the detail types of both types. Furthermore, if any of the types being intersected is a distinct type, then the resultant type's type ID set includes all the type IDs of that type.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

::: code error_type_intersection.bal :::

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
$ bal run error_type_intersection.bal
true
true
false
false
true
true
6 changes: 2 additions & 4 deletions examples/panics/panics.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Panics

Ballerina distinguishes normal errors from abnormal errors. Normal errors are handled by returning error values. Abnormal errors are handled using the panic statement. Abnormal errors should typically result in immediate program termination.

E.g., A programming bug or out of memory. A panic has an associated error value.
Ballerina distinguishes normal errors from abnormal errors. Normal errors are handled by returning error values. This signals to the caller that they must handle the error. In contrast, abnormal errors such as division by 0 and out-of-memory are typically unrecoverable errors and we need to terminate the execution of the program. This is achieved by a panic statement, which expects an expression that results in an error value such as the error constructor. At runtime evaluating a panic statement first create the error value by evaluating the expression and then start stack unwinding. In this process, if it comes across a trap expression it stops further unwinding and as a result of that trap expression, you will get the error created at the panic statement.
heshanpadmasiri marked this conversation as resolved.
Show resolved Hide resolved

::: code panics.bal :::

::: out panics.out :::
::: out panics.out :::
Loading