SafeStage

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

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 behavior differs from the normal path.