@@ -5,11 +5,68 @@ import (
55 "context"
66 "errors"
77 "io"
8+ "slices"
9+ "strconv"
810 "sync/atomic"
911
1012 "github.com/go-modkit/modkit/modkit/module"
1113)
1214
15+ type bootstrapConfig struct {
16+ providerOverrides []ProviderOverride
17+ firstOptionByTok map [module.Token ]int
18+ optionNames map [module.Token ][]string
19+ currentOptionIdx int
20+ err error
21+ }
22+
23+ func newBootstrapConfig () bootstrapConfig {
24+ return bootstrapConfig {
25+ providerOverrides : make ([]ProviderOverride , 0 ),
26+ firstOptionByTok : make (map [module.Token ]int ),
27+ optionNames : make (map [module.Token ][]string ),
28+ }
29+ }
30+
31+ func (c * bootstrapConfig ) setCurrentOption (index int ) {
32+ c .currentOptionIdx = index
33+ }
34+
35+ func (c * bootstrapConfig ) addProviderOverrides (overrides []ProviderOverride ) {
36+ if c .err != nil {
37+ return
38+ }
39+
40+ seenInThisOption := make (map [module.Token ]bool )
41+ optionName := c .providerOverrideOptionName (c .currentOptionIdx )
42+ for _ , override := range overrides {
43+ if override .Build == nil {
44+ c .err = & OverrideBuildNilError {Token : override .Token }
45+ return
46+ }
47+
48+ if seenInThisOption [override .Token ] {
49+ c .err = & DuplicateOverrideTokenError {Token : override .Token }
50+ return
51+ }
52+ seenInThisOption [override .Token ] = true
53+
54+ if firstIdx , ok := c .firstOptionByTok [override .Token ]; ok && firstIdx != c .currentOptionIdx {
55+ names := append (slices .Clone (c .optionNames [override .Token ]), optionName )
56+ c .err = & BootstrapOptionConflictError {Token : override .Token , Options : names }
57+ return
58+ }
59+
60+ c .firstOptionByTok [override .Token ] = c .currentOptionIdx
61+ c .optionNames [override .Token ] = append (c .optionNames [override .Token ], optionName )
62+ c .providerOverrides = append (c .providerOverrides , override )
63+ }
64+ }
65+
66+ func (c * bootstrapConfig ) providerOverrideOptionName (index int ) string {
67+ return "WithProviderOverrides#" + strconv .Itoa (index + 1 )
68+ }
69+
1370// App represents a bootstrapped modkit application with its dependency graph,
1471// container, and instantiated controllers.
1572type App struct {
@@ -28,6 +85,11 @@ func controllerKey(moduleName, controllerName string) string {
2885// It builds the module graph, validates dependencies, creates the DI container,
2986// and instantiates all controllers.
3087func Bootstrap (root module.Module ) (* App , error ) {
88+ return BootstrapWithOptions (root )
89+ }
90+
91+ // BootstrapWithOptions constructs a modkit application from a root module and explicit bootstrap options.
92+ func BootstrapWithOptions (root module.Module , opts ... BootstrapOption ) (* App , error ) {
3193 graph , err := BuildGraph (root )
3294 if err != nil {
3395 return nil , err
@@ -38,11 +100,38 @@ func Bootstrap(root module.Module) (*App, error) {
38100 return nil , err
39101 }
40102
41- container , err := newContainer (graph , visibility )
103+ cfg := newBootstrapConfig ()
104+ for idx , opt := range opts {
105+ if opt == nil {
106+ return nil , & NilBootstrapOptionError {Index : idx }
107+ }
108+ cfg .setCurrentOption (idx )
109+ opt .apply (& cfg )
110+ if cfg .err != nil {
111+ return nil , cfg .err
112+ }
113+ }
114+
115+ providers , err := providerEntriesFromGraph (graph )
42116 if err != nil {
43117 return nil , err
44118 }
45119
120+ for _ , override := range cfg .providerOverrides {
121+ entry , ok := providers [override .Token ]
122+ if ! ok {
123+ return nil , & OverrideTokenNotFoundError {Token : override .Token }
124+ }
125+ if ! visibility [graph.Root ][override.Token ] {
126+ return nil , & OverrideTokenNotVisibleFromRootError {Root : graph .Root , Token : override .Token }
127+ }
128+ entry .build = override .Build
129+ entry .cleanup = override .Cleanup
130+ providers [override .Token ] = entry
131+ }
132+
133+ container := newContainerWithProviders (providers , visibility )
134+
46135 controllers := make (map [string ]any )
47136 perModule := make (map [string ]map [string ]bool )
48137 for i := range graph .Modules {
0 commit comments