Error Handling Examples¶
Onion provides Option and Result types for explicit, composable error handling without relying solely on exceptions.
Option¶
Option[T] represents a value that may be absent.
record User(id: Int, name: String)
def findUser(id: Int): Option[User] {
if id > 0 {
return Option::some(new User(id, "Alice"))
}
return Option::none()
}
val user = findUser(1)
if user.isEmpty() {
println("not found")
} else {
println("found " + user.get().name())
}
// Provide a fallback with getOrElse.
val name = user.map((u: User) -> { return u.name() }).getOrElse("guest")
Result¶
Result[T, E] represents either a success value or an error value.
def parseAge(raw: String): Result[Int, String] {
try {
val age = JInteger::parseInt(raw)
if age >= 0 && age <= 150 {
return Result::ok(age)
}
return Result::err("age out of range")
} catch e: NumberFormatException {
return Result::err("invalid age")
}
}
val age = parseAge("30")
if age.isOk() {
println("age=" + age.getOrElse(0))
} else {
println("error=" + age.getError())
}
Do-Notation for Result¶
Chain fallible operations with do[Result].
def validate(input: UserInput): Result[UserInput, String] {
return do[Result] {
name <- optionToResult(nonEmpty(input.name()), "name is required")
age <- parseAge(input.age())
email <- validateEmail(input.email())
ret new UserInput(name, "" + age, email)
}
}
Complete Example: Result Validation¶
ResultValidation.on validates user input using Option and Result.
record UserInput(name: String, age: String, email: String)
def nonEmpty(value: String): Option[String] {
if value != null && value.trim().length() > 0 {
return Option::some(value.trim())
}
return Option::none()
}
def optionToResult(opt: Option[String], error: String): Result[String, String] {
if opt.isEmpty() {
return Result::err(error)
}
return Result::ok(opt.get())
}
def parseAge(raw: String): Result[Int, String] {
try {
val age = JInteger::parseInt(raw)
if age >= 0 && age <= 150 {
return Result::ok(age)
}
return Result::err("age out of range: " + raw)
} catch e: NumberFormatException {
return Result::err("invalid age: " + raw)
}
}
def validateEmail(raw: String): Result[String, String] {
val trimmed = raw.trim()
if trimmed.indexOf("@") > 0 && trimmed.indexOf(".") > trimmed.indexOf("@") {
return Result::ok(trimmed)
}
return Result::err("invalid email: " + raw)
}
def validateDo(input: UserInput): Result[UserInput, String] {
return do[Result] {
name <- optionToResult(nonEmpty(input.name()), "name is required")
age <- parseAge(input.age())
email <- validateEmail(input.email())
ret new UserInput(name, "" + age, email)
}
}
def formatResult(r: Result[UserInput, String]): String {
if r.isOk() {
val u = r.get()
return "OK(" + u.name() + ", " + u.age() + ", " + u.email() + ")"
}
return "ERR(" + r.getError() + ")"
}
val good = new UserInput("Alice", "30", "alice@example.com")
val noName = new UserInput("", "30", "alice@example.com")
val badAge = new UserInput("Alice", "abc", "alice@example.com")
val badEmail = new UserInput("Alice", "30", "not-an-email")
println(formatResult(validateDo(good)))
println(formatResult(validateDo(noName)))
println(formatResult(validateDo(badAge)))
println(formatResult(validateDo(badEmail)))
Output:
OK(Alice, 30, alice@example.com)
ERR(name is required)
ERR(invalid age: abc)
ERR(invalid email: not-an-email)
Combining Option and Result¶
Convert an Option into a Result to provide a custom error message.
def optionToResult(opt: Option[String], error: String): Result[String, String] {
if opt.isEmpty() {
return Result::err(error)
}
return Result::ok(opt.get())
}
Next Steps¶
- Async Examples - Error handling with futures
- Scripting Examples - Validate CLI input
- Standard Library Reference - Full Option/Result API