Skip to content

Error Handling

Calls that can fail have a fallible type T!. You either propagate the failure with try or handle it with else.

try and else

try e propagates a failure as an error response; let x = e else err { … } runs a (diverging) block that can inspect the error:

weld
route GET "/proxy/:id" (id: int) -> User {
    respond try backend.getUser(id)          # try -> auto 502 on failure
}

route GET "/safe/:id" (id: int) -> User {
    let u = backend.getUser(id) else e {     # else -> handle it
        status 502
        respond "upstream down: {e.message}\n"
    }
    respond u
}

The built-in Error { status: int, message: string } is what a bare else binds.

Unwrapping optionals

The same else form unwraps an optional or runs a diverging block:

weld
let header = request_header("Authorization") else { fail 401 "missing authorization" }

Typed errors

A function can declare an error type with -> T ! E (where E is a sum type). It fails with a variant, and the fallible T! carries that typed value so a caller can match on it. A bare T! still uses the built-in Error.

weld
type LookupError =
    | NotFound
    | Invalid(string)

fn lookup(id: int) -> string ! LookupError {
    if id < 0   { fail LookupError.Invalid("id must be non-negative") }
    if id > 100 { fail LookupError.NotFound }
    return "user-{id}"
}

route GET "/user/:id" (id: int) -> string {
    let name = lookup(id) else e {                # handle each variant
        let code = match e { NotFound => 404, Invalid(_) => 400 }
        let msg  = match e { NotFound => "no such user", Invalid(reason) => "bad: {reason}" }
        status code
        respond msg
    }
    respond "found {name}"
}

# Without `else`, an unhandled typed error becomes a 500 with the error as JSON.
route GET "/raw/:id" (id: int) -> string {
    respond try lookup(id)
}

try in a fallible fn propagates the error across a call chain, handled once at the boundary with else / match.

fail

fail STATUS "message" short-circuits with an error response (used in routes, decorators, hubs, and transactions). The status is any int expression — a literal, a variable, or a std.HttpStatus value (fail std.HttpStatus.NotFound "no such user") — and the message any string expression. In a typed-error function, fail Variant(…) raises the typed value instead.