Skip to content

Commit

Permalink
interpreter failure split into internal/external, finalizers aren't e…
Browse files Browse the repository at this point in the history
…xposed anymore
  • Loading branch information
pshirshov committed Feb 27, 2023
1 parent e9ed999 commit 38a6709
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ import izumi.reflect.TagK
* @throws izumi.distage.model.exceptions.runtime.ProvisioningException produce* methods raise this exception in `F` effect type on failure
*/
trait Producer {
private[distage] def produceDetailedFX[F[_]: TagK: QuasiIO](plan: Plan, filter: FinalizerFilter[F]): Lifecycle[F, Either[FailedProvision[F], Locator]]
private[distage] def produceDetailedFX[F[_]: TagK: QuasiIO](plan: Plan, filter: FinalizerFilter[F]): Lifecycle[F, Either[FailedProvision, Locator]]
private[distage] final def produceFX[F[_]: TagK: QuasiIO](plan: Plan, filter: FinalizerFilter[F]): Lifecycle[F, Locator] = {
produceDetailedFX[F](plan, filter).evalMap(_.throwOnFailure())
produceDetailedFX[F](plan, filter).evalMap(_.failOnFailure())
}

/** Produce [[izumi.distage.model.Locator]] interpreting effect- and resource-bindings into the provided `F` */
final def produceCustomF[F[_]: TagK: QuasiIO](plan: Plan): Lifecycle[F, Locator] = {
produceFX[F](plan, FinalizerFilter.all[F])
}
final def produceDetailedCustomF[F[_]: TagK: QuasiIO](plan: Plan): Lifecycle[F, Either[FailedProvision[F], Locator]] = {
final def produceDetailedCustomF[F[_]: TagK: QuasiIO](plan: Plan): Lifecycle[F, Either[FailedProvision, Locator]] = {
produceDetailedFX[F](plan, FinalizerFilter.all[F])
}

/** Produce [[izumi.distage.model.Locator]], supporting only effect- and resource-bindings in `Identity` */
final def produceCustomIdentity(plan: Plan): Lifecycle[Identity, Locator] =
produceCustomF[Identity](plan)
final def produceDetailedIdentity(plan: Plan): Lifecycle[Identity, Either[FailedProvision[Identity], Locator]] =
final def produceDetailedIdentity(plan: Plan): Lifecycle[Identity, Either[FailedProvision, Locator]] =
produceDetailedCustomF[Identity](plan)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package izumi.distage.model.provisioning
import izumi.distage.model.Locator
import izumi.distage.model.definition.Lifecycle
import izumi.distage.model.definition.errors.DIError
import izumi.functional.quasi.QuasiIO
import izumi.distage.model.exceptions.*
import izumi.distage.model.definition.errors.ProvisionerIssue.*
import izumi.distage.model.definition.errors.ProvisionerIssue.ProvisionerExceptionIssue.*
import izumi.distage.model.exceptions.*
import izumi.distage.model.exceptions.runtime.{MissingInstanceException, ProvisioningException}
import izumi.distage.model.plan.Plan
import izumi.distage.model.provisioning.PlanInterpreter.{FailedProvision, FinalizerFilter}
import izumi.distage.model.provisioning.Provision.ProvisionImmutable
import izumi.distage.model.provisioning.Provision.{ProvisionImmutable, ProvisionInstances}
import izumi.distage.model.reflection.*
import izumi.functional.quasi.QuasiIO
import izumi.fundamentals.platform.IzumiProject
import izumi.fundamentals.platform.build.MacroParameters
import izumi.fundamentals.platform.exceptions.IzThrowable.*
Expand All @@ -23,7 +23,7 @@ trait PlanInterpreter {
plan: Plan,
parentLocator: Locator,
filterFinalizers: FinalizerFilter[F],
): Lifecycle[F, Either[FailedProvision[F], Locator]]
): Lifecycle[F, Either[FailedProvision, Locator]]
}

object PlanInterpreter {
Expand All @@ -41,18 +41,19 @@ object PlanInterpreter {
}
}

case class FailedProvisionMeta(status: Map[DIKey, OpStatus])
final case class FailedProvisionMeta(status: Map[DIKey, OpStatus])

final case class FailedProvisionInternal[F[_]](provision: ProvisionImmutable[F], fail: FailedProvision)

final case class FailedProvision[F[_]](
failed: ProvisionImmutable[F],
final case class FailedProvision(
failed: ProvisionInstances,
plan: Plan,
parentContext: Locator,
failure: ProvisioningFailure,
meta: FailedProvisionMeta,
fullStackTraces: Boolean,
) {
/** @throws ProvisioningException in `F` effect type */
def throwException[A]()(implicit F: QuasiIO[F]): F[A] = {
def toThrowable(): Throwable = {
import FailedProvision.ProvisioningFailureOps
val repr = failure.render(fullStackTraces)
val ccFailed = failure.status.collect { case (key, _: OpStatus.Failure) => key }.toSet.size
Expand All @@ -69,21 +70,28 @@ object PlanInterpreter {
case _: ProvisioningFailure.CantBuildIntegrationSubplan => Seq.empty
}

F.fail {
new ProvisioningException(
s"""Interpreter stopped; out of $ccTotal operations: $ccFailed failed, $ccDone succeeded, $ccPending ignored
|$repr
|""".stripMargin,
null,
).addAllSuppressed(allExceptions)
}
new ProvisioningException(
s"""Interpreter stopped; out of $ccTotal operations: $ccFailed failed, $ccDone succeeded, $ccPending ignored
|$repr
|""".stripMargin,
null,
).addAllSuppressed(allExceptions)
}

/** @throws ProvisioningException in `F` effect type */

}

object FailedProvision {
implicit final class FailedProvisionExt[F[_]](private val p: Either[FailedProvision[F], Locator]) extends AnyVal {
implicit final class FailedProvisionExt[F[_]](private val p: Either[FailedProvision, Locator]) extends AnyVal {
/** @throws ProvisioningException in `F` effect type */
def throwOnFailure()(implicit F: QuasiIO[F]): F[Locator] = p.fold(_.throwException(), F.pure)
def failOnFailure()(implicit F: QuasiIO[F]): F[Locator] = p.fold(f => F.fail(f.toThrowable()), F.pure)
def throwOnFailure(): Locator = p match {
case Left(f) =>
throw f.toThrowable()
case Right(value) =>
value
}
}

implicit class ProvisioningFailureOps(private val failure: ProvisioningFailure) extends AnyVal {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ trait Provision[+F[_]] {
}

object Provision {
final case class ProvisionInstances(
instances: Map[DIKey, Any],
imports: Map[DIKey, Any],
)

final case class ProvisionImmutable[+F[_]](
// LinkedHashMap for ordering
instancesImpl: mutable.LinkedHashMap[DIKey, Any],
imports: Map[DIKey, Any],
finalizers: Seq[Finalizer[F]],
) extends Provision[F] {
def raw: ProvisionInstances = ProvisionInstances(instances, imports)
override def instances: Map[DIKey, Any] = instancesImpl
override lazy val enumerate: immutable.Seq[IdentifiedRef] = super.enumerate
override lazy val index: immutable.Map[DIKey, Any] = super.index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class InjectorDefaultImpl[F[_]](
override private[distage] def produceDetailedFX[G[_]: TagK: QuasiIO](
plan: Plan,
filter: FinalizerFilter[G],
): Lifecycle[G, Either[FailedProvision[G], Locator]] = {
): Lifecycle[G, Either[FailedProvision, Locator]] = {
interpreter.run[G](plan, bootstrapLocator, filter)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ trait Injector[F[_]] extends Planner with Producer {
.liftF(G.maybeSuspendEither(plan(input).aggregateErrors))
.flatMap(produceCustomF[G])
}
final def produceDetailedCustomF[G[_]: TagK](input: PlannerInput)(implicit G: QuasiIO[G]): Lifecycle[G, Either[FailedProvision[G], Locator]] = {
final def produceDetailedCustomF[G[_]: TagK](input: PlannerInput)(implicit G: QuasiIO[G]): Lifecycle[G, Either[FailedProvision, Locator]] = {
Lifecycle
.liftF(G.maybeSuspendEither(plan(input).aggregateErrors))
.flatMap(produceDetailedCustomF[G])
Expand All @@ -240,7 +240,7 @@ trait Injector[F[_]] extends Planner with Producer {
final def produceCustomIdentity(input: PlannerInput): Lifecycle[Identity, Locator] = {
produceCustomF[Identity](input)
}
final def produceDetailedIdentity(input: PlannerInput): Lifecycle[Identity, Either[FailedProvision[Identity], Locator]] = {
final def produceDetailedIdentity(input: PlannerInput): Lifecycle[Identity, Either[FailedProvision, Locator]] = {
produceDetailedCustomF[Identity](input)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import izumi.distage.model.exceptions.runtime.IntegrationCheckException
import izumi.distage.model.plan.ExecutableOp.*
import izumi.distage.model.plan.{ExecutableOp, Plan}
import izumi.distage.model.provisioning.*
import izumi.distage.model.provisioning.PlanInterpreter.{FailedProvision, FinalizerFilter}
import izumi.distage.model.provisioning.PlanInterpreter.{FailedProvision, FailedProvisionInternal, FinalizerFilter}
import izumi.distage.model.provisioning.strategies.*
import izumi.distage.model.{Locator, Planner}
import izumi.distage.provisioning.PlanInterpreterNonSequentialRuntimeImpl.{abstractCheckType, integrationCheckIdentityType, nullType}
Expand Down Expand Up @@ -39,32 +39,33 @@ class PlanInterpreterNonSequentialRuntimeImpl(
parentLocator: Locator,
filterFinalizers: FinalizerFilter[F],
)(implicit F: QuasiIO[F]
): Lifecycle[F, Either[FailedProvision[F], Locator]] = {
Lifecycle.make(
acquire = instantiateImpl(plan, parentLocator)
)(release = {
resource =>
val finalizers = resource match {
case Left(failedProvision) => failedProvision.failed.finalizers
case Right(locator) => locator.finalizers
}
filterFinalizers.filter(finalizers).foldLeft(F.unit) {
case (acc, f) => acc.guarantee(F.suspendF(f.effect()))
}
})
): Lifecycle[F, Either[FailedProvision, Locator]] = {
Lifecycle
.make(
acquire = instantiateImpl(plan, parentLocator)
)(release = {
resource =>
val finalizers = resource match {
case Left(failedProvision) => failedProvision.provision.finalizers
case Right(locator) => locator.finalizers
}
filterFinalizers.filter(finalizers).foldLeft(F.unit) {
case (acc, f) => acc.guarantee(F.suspendF(f.effect()))
}
}).map(_.left.map(_.fail))
}

private[this] def instantiateImpl[F[_]: TagK](
plan: Plan,
parentContext: Locator,
)(implicit F: QuasiIO[F]
): F[Either[FailedProvision[F], LocatorDefaultImpl[F]]] = {
): F[Either[FailedProvisionInternal[F], LocatorDefaultImpl[F]]] = {
val integrationCheckFType = SafeType.get[IntegrationCheck[F]]

val ctx: ProvisionMutable[F] = new ProvisionMutable[F](plan, parentContext)

@nowarn("msg=Unused import")
def run(state: TraversalState, integrationPaths: Set[DIKey]): F[Either[TraversalState, Either[FailedProvision[F], LocatorDefaultImpl[F]]]] = {
def run(state: TraversalState, integrationPaths: Set[DIKey]): F[Either[TraversalState, Either[FailedProvisionInternal[F], LocatorDefaultImpl[F]]]] = {
import scala.collection.compat.*

state.current match {
Expand Down Expand Up @@ -122,7 +123,7 @@ class PlanInterpreterNonSequentialRuntimeImpl(
initial: TraversalState,
issues: Iterable[ProvisionerIssue],
)(implicit F: QuasiIO[F]
): F[Either[FailedProvision[F], A]] = {
): F[Either[FailedProvisionInternal[F], A]] = {
val failures = issues.map {
issue =>
TimedFinalResult.Failure(
Expand All @@ -139,7 +140,7 @@ class PlanInterpreterNonSequentialRuntimeImpl(
state: TraversalState,
ctx: ProvisionMutable[F],
)(implicit F: QuasiIO[F]
): F[Either[FailedProvision[F], Plan]] = {
): F[Either[FailedProvisionInternal[F], Plan]] = {
val allChecks = ctx.plan.stepsUnordered.iterator.collect {
case op: InstantiationOp if op.instanceType <:< abstractCheckType => op
}.toSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import izumi.distage.model.Locator
import izumi.distage.model.Locator.LocatorMeta
import izumi.distage.model.definition.errors.ProvisionerIssue
import izumi.distage.model.plan.Plan
import izumi.distage.model.provisioning.PlanInterpreter.{FailedProvision, FailedProvisionMeta, Finalizer}
import izumi.distage.model.provisioning.PlanInterpreter.{FailedProvision, FailedProvisionInternal, FailedProvisionMeta, Finalizer}
import izumi.distage.model.provisioning.Provision.ProvisionImmutable
import izumi.distage.model.provisioning.{NewObjectOp, Provision, ProvisioningFailure}
import izumi.distage.model.recursive.LocatorRef
Expand All @@ -28,7 +28,7 @@ final class ProvisionMutable[F[_]: TagK](
Seq(NewObjectOp.NewImport(DIKey.get[LocatorRef], locatorRef))
}

def makeFailure(state: TraversalState, fullStackTraces: Boolean): FailedProvision[F] = {
def makeFailure(state: TraversalState, fullStackTraces: Boolean): FailedProvisionInternal[F] = {
val diag = if (state.failures.isEmpty) {
ProvisioningFailure.BrokenGraph(state.preds, state.status())
} else {
Expand All @@ -37,16 +37,20 @@ final class ProvisionMutable[F[_]: TagK](
makeFailure(state, fullStackTraces, diag)
}

def makeFailure(state: TraversalState, fullStackTraces: Boolean, diag: ProvisioningFailure): FailedProvision[F] = {
def makeFailure(state: TraversalState, fullStackTraces: Boolean, diag: ProvisioningFailure): FailedProvisionInternal[F] = {
val meta = FailedProvisionMeta(state.status())

FailedProvision(
failed = toImmutable,
plan = plan,
parentContext = parentContext,
failure = diag,
meta = meta,
fullStackTraces = fullStackTraces,
val prov = toImmutable
FailedProvisionInternal(
prov,
FailedProvision(
failed = toImmutable.raw,
plan = plan,
parentContext = parentContext,
failure = diag,
meta = meta,
fullStackTraces = fullStackTraces,
),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import izumi.distage.fixtures.ResourceCases.*
import izumi.distage.injector.ResourceEffectBindingsTest.Fn
import izumi.distage.model.definition.Lifecycle
import izumi.functional.quasi.QuasiApplicative
import izumi.distage.model.exceptions.runtime.ProvisioningException
import izumi.distage.model.plan.Roots
import izumi.functional.bio.data.{Free, FreeError, FreePanic}
import izumi.fundamentals.platform.functional.Identity
Expand Down Expand Up @@ -395,9 +394,7 @@ class ResourceEffectBindingsTest extends AnyWordSpec with MkInjector with GivenW
fail(s"Unexpected success! $value")
}.unsafeRun()

val exc = intercept[ProvisioningException] {
failure.throwException().unsafeRun()
}
val exc = failure.toThrowable()

assert(exc.getMessage.contains("Incompatible effect types"))
}
Expand Down

0 comments on commit 38a6709

Please sign in to comment.