Skip to content

Commit

Permalink
Support annotated provides with fx.Annotated (#656)
Browse files Browse the repository at this point in the history
This adds a special `fx.Annotated` type which, when provided with
`fx.Provide`, translates to a regular dig Provide with additional
options.

This change includes support for named values with fx.Annotated, making
it possible to provide named values without rewriting constructors.

A future change will add support for value groups.

Resolves #610
  • Loading branch information
srikrsna authored and abhinav committed Nov 8, 2018
1 parent 9c82e1d commit bd9897f
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Add `fx.Annotated` to allow users to provide named values without creating a new constructor.

## [1.8.0] - 2018-11-06
### Added
- Provide DOT graph of dependencies in the container.
Expand Down
52 changes: 52 additions & 0 deletions annotated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fx

// Annotated is a special provide type that specifies that all values produced by a
// constructor should have the given name. See also In and Out documentation
// about Named Values.
//
// Given,
//
// func NewReadOnlyConnection(...) (*Connection, error)
// func NewReadWriteConnection(...) (*Connection, error)
//
// The following will provide two connections to the container: one under the
// name "ro" and the other under the name "rw".
//
// fx.Provide(
// fx.Annotated{
// Name: "ro",
// Fn: NewReadOnlyConnection,
// },
// fx.Annotated{
// Name: "rw",
// Fn: NewReadOnlyConnection,
// },
// )
//
// This option cannot be provided for constructors which produce result
// objects.
//
type Annotated struct {
Name string
Fn interface{}
}
102 changes: 102 additions & 0 deletions annotated_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fx_test

import (
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/fx"
"go.uber.org/fx/fxtest"
)

func TestAnnotated(t *testing.T) {
type a struct {
name string
}
type in struct {
fx.In

A *a `name:"foo"`
}
newA := func() *a {
return &a{name: "foo"}
}
t.Run("Provided", func(t *testing.T) {
var in in
app := fxtest.New(t,
fx.Provide(
fx.Annotated{
Name: "foo",
Fn: newA,
},
),
fx.Populate(&in),
)
defer app.RequireStart().RequireStop()
assert.NotNil(t, in.A, "expected in.A to be injected")
assert.Equal(t, "foo", in.A.name, "expected to get a type 'a' of name 'foo'")
})
}

func TestAnnotatedWrongUsage(t *testing.T) {
type a struct {
name string
}
type in struct {
fx.In

A *a `name:"foo"`
}
newA := func() *a {
return &a{name: "foo"}
}

t.Run("In Constructor", func(t *testing.T) {
var in in
app := fx.New(
fx.Provide(
func() fx.Annotated {
return fx.Annotated{
Name: "foo",
Fn: newA,
}
},
),
fx.Populate(&in),
)
assert.Contains(t, app.Err().Error(), "fx.Annotated should be passed to fx.Provide directly", "expected error when return types were annotated")
})

t.Run("Result Type", func(t *testing.T) {
app := fx.New(
fx.Provide(
fx.Annotated{
Name: "foo",
Fn: func() in {
return in{A: &a{name: "foo"}}
},
},
),
)
assert.Contains(t, app.Err().Error(), "embeds a dig.In", "expected error when result types were annotated")
})
}
21 changes: 21 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"fmt"
"os"
"os/signal"
"reflect"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -481,6 +482,26 @@ func (app *App) provide(constructor interface{}) {
return
}

if a, ok := constructor.(Annotated); ok {
if err := app.container.Provide(a.Fn, dig.Name(a.Name)); err != nil {
app.err = err
}
return
}

if reflect.TypeOf(constructor).Kind() == reflect.Func {
ft := reflect.ValueOf(constructor).Type()

for i := 0; i < ft.NumOut(); i++ {
t := ft.Out(i)

if t == reflect.TypeOf(Annotated{}) {
app.err = fmt.Errorf("fx.Annotated should be passed to fx.Provide directly, it should not be returned by the constructor: fx.Provide received %v", constructor)
return
}
}
}

if err := app.container.Provide(constructor); err != nil {
app.err = err
}
Expand Down

0 comments on commit bd9897f

Please sign in to comment.