Skip to main content

Error handling

All expressions with the exception of collections can evaluate as an error value. For instance when reading a dataset as a string, from a wrong URL:

let x = String.Read(Http.Get("http://broken/location"))

Because the URL http://broken/location can't be downloaded, String.Read evaluates as an error:

"service error: http error: host not found for http://broken/location"

An error is a value. It doesn't interrupt the execution when it occurs. In the example below, x is declared as a list of two items, two strings read from two URLs using String.Read. The first call to String.Read fails because the host doesn't resolve, and evaluates as an error. The second call succeeds. Both values end up in the list.

let x = [
String.Read(Http.Get("http://broken/location")), // that one fails
String.Read(Http.Get("http://www.example.com")) // that one succeeds
]

Here's the value of x:

[
"http error: host not found for http://broken/location",
"... <the HTML page downloaded from www.example.com> ..."
]

Error propagation

If an error value is passed as a parameter to a function, the error propagates. In the program below, List.Transform is used to apply String.Lengthto both items of x. When it is applied to the error value, String.Length cannot compute the string length and evaluates as an error:

let x = [
String.Read(Http.Get("http://broken/location")),
String.Read(Http.Get("http://www.example.com"))
]
in List.Transform(x, s -> String.Length(s))

The code executes till the end and returns a list of items, one of which is an error.

[
"http error: host not found for http://broken/location",
1256
]

Checking for errors

To check if a value is an error, use Try.IsError.

let
x: string = String.Read(Http.Get("http://broken/location"))
in
Try.IsError(x) // true

Try.IsSuccess is also available.

let
x: string = String.Read(Http.Get("http://broken/location"))
in
Try.IsSuccess(x) // false

Building and returning custom errors

Building a custom error value is also possible with Error.Build. This is useful if willing to return an error from a function. Here is an example function that divides by two its argument n, but returns an error when the number isn't even:

let
half(n: int) =
if n % 2 == 0 then
n / 2
else
Error.Build(String.From(n) + " isn\'t even")
in
List.Transform([1, 2, 3, 4], (i) -> half(i))

evaluates to:

[
"1 isn't even",
1,
"3 isn't even",
2
]

Example

A call can fail unexpectedly: for instance, reading data from an external source can fail because the source cannot be reached or because it contains wrong data.

info

If you want to try this example, you can deploy the following endpoint:

Tutorial 11 - Handling errors
How to handle errors?

Usage:

/tutorial/11-error-handling

As an example, suppose we are reading data from a list of remote locations (HTTPS endpoints). We open each location with Json.Read and then run List.Count.

let urls = [
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-01.json",
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-02.json",
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-03.json",
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/not_found.json", // doesn't exist
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/airports.csv" // fails to parse as JSON
]
in List.Transform(urls, url ->
let contents = Json.Read(url, type record(creation_date: string, entries: list(record(hostname: string)))),
nEntries = List.Count(contents.entries)
in {location: url, count: nEntries })

If we execute the query above, the two last rows show an error in the count field instead of the expected value. That's because the URLs do not exist or are not JSON data.

[
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-01.json",
"count": 2
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-02.json",
"count": 2
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-03.json",
"count": 3
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/not_found.json",
"count": "http error: could not read (HTTP GET) from https://raw-tutorial.s3.eu-west-1.amazonaws.com/not_found.json: (404)\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>not_found.json</Key><RequestId>0QY68Y95EK2FYQRR</RequestId><HostId>XoGyiNc8l9ZF33fv5/DsyDiBiN50nDa2DOaOovb+7ExYF4N3ddrBFxfVfGJGXufQRQp1NW3ArzY=</HostId></Error>"
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/airports.csv",
"count": "expected { but token VALUE_STRING found"
}
]

Let's now replace the errors with different data. For this we use Try.IsError with an if statement.

let ...
nEntries = List.Count(contents.entries), // can be an error
fixedCount = if Try.IsError(nEntries) then -1 else nEntries

The code is now:

let urls = [
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-01.json",
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-02.json",
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-03.json",
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/not_found.json", // doesn't exist
"https://raw-tutorial.s3.eu-west-1.amazonaws.com/airports.csv" // fails to parse as JSON
]
in List.Transform(urls, url ->
let contents = Json.Read(url, type record(creation_date: string, entries: list(record(hostname: string)))),
nEntries = List.Count(contents.entries),
fixedCount = if Try.IsError(nEntries) then -1 else nEntries
in {location: url, count: fixedCount })

And the results are:

[
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-01.json",
"count": 2
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-02.json",
"count": 2
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/error-handling/log-2022-04-03.json",
"count": 3
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/not_found.json",
"count": -1
},
{
"location": "https://raw-tutorial.s3.eu-west-1.amazonaws.com/airports.csv",
"count": -1
}
]