Skip to main content

Safe

Safe catches non-fatal exceptions thrown by the inner stage and converts them into Status.Complete error values, widening the error type from E to Either[Throwable, E].

  • Exceptions caught during apply are reported as Left(throwable) in Yield.None(Status.error(Left(e)), ...).
  • Errors already carried by the inner stage are wrapped as Right(e).

Fatal exceptions (those not matched by scala.util.control.NonFatal) propagate normally. The evolution is mapped so that every continuation stage remains wrapped in Safe.

Safe is a crutch for the simplest cases — stateless stages. Per the Lifecycle contract, a stage that throws from apply has already released its own resources, so the recovered continuation simply reuses the inner stage — retrying it on the next input — and carries a no-op dispose. A stage that owns resources should be made safe by itself instead of being wrapped: extend SafeStage and supply a recover with the appropriate cleanup (the ConstEvolution overload taking a dispose function exists for exactly this).

import h8io.stages.*
import h8io.stages.base.*
import h8io.stages.operators.*
object ParseIntUnsafe extends SAMStage[String, Int, Nothing] {
override def apply(in: String): Yield[String, Int, Nothing] =
Yield.Some(in.toInt, Status.Success, this)
}

val safe = Safe(ParseIntUnsafe)
// safe: Safe[String, Int, Nothing] = Safe(alterand = <function1>)
safe("42")
// res0: Yield[String, Int, Either[Throwable, Nothing]] = Some(
// out = 42,
// status = Success,
// evolution = Mapped(
// evolution = <function1>,
// f = h8io.stages.operators.Safe$$Lambda$28801/0x00007fea1f5bf608@4fc3fae3
// )
// )
safe("hello")
// res1: Yield[String, Int, Either[Throwable, Nothing]] = None(
// status = Complete(
// Left(value = java.lang.NumberFormatException: For input string: "hello")
// ),
// evolution = ConstEvolution(
// stage = Safe(alterand = <function1>),
// _dispose = h8io.stages.base.ConstEvolution$$$Lambda$28659/0x00007fea1f5774b8@1ef9c3d2
// )
// )