Skip to main content

SafeStage

SafeStage separates normal execution from exception recovery. It seals apply and routes each invocation through two abstract methods:

  • body(in: I) — the happy path;
  • recover(in: I, e: Throwable) — called when body throws a non-fatal exception.

Fatal exceptions — anything scala.util.control.NonFatal does not match — propagate normally without being caught.

import h8io.stages.*
import h8io.stages.base.*
object SafeParseInt
extends SafeStage[String, Int, String] with SAMStage[String, Int, String] {

override def body(in: String): Yield[String, Int, String] =
Yield.Some(in.toInt, Status.Success, this)

override def recover(in: String, e: Throwable): Yield[String, Int, String] =
Yield.None(Status.error(s"parse failed: ${e.getMessage}"), this)
}

SafeParseInt("99")
// res0: Yield[String, Int, String] = Some(
// out = 99,
// status = Success,
// evolution = <function1>
// )
SafeParseInt("not-a-number")
// res1: Yield[String, Int, String] = None(
// status = Complete("parse failed: For input string: \"not-a-number\""),
// evolution = <function1>
// )

SafeStage composes well with SAMStage: mix both when the stage is stateless and only the error recovery behaviour differs from the normal path.