// Copyright (c) 2021 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 digimport ()// A ProvideOption modifies the default behavior of Provide.typeProvideOptioninterface { applyProvideOption(*provideOptions)}type provideOptions struct { Name string Group string Info *ProvideInfo As []interface{} Location *digreflect.Func Exported bool Callback Callback BeforeCallback BeforeCallback}func ( *provideOptions) () error {iflen(.Group) > 0 {iflen(.Name) > 0 {returnnewErrInvalidInput(fmt.Sprintf("cannot use named values with value groups: name:%q provided with group:%q", .Name, .Group), nil) } }// Names must be representable inside a backquoted string. The only // limitation for raw string literals as per // https://golang.org/ref/spec#raw_string_lit is that they cannot contain // backquotes.ifstrings.ContainsRune(.Name, '`') {returnnewErrInvalidInput(fmt.Sprintf("invalid dig.Name(%q): names cannot contain backquotes", .Name), nil) }ifstrings.ContainsRune(.Group, '`') {returnnewErrInvalidInput(fmt.Sprintf("invalid dig.Group(%q): group names cannot contain backquotes", .Group), nil) }for , := range .As { := reflect.TypeOf()if == nil {returnnewErrInvalidInput("invalid dig.As(nil): argument must be a pointer to an interface", nil) }if .Kind() != reflect.Ptr {returnnewErrInvalidInput(fmt.Sprintf("invalid dig.As(%v): argument must be a pointer to an interface", ), nil) } := .Elem()if .Kind() != reflect.Interface {returnnewErrInvalidInput(fmt.Sprintf("invalid dig.As(*%v): argument must be a pointer to an interface", ), nil) } }returnnil}// Name is a ProvideOption that specifies that all values produced by a// constructor should have the given name. See also the package 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".//// c.Provide(NewReadOnlyConnection, dig.Name("ro"))// c.Provide(NewReadWriteConnection, dig.Name("rw"))//// This option cannot be provided for constructors which produce result// objects.func ( string) ProvideOption {returnprovideNameOption()}type provideNameOption stringfunc ( provideNameOption) () string {returnfmt.Sprintf("Name(%q)", string())}func ( provideNameOption) ( *provideOptions) { .Name = string()}// Group is a ProvideOption that specifies that all values produced by a// constructor should be added to the specified group. See also the package// documentation about Value Groups.//// This option cannot be provided for constructors which produce result// objects.func ( string) ProvideOption {returnprovideGroupOption()}type provideGroupOption stringfunc ( provideGroupOption) () string {returnfmt.Sprintf("Group(%q)", string())}func ( provideGroupOption) ( *provideOptions) { .Group = string()}// ID is a unique integer representing the constructor node in the dependency graph.typeIDint// ProvideInfo provides information about the constructor's inputs and outputs// types as strings, as well as the ID of the constructor supplied to the Container.// It contains ID for the constructor, as well as slices of Input and Output types,// which are Stringers that report the types of the parameters and results respectively.typeProvideInfostruct { ID ID Inputs []*Input Outputs []*Output}// Input contains information on an input parameter of a function.typeInputstruct { t reflect.Type optional bool name, group string}func ( *Input) () string { := make([]string, 0, 3) := .t.String()if .optional { = append(, "optional") }if .name != "" { = append(, fmt.Sprintf("name = %q", .name)) }if .group != "" { = append(, fmt.Sprintf("group = %q", .group)) }iflen() == 0 {return }returnfmt.Sprintf("%v[%v]", , strings.Join(, ", "))}// Output contains information on an output produced by a function.typeOutputstruct { t reflect.Type name, group string}func ( *Output) () string { := make([]string, 0, 2) := .t.String()if .name != "" { = append(, fmt.Sprintf("name = %q", .name)) }if .group != "" { = append(, fmt.Sprintf("group = %q", .group)) }iflen() == 0 {return }returnfmt.Sprintf("%v[%v]", , strings.Join(, ", "))}// FillProvideInfo is a ProvideOption that writes info on what Dig was able to get// out of the provided constructor into the provided ProvideInfo.func ( *ProvideInfo) ProvideOption {returnfillProvideInfoOption{info: }}type fillProvideInfoOption struct{ info *ProvideInfo }func ( fillProvideInfoOption) () string {returnfmt.Sprintf("FillProvideInfo(%p)", .info)}func ( fillProvideInfoOption) ( *provideOptions) { .Info = .info}// As is a ProvideOption that specifies that the value produced by the// constructor implements one or more other interfaces and is provided// to the container as those interfaces.//// As expects one or more pointers to the implemented interfaces. Values// produced by constructors will be then available in the container as// implementations of all of those interfaces, but not as the value itself.//// For example, the following will make io.Reader and io.Writer available// in the container, but not buffer.//// c.Provide(newBuffer, dig.As(new(io.Reader), new(io.Writer)))//// That is, the above is equivalent to the following.//// c.Provide(func(...) (io.Reader, io.Writer) {// b := newBuffer(...)// return b, b// })//// If used with dig.Name, the type produced by the constructor and the types// specified with dig.As will all use the same name. For example,//// c.Provide(newFile, dig.As(new(io.Reader)), dig.Name("temp"))//// The above is equivalent to the following.//// type Result struct {// dig.Out//// Reader io.Reader `name:"temp"`// }//// c.Provide(func(...) Result {// f := newFile(...)// return Result{// Reader: f,// }// })//// This option cannot be provided for constructors which produce result// objects.func ( ...interface{}) ProvideOption {returnprovideAsOption()}type provideAsOption []interface{}func ( provideAsOption) () string { := bytes.NewBufferString("As(")for , := range {if > 0 { .WriteString(", ") } .WriteString(reflect.TypeOf().Elem().String()) } .WriteString(")")return .String()}func ( provideAsOption) ( *provideOptions) { .As = append(.As, ...)}// LocationForPC is a ProvideOption which specifies an alternate function program// counter address to be used for debug information. The package, name, file and// line number of this alternate function address will be used in error messages// and DOT graphs. This option is intended to be used with functions created// with the reflect.MakeFunc method whose error messages are otherwise hard to// understandfunc ( uintptr) ProvideOption {returnprovideLocationOption{loc: digreflect.InspectFuncPC(), }}type provideLocationOption struct{ loc *digreflect.Func }func ( provideLocationOption) () string {returnfmt.Sprintf("LocationForPC(%v)", .loc)}func ( provideLocationOption) ( *provideOptions) { .Location = .loc}// Export is a ProvideOption which specifies that the provided function should// be made available to all Scopes available in the application, regardless// of which Scope it was provided from. By default, it is false.//// For example,//// c := New()// s1 := c.Scope("child 1")// s2:= c.Scope("child 2")// s1.Provide(func() *bytes.Buffer { ... })//// does not allow the constructor returning *bytes.Buffer to be made available to// the root Container c or its sibling Scope s2.//// With Export, you can make this constructor available to all the Scopes://// s1.Provide(func() *bytes.Buffer { ... }, Export(true))func ( bool) ProvideOption {returnprovideExportOption{exported: }}type provideExportOption struct{ exported bool }func ( provideExportOption) () string {returnfmt.Sprintf("Export(%v)", .exported)}func ( provideExportOption) ( *provideOptions) { .Exported = .exported}// provider encapsulates a user-provided constructor.type provider interface {// ID is a unique numerical identifier for this provider. ID() dot.CtorID// Order reports the order of this provider in the graphHolder. // This value is usually returned by the graphHolder.NewNode method. Order(*Scope) int// Location returns where this constructor was defined. Location() *digreflect.Func// ParamList returns information about the direct dependencies of this // constructor. ParamList() paramList// ResultList returns information about the values produced by this // constructor. ResultList() resultList// Calls the underlying constructor, reading values from the // containerStore as needed. // // The values produced by this provider should be submitted into the // containerStore. Call(containerStore) error CType() reflect.Type OrigScope() *Scope}// Provide teaches the container how to build values of one or more types and// expresses their dependencies.//// The first argument of Provide is a function that accepts zero or more// parameters and returns one or more results. The function may optionally// return an error to indicate that it failed to build the value. This// function will be treated as the constructor for all the types it returns.// This function will be called AT MOST ONCE when a type produced by it, or a// type that consumes this function's output, is requested via Invoke. If the// same types are requested multiple times, the previously produced value will// be reused.//// Provide accepts argument types or dig.In structs as dependencies, and// separate return values or dig.Out structs for results.func ( *Container) ( interface{}, ...ProvideOption) error {return .scope.Provide(, ...)}// Provide teaches the Scope how to build values of one or more types and// expresses their dependencies.//// The first argument of Provide is a function that accepts zero or more// parameters and returns one or more results. The function may optionally// return an error to indicate that it failed to build the value. This// function will be treated as the constructor for all the types it returns.// This function will be called AT MOST ONCE when a type produced by it, or a// type that consumes this function's output, is requested via Invoke. If the// same types are requested multiple times, the previously produced value will// be reused.//// Provide accepts argument types or dig.In structs as dependencies, and// separate return values or dig.Out structs for results.//// When a constructor is Provided to a Scope, it will propagate this to any// Scopes that are descendents, but not ancestors of this Scope.// To provide a constructor to all the Scopes available, provide it to// Container, which is the root Scope.func ( *Scope) ( interface{}, ...ProvideOption) error { := reflect.TypeOf()if == nil {returnnewErrInvalidInput("can't provide an untyped nil", nil) }if .Kind() != reflect.Func {returnnewErrInvalidInput(fmt.Sprintf("must provide constructor function, got %v (type %v)", , ), nil) }varprovideOptionsfor , := range { .applyProvideOption(&) }if := .Validate(); != nil {return }if := .provide(, ); != nil {var *digreflect.Funcif .Location == nil { = digreflect.InspectFunc() } else { = .Location }returnerrProvide{Func: ,Reason: , } }returnnil}func ( *Scope) ( interface{}, provideOptions) ( error) {// If Export option is provided to the constructor, this should be injected to the // root-level Scope (Container) to allow it to propagate to all other Scopes. := if .Exported { = .rootScope() }// For all scopes affected by this change, // take a snapshot of the current graph state before // we start making changes to it as we may need to // undo them upon encountering errors. := .appendSubscopes(nil)deferfunc( []*Scope) {if != nil {for , := range { .gh.Rollback() } } }()for , := range { .gh.Snapshot() } , := newConstructorNode( , , ,constructorOptions{ResultName: .Name,ResultGroup: .Group,ResultAs: .As,Location: .Location,Callback: .Callback,BeforeCallback: .BeforeCallback, }, )if != nil {return } , := .findAndValidateResults(.ResultList())if != nil {return } := reflect.TypeOf()iflen() == 0 {returnnewErrInvalidInput(fmt.Sprintf("%v must provide at least one non-error type", ), nil) } := make(map[key][]*constructorNode)for := range {// Cache old providers before running cycle detection. [] = .providers[] .providers[] = append(.providers[], ) }for , := range { .isVerifiedAcyclic = falseif .deferAcyclicVerification {continue }if , := graph.IsAcyclic(.gh); ! {// When a cycle is detected, recover the old providers to reset // the providers map back to what it was before this node was // introduced.for , := range { .providers[] = }returnnewErrInvalidInput("this function introduces a cycle", .cycleDetectedError()) } .isVerifiedAcyclic = true } .nodes = append(.nodes, )// Record introspection info for caller if Info option is specifiedif := .Info; != nil { := .ParamList().DotParam() := .ResultList().DotResult() .ID = (ID)(.id) .Inputs = make([]*Input, len()) .Outputs = make([]*Output, len())for , := range { .Inputs[] = &Input{t: .Type,optional: .Optional,name: .Name,group: .Group, } }for , := range { .Outputs[] = &Output{t: .Type,name: .Name,group: .Group, } } }returnnil}// Builds a collection of all result types produced by this constructor.func ( *Scope) ( resultList) (map[key]struct{}, error) {varerror := make(map[key]string)walkResult(, connectionVisitor{s: ,err: &,keyPaths: , })if != nil {returnnil, } := make(map[key]struct{}, len())for := range { [] = struct{}{} }return , nil}// Visits the results of a node and compiles a collection of all the keys// produced by that node.type connectionVisitor struct { s *Scope// If this points to a non-nil value, we've already encountered an error // and should stop traversing. err *error// Map of keys provided to path that provided this. The path is a string // documenting which positional return value or dig.Out attribute is // providing this particular key. // // For example, "[0].Foo" indicates that the value was provided by the Foo // attribute of the dig.Out returned as the first result of the // constructor. keyPaths map[key]string// We track the path to the current result here. For example, this will // be, ["[1]", "Foo", "Bar"] when we're visiting Bar in, // // func() (io.Writer, struct { // dig.Out // // Foo struct { // dig.Out // // Bar io.Reader // } // }) currentResultPath []string}func ( connectionVisitor) ( resultObjectField) resultVisitor { .currentResultPath = append(.currentResultPath, .FieldName)return}func ( connectionVisitor) ( int) resultVisitor { .currentResultPath = append(.currentResultPath, fmt.Sprintf("[%d]", ))return}func ( connectionVisitor) ( result) resultVisitor {// Already failed. Stop looking.if *.err != nil {returnnil } := strings.Join(.currentResultPath, ".")switch r := .(type) {caseresultSingle: := key{name: .Name, t: .Type}if := .checkKey(, ); != nil { *.err = returnnil }for , := range .As { := key{name: .Name, t: }if := .checkKey(, ); != nil { *.err = returnnil } }caseresultGrouped:// we don't really care about the path for this since conflicts are // okay for group results. We'll track it for the sake of having a // value there. := key{group: .Group, t: .Type} .keyPaths[] = for , := range .As { := key{group: .Group, t: } .keyPaths[] = } }return}func ( connectionVisitor) ( key, string) error {deferfunc() { .keyPaths[] = }()if , := .keyPaths[]; {returnnewErrInvalidInput(fmt.Sprintf("cannot provide %v from %v", , ),newErrInvalidInput(fmt.Sprintf("already provided by %v", ), nil)) }if := .s.providers[]; len() > 0 { := make([]string, len())for , := range { [] = fmt.Sprint(.Location()) }returnnewErrInvalidInput(fmt.Sprintf("cannot provide %v from %v", , ),newErrInvalidInput(fmt.Sprintf("already provided by %v", strings.Join(, "; ")), nil)) }returnnil}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.