Skip to content

Commit b3b1c3b

Browse files
authored
Support fx.Private w/ fx.Supply (#1207)
`fx.Supply` is essentially an API that allows for conveniently `fx.Provide`ing an exact value, rather than a function that will return that value. For example, `fx.Provide(func() int { return 5 })` is equivalent to `fx.Supply(5)`. `fx.Private` allows for usage of a provided constructor's results to be restricted to the current module and its child modules. ```go fx.Module( "parent", fx.Invoke(func(int) { /* this will error out! */ }), fx.Module( "child", fx.Provide(func() int { return 5 }, fx.Private), ), ), ``` This PR allows for using `fx.Private` with `fx.Supply` as well, so that folks can enjoy the convenience of `fx.Supply` when they also wish to restrict the usage of the supplied value. ```go fx.Module( "parent" fx.Invoke(func(int) { /* this will error out! */ }), fx.Module( "child", fx.Supply(5, fx.Private), ), ), ``` Ref #1206 Since the behavior between Supply + Private and Provide + Private should be identical, I opted to generalize the existing `fx.Private` tests to run for both Provide and Supply. This keeps the tests a little more DRY but does complicate them/hurt readability. I feel like this is OK since there are a lot of tests, but I also am the one who wrote the tests, so I am biased regarding its readability. Thus, I am happy to break out Supply + Private into its own tests if folks feel strongly that these tests are hard to read.
1 parent 9e6f6c2 commit b3b1c3b

3 files changed

Lines changed: 222 additions & 118 deletions

File tree

app_test.go

Lines changed: 200 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -391,134 +391,227 @@ func TestNewApp(t *testing.T) {
391391
})
392392
}
393393

394-
func TestPrivateProvide(t *testing.T) {
394+
// TestPrivate tests Private when used with both fx.Provide and fx.Supply.
395+
func TestPrivate(t *testing.T) {
395396
t.Parallel()
396397

397-
t.Run("CanUsePrivateAndNonPrivateFromOuterModule", func(t *testing.T) {
398-
t.Parallel()
398+
testCases := []struct {
399+
desc string
399400

400-
app := fxtest.New(t,
401-
Module("SubModule", Invoke(func(a int, b string) {})),
402-
Provide(func() int { return 0 }, Private),
403-
Provide(func() string { return "" }),
404-
)
405-
app.RequireStart().RequireStop()
406-
})
401+
// provide is either a Supply or Provide wrapper around the given int
402+
// that allows us to generalize these test cases for both APIs.
403+
provide func(int, bool) Option
404+
}{
405+
{
406+
desc: "Provide",
407+
provide: func(i int, private bool) Option {
408+
opts := []any{func() int { return i }}
409+
if private {
410+
opts = append(opts, Private)
411+
}
412+
return Provide(opts...)
413+
},
414+
},
415+
{
416+
desc: "Supply",
417+
provide: func(i int, private bool) Option {
418+
opts := []any{i}
419+
if private {
420+
opts = append(opts, Private)
421+
}
422+
return Supply(opts...)
423+
},
424+
},
425+
}
407426

408-
t.Run("CantUsePrivateFromSubModule", func(t *testing.T) {
409-
t.Parallel()
427+
for _, tt := range testCases {
428+
t.Run(tt.desc, func(t *testing.T) {
429+
t.Parallel()
410430

411-
app := NewForTest(t,
412-
Module("SubModule", Provide(func() int { return 0 }, Private)),
413-
Invoke(func(a int) {}),
414-
)
415-
err := app.Err()
416-
require.Error(t, err)
417-
assert.Contains(t, err.Error(), "missing dependencies for function")
418-
assert.Contains(t, err.Error(), "missing type: int")
419-
})
431+
t.Run("CanUsePrivateFromParentModule", func(t *testing.T) {
432+
t.Parallel()
433+
434+
var invoked bool
435+
app := fxtest.New(t,
436+
Module("SubModule", Invoke(func(a int, b string) {
437+
assert.Equal(t, 0, a)
438+
invoked = true
439+
})),
440+
Provide(func() string { return "" }),
441+
tt.provide(0, true /* private */),
442+
)
443+
app.RequireStart().RequireStop()
444+
assert.True(t, invoked)
445+
})
420446

421-
t.Run("DifferentModulesCanProvideSamePrivateType", func(t *testing.T) {
422-
t.Parallel()
447+
t.Run("CannotUsePrivateFromSubModule", func(t *testing.T) {
448+
t.Parallel()
423449

424-
app := fxtest.New(t,
425-
Module("SubModuleA",
426-
Provide(func() int { return 1 }, Private),
427-
Invoke(func(s int) {
428-
assert.Equal(t, 1, s)
429-
}),
430-
),
431-
Module("SubModuleB",
432-
Provide(func() int { return 2 }, Private),
433-
Invoke(func(s int) {
434-
assert.Equal(t, 2, s)
435-
}),
436-
),
437-
Provide(func() int { return 3 }),
438-
Invoke(func(s int) {
439-
assert.Equal(t, 3, s)
440-
}),
441-
)
442-
app.RequireStart().RequireStop()
443-
})
450+
app := NewForTest(t,
451+
Module("SubModule", tt.provide(0, true /* private */)),
452+
Invoke(func(a int) {}),
453+
)
454+
err := app.Err()
455+
require.Error(t, err)
456+
assert.Contains(t, err.Error(), "missing dependencies for function")
457+
assert.Contains(t, err.Error(), "missing type: int")
458+
})
459+
460+
t.Run("MultipleModulesSameType", func(t *testing.T) {
461+
t.Parallel()
462+
463+
var invoked int
464+
app := fxtest.New(t,
465+
Module("SubModuleA",
466+
tt.provide(1, true /* private */),
467+
Invoke(func(s int) {
468+
assert.Equal(t, 1, s)
469+
invoked++
470+
}),
471+
),
472+
Module("SubModuleB",
473+
tt.provide(2, true /* private */),
474+
Invoke(func(s int) {
475+
assert.Equal(t, 2, s)
476+
invoked++
477+
}),
478+
),
479+
tt.provide(3, false /* private */),
480+
Invoke(func(s int) {
481+
assert.Equal(t, 3, s)
482+
invoked++
483+
}),
484+
)
485+
app.RequireStart().RequireStop()
486+
assert.Equal(t, 3, invoked)
487+
})
488+
})
489+
}
444490
}
445491

446492
func TestPrivateProvideWithDecorators(t *testing.T) {
447493
t.Parallel()
448494

449-
t.Run("DecoratedPublicOrPrivateTypeInSubModule", func(t *testing.T) {
450-
t.Parallel()
495+
testCases := []struct {
496+
desc string
451497

452-
runApp := func(private bool) {
453-
provideOpts := []interface{}{func() int { return 0 }}
454-
if private {
455-
provideOpts = append(provideOpts, Private)
456-
}
457-
app := NewForTest(t,
458-
Module("SubModule",
459-
Provide(provideOpts...),
460-
Decorate(func(a int) int { return a + 2 }),
461-
Invoke(func(a int) { assert.Equal(t, 2, a) }),
462-
),
463-
Invoke(func(a int) { assert.Equal(t, 0, a) }),
464-
)
465-
err := app.Err()
466-
if private {
467-
require.Error(t, err)
468-
assert.Contains(t, err.Error(), "missing dependencies for function")
469-
assert.Contains(t, err.Error(), "missing type: int")
470-
} else {
471-
require.NoError(t, err)
472-
}
473-
}
498+
// provide is either a Supply or Provide wrapper around the given int
499+
// that allows us to generalize these test cases for both APIs.
500+
provide func(int) Option
501+
private bool
502+
}{
503+
{
504+
desc: "Private/Provide",
505+
provide: func(i int) Option {
506+
return Provide(
507+
func() int { return i },
508+
Private,
509+
)
510+
},
511+
private: true,
512+
},
513+
{
514+
desc: "Private/Supply",
515+
provide: func(i int) Option {
516+
return Supply(i, Private)
517+
},
518+
private: true,
519+
},
520+
{
521+
desc: "Public/Provide",
522+
provide: func(i int) Option {
523+
return Provide(func() int { return i })
524+
},
525+
private: false,
526+
},
527+
{
528+
desc: "Public/Supply",
529+
provide: func(i int) Option {
530+
return Supply(i)
531+
},
532+
private: false,
533+
},
534+
}
474535

475-
t.Run("Public", func(t *testing.T) { runApp(false) })
476-
t.Run("Private", func(t *testing.T) { runApp(true) })
477-
})
536+
for _, tt := range testCases {
537+
t.Run(tt.desc, func(t *testing.T) {
538+
t.Parallel()
478539

479-
t.Run("DecoratedPublicOrPrivateTypeInOuterModule", func(t *testing.T) {
480-
t.Parallel()
540+
t.Run("DecoratedTypeInSubModule", func(t *testing.T) {
541+
t.Parallel()
481542

482-
runApp := func(private bool) {
483-
provideOpts := []interface{}{func() int { return 0 }}
484-
if private {
485-
provideOpts = append(provideOpts, Private)
486-
}
487-
app := fxtest.New(t,
488-
Provide(provideOpts...),
489-
Decorate(func(a int) int { return a - 5 }),
490-
Invoke(func(a int) {
491-
assert.Equal(t, -5, a)
492-
}),
493-
Module("Child",
494-
Decorate(func(a int) int { return a + 10 }),
543+
var invoked bool
544+
app := NewForTest(t,
545+
Module("SubModule",
546+
tt.provide(0),
547+
Decorate(func(a int) int { return a + 2 }),
548+
Invoke(func(a int) { assert.Equal(t, 2, a) }),
549+
),
495550
Invoke(func(a int) {
496-
assert.Equal(t, 5, a)
551+
// Decoration is always "private",
552+
// so raw provided value is expected here
553+
// when the submodule provides it as public.
554+
assert.Equal(t, 0, a)
555+
invoked = true
497556
}),
498-
),
499-
)
500-
app.RequireStart().RequireStop()
501-
}
557+
)
558+
err := app.Err()
559+
if tt.private {
560+
require.Error(t, err)
561+
assert.Contains(t, err.Error(), "missing dependencies for function")
562+
assert.Contains(t, err.Error(), "missing type: int")
563+
} else {
564+
require.NoError(t, err)
565+
assert.True(t, invoked)
566+
}
567+
})
502568

503-
t.Run("Public", func(t *testing.T) { runApp(false) })
504-
t.Run("Private", func(t *testing.T) { runApp(true) })
505-
})
569+
t.Run("DecoratedTypeInParentModule", func(t *testing.T) {
570+
t.Parallel()
506571

507-
t.Run("CannotDecoratePrivateChildType", func(t *testing.T) {
508-
t.Parallel()
572+
var invoked int
573+
app := fxtest.New(t,
574+
tt.provide(0),
575+
Decorate(func(a int) int { return a - 5 }),
576+
Invoke(func(a int) {
577+
assert.Equal(t, -5, a)
578+
invoked++
579+
}),
580+
Module("Child",
581+
Decorate(func(a int) int { return a + 10 }),
582+
Invoke(func(a int) {
583+
assert.Equal(t, 5, a)
584+
invoked++
585+
}),
586+
),
587+
)
588+
app.RequireStart().RequireStop()
589+
assert.Equal(t, 2, invoked)
590+
})
509591

510-
app := NewForTest(t,
511-
Module("Child",
512-
Provide(func() int { return 0 }, Private),
513-
),
514-
Decorate(func(a int) int { return a + 5 }),
515-
Invoke(func(a int) {}),
516-
)
517-
err := app.Err()
518-
require.Error(t, err)
519-
assert.Contains(t, err.Error(), "missing dependencies for function")
520-
assert.Contains(t, err.Error(), "missing type: int")
521-
})
592+
t.Run("ParentDecorateChildType", func(t *testing.T) {
593+
t.Parallel()
594+
595+
var invoked bool
596+
app := NewForTest(t,
597+
Module("Child", tt.provide(0)),
598+
Decorate(func(a int) int { return a + 5 }),
599+
Invoke(func(a int) {
600+
invoked = true
601+
}),
602+
)
603+
err := app.Err()
604+
if tt.private {
605+
require.Error(t, err)
606+
assert.Contains(t, err.Error(), "missing dependencies for function")
607+
assert.Contains(t, err.Error(), "missing type: int")
608+
} else {
609+
require.NoError(t, err)
610+
assert.True(t, invoked)
611+
}
612+
})
613+
})
614+
}
522615
}
523616

524617
func TestWithLoggerErrorUseDefault(t *testing.T) {

provide.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (o provideOption) apply(mod *module) {
9696

9797
type privateOption struct{}
9898

99-
// Private is an option that can be passed as an argument to [Provide] to
99+
// Private is an option that can be passed as an argument to [Provide] or [Supply] to
100100
// restrict access to the constructors being provided. Specifically,
101101
// corresponding constructors can only be used within the current module
102102
// or modules the current module contains. Other modules that contain this

0 commit comments

Comments
 (0)