uber-go / dig
Showing 2 of 11 files from the diff.

@@ -22,6 +22,7 @@
Loading
22 22
23 23
import (
24 24
	"errors"
25 +
	"fmt"
25 26
	"reflect"
26 27
27 28
	"go.uber.org/dig/internal/digerror"
@@ -37,6 +38,7 @@
Loading
37 38
//                 another result.
38 39
//   resultGrouped A value produced by a constructor that is part of a value
39 40
//                 group.
41 +
40 42
type result interface {
41 43
	// Extracts the values for this result from the provided value and
42 44
	// stores them into the provided containerWriter.
@@ -61,6 +63,7 @@
Loading
61 63
	// For Result Objects, name:".." tags on fields override this.
62 64
	Name  string
63 65
	Group string
66 +
	As    []interface{}
64 67
}
65 68
66 69
// newResult builds a result from the given type.
@@ -97,7 +100,7 @@
Loading
97 100
		}
98 101
		return rg, nil
99 102
	default:
100 -
		return resultSingle{Type: t, Name: opts.Name}, nil
103 +
		return newResultSingle(t, opts)
101 104
	}
102 105
}
103 106
@@ -190,14 +193,15 @@
Loading
190 193
}
191 194
192 195
func newResultList(ctype reflect.Type, opts resultOptions) (resultList, error) {
196 +
	numOut := ctype.NumOut()
193 197
	rl := resultList{
194 198
		ctype:         ctype,
195 -
		Results:       make([]result, 0, ctype.NumOut()),
196 -
		resultIndexes: make([]int, ctype.NumOut()),
199 +
		Results:       make([]result, 0, numOut),
200 +
		resultIndexes: make([]int, numOut),
197 201
	}
198 202
199 203
	resultIdx := 0
200 -
	for i := 0; i < ctype.NumOut(); i++ {
204 +
	for i := 0; i < numOut; i++ {
201 205
		t := ctype.Out(i)
202 206
		if isError(t) {
203 207
			rl.resultIndexes[i] = -1
@@ -243,21 +247,69 @@
Loading
243 247
type resultSingle struct {
244 248
	Name string
245 249
	Type reflect.Type
250 +
251 +
	// If specified, this is a list of types which the value will be made
252 +
	// available as, in addition to its own type.
253 +
	As []reflect.Type
254 +
}
255 +
256 +
func newResultSingle(t reflect.Type, opts resultOptions) (resultSingle, error) {
257 +
	r := resultSingle{
258 +
		Type: t,
259 +
		Name: opts.Name,
260 +
	}
261 +
262 +
	var asTypes []reflect.Type
263 +
264 +
	for _, as := range opts.As {
265 +
		ifaceType := reflect.TypeOf(as).Elem()
266 +
		if ifaceType == t {
267 +
			// Special case:
268 +
			//   c.Provide(func() io.Reader, As(new(io.Reader)))
269 +
			// Ignore instead of erroring out.
270 +
			continue
271 +
		}
272 +
		if !t.Implements(ifaceType) {
273 +
			return r, fmt.Errorf("invalid dig.As: %v does not implement %v", t, ifaceType)
274 +
		}
275 +
		asTypes = append(asTypes, ifaceType)
276 +
	}
277 +
278 +
	if len(asTypes) == 0 {
279 +
		return r, nil
280 +
	}
281 +
282 +
	return resultSingle{
283 +
		Type: asTypes[0],
284 +
		Name: opts.Name,
285 +
		As:   asTypes[1:],
286 +
	}, nil
246 287
}
247 288
248 289
func (rs resultSingle) DotResult() []*dot.Result {
249 -
	return []*dot.Result{
250 -
		{
251 -
			Node: &dot.Node{
252 -
				Type: rs.Type,
253 -
				Name: rs.Name,
254 -
			},
290 +
	dotResults := make([]*dot.Result, 0, len(rs.As)+1)
291 +
	dotResults = append(dotResults, &dot.Result{
292 +
		Node: &dot.Node{
293 +
			Type: rs.Type,
294 +
			Name: rs.Name,
255 295
		},
296 +
	})
297 +
298 +
	for _, asType := range rs.As {
299 +
		dotResults = append(dotResults, &dot.Result{
300 +
			Node: &dot.Node{Type: asType, Name: rs.Name},
301 +
		})
256 302
	}
303 +
304 +
	return dotResults
257 305
}
258 306
259 307
func (rs resultSingle) Extract(cw containerWriter, v reflect.Value) {
260 308
	cw.setValue(rs.Name, rs.Type, v)
309 +
310 +
	for _, asType := range rs.As {
311 +
		cw.setValue(rs.Name, asType, v)
312 +
	}
261 313
}
262 314
263 315
// resultObject is a dig.Out struct where each field is another result.

@@ -63,14 +63,20 @@
Loading
63 63
	Name     string
64 64
	Group    string
65 65
	Info     *ProvideInfo
66 +
	As       []interface{}
66 67
	Location *digreflect.Func
67 68
}
68 69
69 70
func (o *provideOptions) Validate() error {
70 -
	if len(o.Group) > 0 && len(o.Name) > 0 {
71 -
		return errf(
72 -
			"cannot use named values with value groups",
73 -
			"name:%q provided with group:%q", o.Name, o.Group)
71 +
	if len(o.Group) > 0 {
72 +
		if len(o.Name) > 0 {
73 +
			return fmt.Errorf(
74 +
				"cannot use named values with value groups: name:%q provided with group:%q", o.Name, o.Group)
75 +
		}
76 +
		if len(o.As) > 0 {
77 +
			return fmt.Errorf(
78 +
				"cannot use dig.As with value groups: dig.As provided with group:%q", o.Group)
79 +
		}
74 80
	}
75 81
76 82
	// Names must be representable inside a backquoted string. The only
@@ -83,6 +89,23 @@
Loading
83 89
	if strings.ContainsRune(o.Group, '`') {
84 90
		return errf("invalid dig.Group(%q): group names cannot contain backquotes", o.Group)
85 91
	}
92 +
93 +
	for _, i := range o.As {
94 +
		t := reflect.TypeOf(i)
95 +
96 +
		if t == nil {
97 +
			return fmt.Errorf("invalid dig.As(nil): argument must be a pointer to an interface")
98 +
		}
99 +
100 +
		if t.Kind() != reflect.Ptr {
101 +
			return fmt.Errorf("invalid dig.As(%v): argument must be a pointer to an interface", t)
102 +
		}
103 +
104 +
		pointingTo := t.Elem()
105 +
		if pointingTo.Kind() != reflect.Interface {
106 +
			return fmt.Errorf("invalid dig.As(*%v): argument must be a pointer to an interface", pointingTo)
107 +
		}
108 +
	}
86 109
	return nil
87 110
}
88 111
@@ -199,6 +222,54 @@
Loading
199 222
	})
200 223
}
201 224
225 +
// As is a ProvideOption that specifies that the value produced by the
226 +
// constructor implements one or more other interfaces and is provided
227 +
// to the container as those interfaces.
228 +
//
229 +
// As expects one or more pointers to the implemented interfaces. Values
230 +
// produced by constructors will be then available in the container as
231 +
// implementations of all of those interfaces, but not as the value itself.
232 +
//
233 +
// For example, the following will make io.Reader and io.Writer available
234 +
// in the container, but not buffer.
235 +
//
236 +
//   c.Provide(newBuffer, dig.As(new(io.Reader), new(io.Writer)))
237 +
//
238 +
// That is, the above is equivalent to the following.
239 +
//
240 +
//   c.Provide(func(...) (io.Reader, io.Writer) {
241 +
//     b := newBuffer(...)
242 +
//     return b, b
243 +
//   })
244 +
//
245 +
// If used with dig.Name, the type produced by the constructor and the types
246 +
// specified with dig.As will all use the same name. For example,
247 +
//
248 +
//   c.Provide(newFile, dig.As(new(io.Reader)), dig.Name("temp"))
249 +
//
250 +
// The above is equivalent to the following.
251 +
//
252 +
//   type Result struct {
253 +
//     dig.Out
254 +
//
255 +
//     Reader io.Reader `name:"temp"`
256 +
//   }
257 +
//
258 +
//   c.Provide(func(...) Result {
259 +
//     f := newFile(...)
260 +
//     return Result{
261 +
//       Reader: f,
262 +
//     }
263 +
//   })
264 +
//
265 +
// This option cannot be provided for constructors which produce result
266 +
// objects.
267 +
func As(i ...interface{}) ProvideOption {
268 +
	return provideOptionFunc(func(opts *provideOptions) {
269 +
		opts.As = append(opts.As, i...)
270 +
	})
271 +
}
272 +
202 273
// LocationForPC is a ProvideOption which specifies an alternate function program
203 274
// counter address to be used for debug information. The package, name, file and
204 275
// line number of this alternate function address will be used in error messages
@@ -551,6 +622,7 @@
Loading
551 622
		nodeOptions{
552 623
			ResultName:  opts.Name,
553 624
			ResultGroup: opts.Group,
625 +
			ResultAs:    opts.As,
554 626
			Location:    opts.Location,
555 627
		},
556 628
	)
@@ -688,32 +760,22 @@
Loading
688 760
	path := strings.Join(cv.currentResultPath, ".")
689 761
690 762
	switch r := res.(type) {
763 +
691 764
	case resultSingle:
692 765
		k := key{name: r.Name, t: r.Type}
693 766
694 -
		if conflict, ok := cv.keyPaths[k]; ok {
695 -
			*cv.err = errf(
696 -
				"cannot provide %v from %v", k, path,
697 -
				"already provided by %v", conflict,
698 -
			)
767 +
		if err := cv.checkKey(k, path); err != nil {
768 +
			*cv.err = err
699 769
			return nil
700 770
		}
701 -
702 -
		if ps := cv.c.providers[k]; len(ps) > 0 {
703 -
			cons := make([]string, len(ps))
704 -
			for i, p := range ps {
705 -
				cons[i] = fmt.Sprint(p.Location())
771 +
		for _, asType := range r.As {
772 +
			k := key{name: r.Name, t: asType}
773 +
			if err := cv.checkKey(k, path); err != nil {
774 +
				*cv.err = err
775 +
				return nil
706 776
			}
707 -
708 -
			*cv.err = errf(
709 -
				"cannot provide %v from %v", k, path,
710 -
				"already provided by %v", strings.Join(cons, "; "),
711 -
			)
712 -
			return nil
713 777
		}
714 778
715 -
		cv.keyPaths[k] = path
716 -
717 779
	case resultGrouped:
718 780
		// we don't really care about the path for this since conflicts are
719 781
		// okay for group results. We'll track it for the sake of having a
@@ -725,6 +787,28 @@
Loading
725 787
	return cv
726 788
}
727 789
790 +
func (cv connectionVisitor) checkKey(k key, path string) error {
791 +
	defer func() { cv.keyPaths[k] = path }()
792 +
	if conflict, ok := cv.keyPaths[k]; ok {
793 +
		return errf(
794 +
			"cannot provide %v from %v", k, path,
795 +
			"already provided by %v", conflict,
796 +
		)
797 +
	}
798 +
	if ps := cv.c.providers[k]; len(ps) > 0 {
799 +
		cons := make([]string, len(ps))
800 +
		for i, p := range ps {
801 +
			cons[i] = fmt.Sprint(p.Location())
802 +
		}
803 +
804 +
		return errf(
805 +
			"cannot provide %v from %v", k, path,
806 +
			"already provided by %v", strings.Join(cons, "; "),
807 +
		)
808 +
	}
809 +
	return nil
810 +
}
811 +
728 812
// node is a node in the dependency graph. Each node maps to a single
729 813
// constructor provided by the user.
730 814
//
@@ -753,9 +837,10 @@
Loading
753 837
754 838
type nodeOptions struct {
755 839
	// If specified, all values produced by this node have the provided name
756 -
	// or belong to the specified value group
840 +
	// belong to the specified value group or implement any of the interfaces.
757 841
	ResultName  string
758 842
	ResultGroup string
843 +
	ResultAs    []interface{}
759 844
	Location    *digreflect.Func
760 845
}
761 846
@@ -774,6 +859,7 @@
Loading
774 859
		resultOptions{
775 860
			Name:  opts.ResultName,
776 861
			Group: opts.ResultGroup,
862 +
			As:    opts.ResultAs,
777 863
		},
778 864
	)
779 865
	if err != nil {
Files Coverage
internal 96.62%
cycle.go 100.00%
dig.go 98.78%
error.go 100.00%
graph.go 89.74%
group.go 100.00%
param.go 97.09%
result.go 97.15%
stringer.go 100.00%
types.go 100.00%
Project Totals (14 files) 97.91%
1
coverage:
2
  range: 70..98
3
  round: down
4
  precision: 2
5

6
  status:
7
    project:                   # measuring the overall project coverage
8
      default:                 # context, you can create multiple ones with custom titles
9
        enabled: yes           # must be yes|true to enable this status
10
        target: 97             # specify the target coverage for each commit status
11
                               #   option: "auto" (must increase from parent commit or pull request base)
12
                               #   option: "X%" a static target percentage to hit
13
        if_not_found: success  # if parent is not found report status as success, error, or failure
14
        if_ci_failed: error    # if ci fails report status as success, error, or failure
15

16
    patch:
17
      default:
18
        enabled: yes
19
        target: 70
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading