Skip to content

Commit

Permalink
#1968: locator-private bindings PoC
Browse files Browse the repository at this point in the history
  • Loading branch information
pshirshov committed Aug 8, 2024
1 parent 5e87bfb commit 22efba6
Show file tree
Hide file tree
Showing 16 changed files with 139 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package izumi.distage.model

import izumi.distage.model.definition.errors.DIError
import izumi.distage.model.definition.{Activation, ModuleBase}
import izumi.distage.model.definition.{Activation, LocatorPrivacy, ModuleBase}
import izumi.distage.model.plan.*
import izumi.fundamentals.collections.nonempty.NEList

/** Transforms [[izumi.distage.model.definition.ModuleDef]] into [[izumi.distage.model.plan.Plan]] */
trait Planner {
def plan(input: PlannerInput): Either[NEList[DIError], Plan]

@inline final def plan(bindings: ModuleBase, activation: Activation, roots: Roots): Either[NEList[DIError], Plan] = {
plan(PlannerInput(bindings, activation, roots))
@inline final def plan(bindings: ModuleBase, activation: Activation, roots: Roots, locatorPrivacy: LocatorPrivacy): Either[NEList[DIError], Plan] = {
plan(PlannerInput(bindings, activation, roots, locatorPrivacy))
}

@inline final def plan(bindings: ModuleBase, roots: Roots): Either[NEList[DIError], Plan] = {
plan(bindings, Activation.empty, roots)
plan(bindings, Activation.empty, roots, LocatorPrivacy.PublicByDefault)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package izumi.distage.model

import izumi.distage.model.definition.{Activation, ModuleBase}
import izumi.distage.model.definition.{Activation, LocatorPrivacy, ModuleBase}
import izumi.distage.model.plan.Roots
import izumi.distage.model.reflection.DIKey
import izumi.fundamentals.collections.nonempty.NESet
Expand All @@ -25,29 +25,39 @@ final case class PlannerInput(
bindings: ModuleBase,
activation: Activation,
roots: Roots,
locatorPrivacy: LocatorPrivacy,
)

object PlannerInput {
implicit class PlannerInputSyntax(private val input: PlannerInput) extends AnyVal {
def privateByDefault: PlannerInput = input.copy(locatorPrivacy = LocatorPrivacy.PrivateByDefault)
def publicByDefault: PlannerInput = input.copy(locatorPrivacy = LocatorPrivacy.PublicByDefault)
def withLocatorPrivacy(privacy: LocatorPrivacy): PlannerInput = input.copy(locatorPrivacy = privacy)
}

def apply(bindings: ModuleBase, activation: Activation, roots: Roots): PlannerInput = PlannerInput(bindings, activation, roots, LocatorPrivacy.PublicByDefault)
/**
* Instantiate `roots` and the dependencies of `roots`, discarding bindings that are unrelated.
*
* Effectively, this selects and creates a *sub-graph* of the largest possible object graph that can be described by `bindings`
*/
def apply(bindings: ModuleBase, activation: Activation, roots: NESet[? <: DIKey]): PlannerInput = PlannerInput(bindings, activation, Roots(roots))
def apply(bindings: ModuleBase, activation: Activation, roots: NESet[? <: DIKey]): PlannerInput =
PlannerInput(bindings, activation, Roots(roots), LocatorPrivacy.PublicByDefault)

/**
* Instantiate `roots` and the dependencies of `roots`, discarding bindings that are unrelated.
*
* Effectively, this selects and creates a *sub-graph* of the largest possible object graph that can be described by `bindings`
*/
def apply(bindings: ModuleBase, activation: Activation, roots: Set[? <: DIKey])(implicit d: DummyImplicit): PlannerInput =
PlannerInput(bindings, activation, Roots(roots))
PlannerInput(bindings, activation, Roots(roots), LocatorPrivacy.PublicByDefault)

/** Instantiate `root`, `roots` and their dependencies, discarding bindings that are unrelated.
*
* Effectively, this selects and creates a *sub-graph* of the largest possible object graph that can be described by `bindings`
*/
def apply(bindings: ModuleBase, activation: Activation, root: DIKey, roots: DIKey*): PlannerInput = PlannerInput(bindings, activation, Roots(root, roots*))
def apply(bindings: ModuleBase, activation: Activation, root: DIKey, roots: DIKey*): PlannerInput =
PlannerInput(bindings, activation, Roots(root, roots*), LocatorPrivacy.PublicByDefault)

/** Instantiate `T` and the dependencies of `T`, discarding bindings that are unrelated.
*
Expand All @@ -64,5 +74,6 @@ object PlannerInput {
def target[T: Tag](name: String)(bindings: ModuleBase, activation: Activation): PlannerInput = PlannerInput(bindings, activation, DIKey.get[T].named(name))

/** Disable all dependency pruning. Every binding in `bindings` will be instantiated, without selection of the root components. There's almost always a better way to model things though. */
def everything(bindings: ModuleBase, activation: Activation = Activation.empty): PlannerInput = PlannerInput(bindings, activation, Roots.Everything)
def everything(bindings: ModuleBase, activation: Activation = Activation.empty): PlannerInput =
PlannerInput(bindings, activation, Roots.Everything, LocatorPrivacy.PublicByDefault)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ object BindingTag {
final case class AxisTag(choice: AxisChoice) extends BindingTag

final case object Confined extends BindingTag
final case object Exposed extends BindingTag
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ trait LocatorDef extends AbstractLocator with AbstractBindingDefDSL[LocatorDef.B

val s = IncidenceMatrix(ops.map(op => (op._1.target, Set.empty[DIKey])).toMap)
val nodes = ops.map(op => (op._1.target, op._1))
Plan(DG(s, s.transposed, GraphMeta(nodes.toMap)), PlannerInput(Module.make(ops.map(_._2).toSet), Activation.empty, Roots.Everything))
Plan(
DG(s, s.transposed, GraphMeta(nodes.toMap)),
PlannerInput(Module.make(ops.map(_._2).toSet), Activation.empty, Roots.Everything, LocatorPrivacy.PublicByDefault),
)
}

override def parent: Option[Locator] = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package izumi.distage.model.definition

sealed trait LocatorPrivacy

object LocatorPrivacy {
case object PublicByDefault extends LocatorPrivacy
case object PrivateByDefault extends LocatorPrivacy
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ trait Tagging[Self] extends Any {
def tagged(tags: BindingTag*): Self

def confined: Self = tagged(BindingTag.Confined)
def exposed: Self = tagged(BindingTag.Exposed)
}
3 changes: 3 additions & 0 deletions distage/distage-core/src/main/scala/distage/Distage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ trait Distage {
type Activation = model.definition.Activation
val Activation: model.definition.Activation.type = model.definition.Activation

type LocatorPrivacy = model.definition.LocatorPrivacy
val LocatorPrivacy: model.definition.LocatorPrivacy.type = model.definition.LocatorPrivacy

type Roots = model.plan.Roots
val Roots: model.plan.Roots.type = model.plan.Roots

Expand Down
13 changes: 8 additions & 5 deletions distage/distage-core/src/main/scala/distage/Injector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ object Injector extends InjectorFactory {
override def apply[F[_]: QuasiIO: TagK: DefaultModule](
overrides: BootstrapModule*
): Injector[F] = {
bootstrap(this, defaultBootstrap, defaultBootstrapActivation, None, overrides)
bootstrap(this, defaultBootstrap, defaultBootstrapActivation, None, overrides, LocatorPrivacy.PublicByDefault)
}

/**
Expand All @@ -48,8 +48,9 @@ object Injector extends InjectorFactory {
bootstrapActivation: Activation = defaultBootstrapActivation,
parent: Option[Locator] = None,
overrides: Seq[BootstrapModule] = Nil,
locatorPrivacy: LocatorPrivacy = LocatorPrivacy.PublicByDefault,
): Injector[F] = {
bootstrap(this, bootstrapBase, defaultBootstrapActivation ++ bootstrapActivation, parent, overrides)
bootstrap(this, bootstrapBase, defaultBootstrapActivation ++ bootstrapActivation, parent, overrides, locatorPrivacy)
}

/**
Expand Down Expand Up @@ -119,16 +120,17 @@ object Injector extends InjectorFactory {
cycleChoice: Cycles.AxisChoiceDef
) extends InjectorFactory {
override final def apply[F[_]: QuasiIO: TagK: DefaultModule](overrides: BootstrapModule*): Injector[F] = {
bootstrap(this, defaultBootstrap, defaultBootstrapActivation, None, overrides)
bootstrap(this, defaultBootstrap, defaultBootstrapActivation, None, overrides, LocatorPrivacy.PublicByDefault)
}

override final def apply[F[_]: QuasiIO: TagK: DefaultModule](
bootstrapBase: BootstrapContextModule,
bootstrapActivation: Activation,
parent: Option[Locator],
overrides: Seq[BootstrapModule],
locatorPrivacy: LocatorPrivacy,
): Injector[F] = {
bootstrap(this, bootstrapBase, defaultBootstrapActivation ++ bootstrapActivation, parent, overrides)
bootstrap(this, bootstrapBase, defaultBootstrapActivation ++ bootstrapActivation, parent, overrides, locatorPrivacy)
}

override final def apply(): Injector[Identity] = apply[Identity]()
Expand Down Expand Up @@ -159,8 +161,9 @@ object Injector extends InjectorFactory {
activation: Activation,
parent: Option[Locator],
overrides: Seq[BootstrapModule],
locatorPrivacy: LocatorPrivacy,
): Injector[F] = {
val bootstrapLocator = BootstrapLocator.bootstrap(bootstrapBase, activation, overrides, parent)
val bootstrapLocator = BootstrapLocator.bootstrap(bootstrapBase, activation, overrides, parent, locatorPrivacy)
inheritWithNewDefaultModuleImpl(injectorFactory, bootstrapLocator, implicitly)
}

Expand Down
3 changes: 3 additions & 0 deletions distage/distage-core/src/main/scala/distage/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ package object distage extends Distage {
override type Activation = model.definition.Activation
override val Activation: model.definition.Activation.type = model.definition.Activation

override type LocatorPrivacy = model.definition.LocatorPrivacy
override val LocatorPrivacy: model.definition.LocatorPrivacy.type = model.definition.LocatorPrivacy

override type Roots = model.plan.Roots
override val Roots: model.plan.Roots.type = model.plan.Roots

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package izumi.distage

import distage.LocatorPrivacy
import izumi.distage.model.definition.{Activation, BootstrapContextModule, BootstrapModule}
import izumi.functional.quasi.QuasiIO
import izumi.distage.model.recursive.Bootloader
Expand Down Expand Up @@ -45,6 +46,7 @@ trait InjectorFactory {
bootstrapActivation: Activation = defaultBootstrapActivation,
parent: Option[Locator] = None,
overrides: Seq[BootstrapModule] = Nil,
locatorPrivacy: LocatorPrivacy,
): Injector[F]

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ object BootstrapLocator {
bootstrapActivation: Activation,
overrides: Seq[BootstrapModule],
parent: Option[Locator],
locatorPrivacy: LocatorPrivacy,
): Locator = {
val bindings0 = bootstrapBase overriddenBy overrides.merge
// BootstrapModule & bootstrap plugins cannot modify `Activation` after 1.0, it's solely under control of `PlannerInput` now.
Expand All @@ -49,7 +50,7 @@ object BootstrapLocator {

val plan =
BootstrapLocator.bootstrapPlanner
.plan(bindings, bootstrapActivation, Roots.Everything).getOrThrow()
.plan(bindings, bootstrapActivation, Roots.Everything, locatorPrivacy).getOrThrow()

val resource =
BootstrapLocator.bootstrapProducer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package izumi.distage.model

import izumi.distage.model.definition.Axis.AxisChoice
import izumi.distage.model.definition.{Activation, Identifier, Lifecycle, ModuleBase}
import izumi.distage.model.definition.{Activation, Identifier, Lifecycle, LocatorPrivacy, ModuleBase}
import izumi.functional.quasi.QuasiIO
import izumi.distage.model.plan.{Plan, Roots}
import izumi.distage.model.providers.Functoid
Expand Down Expand Up @@ -189,8 +189,13 @@ trait Injector[F[_]] extends Planner with Producer {
final def produce(input: PlannerInput): Lifecycle[F, Locator] = {
produceCustomF[F](input)
}
final def produce(bindings: ModuleBase, roots: Roots, activation: Activation = Activation.empty): Lifecycle[F, Locator] = {
produce(PlannerInput(bindings, activation, roots))
final def produce(
bindings: ModuleBase,
roots: Roots,
activation: Activation = Activation.empty,
locatorPrivacy: LocatorPrivacy = LocatorPrivacy.PublicByDefault,
): Lifecycle[F, Locator] = {
produce(PlannerInput(bindings, activation, roots, locatorPrivacy))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package izumi.distage.model.recursive

import izumi.distage.InjectorFactory
import izumi.distage.model.definition.errors.DIError
import izumi.distage.model.definition.{Activation, BootstrapModule, Id, Module, ModuleBase}
import izumi.distage.model.definition.{Activation, BootstrapModule, Id, LocatorPrivacy, Module, ModuleBase}
import izumi.functional.quasi.QuasiIO
import izumi.distage.model.plan.{Plan, Roots}
import izumi.distage.model.{Injector, PlannerInput}
Expand All @@ -23,6 +23,7 @@ final case class BootConfig(
activation: Activation => Activation = identity,
bootstrapActivation: Activation => Activation = identity,
roots: Roots => Roots = identity,
locatorPrivacy: LocatorPrivacy => LocatorPrivacy = identity,
)

class Bootloader(
Expand All @@ -35,9 +36,12 @@ class Bootloader(
def boot(config: BootConfig): Either[NEList[DIError], BootstrappedApp] = {
val activation = config.activation(input.activation)
val bootstrap = config.bootstrap(bootstrapModule)
val locatorPrivacy = config.locatorPrivacy(input.locatorPrivacy)

val injector = injectorFactory(
bootstrapActivation = config.bootstrapActivation(bootstrapActivation),
overrides = Seq(bootstrap),
locatorPrivacy = locatorPrivacy,
)(
QuasiIO[Identity],
TagK[Identity],
Expand All @@ -47,7 +51,7 @@ class Bootloader(
val roots = config.roots(input.roots)

for {
plan <- injector.plan(PlannerInput(module, activation, roots))
plan <- injector.plan(PlannerInput(module, activation, roots, locatorPrivacy))
} yield {
BootstrappedApp(injector, module, plan)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package izumi.distage.provisioning

import izumi.distage.LocatorDefaultImpl
import izumi.distage.model.definition.{BindingTag, Id, Lifecycle}
import izumi.distage.model.definition.{Binding, BindingTag, Id, Lifecycle, LocatorPrivacy}
import izumi.distage.model.definition.errors.ProvisionerIssue
import izumi.distage.model.definition.errors.ProvisionerIssue.IncompatibleEffectTypes
import izumi.distage.model.definition.errors.ProvisionerIssue.ProvisionerExceptionIssue.{IntegrationCheckFailure, UnexpectedIntegrationCheck}
Expand Down Expand Up @@ -63,18 +63,7 @@ class PlanInterpreterNonSequentialRuntimeImpl(
): F[Either[FailedProvisionInternal[F], LocatorDefaultImpl[F]]] = {
val integrationCheckFType = SafeType.get[IntegrationCheck[F]]

val privateBindings = plan.stepsUnordered
.filter {
op =>
op.origin.value match {
case OperationOrigin.UserBinding(binding) =>
binding.tags.contains(BindingTag.Confined)
case OperationOrigin.SyntheticBinding(binding) =>
binding.tags.contains(BindingTag.Confined)
case OperationOrigin.Unknown =>
false // TODO: true if everyting is private
}
}.map(_.target).toSet
val privateBindings = computePrivateBindings(plan)

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

Expand Down Expand Up @@ -132,6 +121,30 @@ class PlanInterpreterNonSequentialRuntimeImpl(
} yield res
}

private def computePrivateBindings(plan: Plan): Set[DIKey] = {
val privacy = plan.input.locatorPrivacy
val defaultPublic = privacy == LocatorPrivacy.PublicByDefault
val defaultPrivate = privacy == LocatorPrivacy.PrivateByDefault

def isPrivate(binding: Binding) = {
(defaultPublic && binding.tags.contains(BindingTag.Confined)) || (defaultPrivate && !binding.tags.contains(BindingTag.Exposed))
}

plan.stepsUnordered
.filter {
op =>
op.origin.value match {
case OperationOrigin.UserBinding(binding) =>
isPrivate(binding)
case OperationOrigin.SyntheticBinding(binding) =>
isPrivate(binding)
case OperationOrigin.Unknown =>
defaultPrivate
}
}
.map(_.target).toSet
}

private def failEarly[F[_]: TagK, A](
ctx: ProvisionMutable[F],
initial: TraversalState,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package izumi.distage.impl

import distage.{Activation, DIKey}
import distage.{Activation, DIKey, LocatorPrivacy}
import izumi.distage.bootstrap.{BootstrapLocator, Cycles}
import izumi.distage.model.exceptions.runtime.MissingInstanceException
import izumi.distage.planning.solver.PlanSolver
Expand All @@ -10,7 +10,7 @@ class BootstrapTest extends AnyWordSpec {

"Bootstrap Context" should {
"contain expected definitions" in {
val context = BootstrapLocator.bootstrap(BootstrapLocator.defaultBootstrap, Activation(Cycles -> Cycles.Byname), Nil, None)
val context = BootstrapLocator.bootstrap(BootstrapLocator.defaultBootstrap, Activation(Cycles -> Cycles.Byname), Nil, None, LocatorPrivacy.PublicByDefault)

val maybeRef = context.find[PlanSolver]
val ref = context.lookupLocal[PlanSolver](DIKey.get[PlanSolver])
Expand Down
Loading

0 comments on commit 22efba6

Please sign in to comment.