523 lines
14 KiB
Go
523 lines
14 KiB
Go
// Copyright (c) 2019 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 dig
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"sort"
|
|
|
|
"go.uber.org/dig/internal/digerror"
|
|
"go.uber.org/dig/internal/digreflect"
|
|
"go.uber.org/dig/internal/dot"
|
|
)
|
|
|
|
// Errors which know their underlying cause should implement this interface to
|
|
// be compatible with RootCause.
|
|
//
|
|
// We use unexported methods because we don't want dig-internal causes to be
|
|
// confused with the cause of the user-provided errors. For example, if we
|
|
// used Unwrap(), then user-provided methods would also be unwrapped by
|
|
// RootCause. We want RootCause to eliminate the dig error chain only.
|
|
type causer interface {
|
|
fmt.Formatter
|
|
|
|
// Returns the next error in the chain.
|
|
cause() error
|
|
|
|
// Writes the message or context for this error in the chain.
|
|
//
|
|
// verb is either %v or %+v.
|
|
writeMessage(w io.Writer, verb string)
|
|
}
|
|
|
|
// Implements fmt.Formatter for errors that implement causer.
|
|
//
|
|
// This Format method supports %v and %+v. In the %v form, the error is
|
|
// printed on one line. In the %+v form, the error is split across multiple
|
|
// lines on each error in the error chain.
|
|
func formatCauser(c causer, w fmt.State, v rune) {
|
|
multiline := w.Flag('+') && v == 'v'
|
|
verb := "%v"
|
|
if multiline {
|
|
verb = "%+v"
|
|
}
|
|
|
|
// "context: " or "context:\n"
|
|
c.writeMessage(w, verb)
|
|
io.WriteString(w, ":")
|
|
if multiline {
|
|
io.WriteString(w, "\n")
|
|
} else {
|
|
io.WriteString(w, " ")
|
|
}
|
|
|
|
fmt.Fprintf(w, verb, c.cause())
|
|
}
|
|
|
|
// RootCause returns the original error that caused the provided dig failure.
|
|
//
|
|
// RootCause may be used on errors returned by Invoke to get the original
|
|
// error returned by a constructor or invoked function.
|
|
func RootCause(err error) error {
|
|
for {
|
|
if e, ok := err.(causer); ok {
|
|
err = e.cause()
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// errf is a version of fmt.Errorf with support for a chain of multiple
|
|
// formatted error messages.
|
|
//
|
|
// After msg, N arguments are consumed as formatting arguments for that
|
|
// message, where N is the number of % symbols in msg. Following that, another
|
|
// string may be added to become the next error in the chain. Each new error
|
|
// is the `cause()` for the prior error.
|
|
//
|
|
// err := errf(
|
|
// "could not process %v", thing,
|
|
// "name %q is invalid", thing.Name,
|
|
// )
|
|
// fmt.Println(err) // could not process Thing: name Foo is invalid
|
|
// fmt.Println(RootCause(err)) // name Foo is invalid
|
|
//
|
|
// In place of a string, the last error can be another error, in which case it
|
|
// will be treated as the cause of the prior error chain.
|
|
//
|
|
// errf(
|
|
// "could not process %v", thing,
|
|
// "date %q could not be parsed", thing.Date,
|
|
// parseError,
|
|
// )
|
|
func errf(msg string, args ...interface{}) error {
|
|
// By implementing buildErrf as a closure rather than a standalone
|
|
// function, we're able to ensure that it is called only from errf, or
|
|
// from itself (recursively). By controlling these invocations in such
|
|
// a tight space, we are able to easily verify manually that we
|
|
// checked len(args) > 0 before making the call.
|
|
var buildErrf func([]interface{}) error
|
|
buildErrf = func(args []interface{}) error {
|
|
arg, args := args[0], args[1:] // assume len(args) > 0
|
|
if arg == nil {
|
|
digerror.BugPanicf("arg must not be nil")
|
|
}
|
|
|
|
switch v := arg.(type) {
|
|
case string:
|
|
need := numFmtArgs(v)
|
|
if len(args) < need {
|
|
digerror.BugPanicf("string %q needs %v arguments, got %v",
|
|
v,
|
|
need,
|
|
len(args))
|
|
}
|
|
|
|
msg := fmt.Sprintf(v, args[:need]...)
|
|
args := args[need:]
|
|
|
|
// If we don't have anything left to chain with, build the
|
|
// final error.
|
|
if len(args) == 0 {
|
|
return errors.New(msg)
|
|
}
|
|
|
|
return wrappedError{
|
|
msg: msg,
|
|
err: buildErrf(args),
|
|
}
|
|
case error:
|
|
if len(args) > 0 {
|
|
digerror.BugPanicf("error must be the last element but got %v", args)
|
|
}
|
|
return v
|
|
default:
|
|
digerror.BugPanicf("unexpected errf-argument type %T", arg)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Prepend msg to the args list so that we can re-use the same
|
|
// args processing logic. The msg is a string just for type-safety of
|
|
// the first error.
|
|
newArgs := make([]interface{}, len(args)+1)
|
|
newArgs[0] = msg
|
|
copy(newArgs[1:], args)
|
|
return buildErrf(newArgs)
|
|
}
|
|
|
|
// Returns the number of formatting arguments in the provided string. Does not
|
|
// count escaped % symbols, specifically the string "%%".
|
|
//
|
|
// fmt.Println(numFmtArgs("rate: %d%%")) // 1
|
|
func numFmtArgs(s string) int {
|
|
var (
|
|
count int
|
|
percent bool // saw %
|
|
)
|
|
for _, c := range s {
|
|
if percent && c != '%' {
|
|
// Counts only if it's not a %%.
|
|
count++
|
|
}
|
|
|
|
// Next iteration should consider % only if the current %
|
|
// stands alone.
|
|
percent = !percent && c == '%'
|
|
}
|
|
return count
|
|
}
|
|
|
|
type wrappedError struct {
|
|
err error
|
|
msg string
|
|
}
|
|
|
|
var _ causer = wrappedError{}
|
|
|
|
func (e wrappedError) cause() error {
|
|
return e.err
|
|
}
|
|
|
|
func (e wrappedError) writeMessage(w io.Writer, _ string) {
|
|
io.WriteString(w, e.msg)
|
|
}
|
|
|
|
func (e wrappedError) Error() string { return fmt.Sprint(e) }
|
|
func (e wrappedError) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
// errProvide is returned when a constructor could not be Provided into the
|
|
// container.
|
|
type errProvide struct {
|
|
Func *digreflect.Func
|
|
Reason error
|
|
}
|
|
|
|
var _ causer = errProvide{}
|
|
|
|
func (e errProvide) cause() error {
|
|
return e.Reason
|
|
}
|
|
|
|
func (e errProvide) writeMessage(w io.Writer, verb string) {
|
|
fmt.Fprintf(w, "cannot provide function "+verb, e.Func)
|
|
}
|
|
|
|
func (e errProvide) Error() string { return fmt.Sprint(e) }
|
|
func (e errProvide) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
// errConstructorFailed is returned when a user-provided constructor failed
|
|
// with a non-nil error.
|
|
type errConstructorFailed struct {
|
|
Func *digreflect.Func
|
|
Reason error
|
|
}
|
|
|
|
var _ causer = errConstructorFailed{}
|
|
|
|
func (e errConstructorFailed) cause() error {
|
|
return e.Reason
|
|
}
|
|
|
|
func (e errConstructorFailed) writeMessage(w io.Writer, verb string) {
|
|
fmt.Fprintf(w, "received non-nil error from function "+verb, e.Func)
|
|
}
|
|
|
|
func (e errConstructorFailed) Error() string { return fmt.Sprint(e) }
|
|
func (e errConstructorFailed) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
// errArgumentsFailed is returned when a function could not be run because one
|
|
// of its dependencies failed to build for any reason.
|
|
type errArgumentsFailed struct {
|
|
Func *digreflect.Func
|
|
Reason error
|
|
}
|
|
|
|
var _ causer = errArgumentsFailed{}
|
|
|
|
func (e errArgumentsFailed) cause() error {
|
|
return e.Reason
|
|
}
|
|
|
|
func (e errArgumentsFailed) writeMessage(w io.Writer, verb string) {
|
|
fmt.Fprintf(w, "could not build arguments for function "+verb, e.Func)
|
|
}
|
|
|
|
func (e errArgumentsFailed) Error() string { return fmt.Sprint(e) }
|
|
func (e errArgumentsFailed) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
// errMissingDependencies is returned when the dependencies of a function are
|
|
// not available in the container.
|
|
type errMissingDependencies struct {
|
|
Func *digreflect.Func
|
|
Reason error
|
|
}
|
|
|
|
var _ causer = errMissingDependencies{}
|
|
|
|
func (e errMissingDependencies) cause() error {
|
|
return e.Reason
|
|
}
|
|
|
|
func (e errMissingDependencies) writeMessage(w io.Writer, verb string) {
|
|
fmt.Fprintf(w, "missing dependencies for function "+verb, e.Func)
|
|
}
|
|
|
|
func (e errMissingDependencies) Error() string { return fmt.Sprint(e) }
|
|
func (e errMissingDependencies) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
// errParamSingleFailed is returned when a paramSingle could not be built.
|
|
type errParamSingleFailed struct {
|
|
Key key
|
|
Reason error
|
|
CtorID dot.CtorID
|
|
}
|
|
|
|
var _ causer = errParamSingleFailed{}
|
|
|
|
func (e errParamSingleFailed) cause() error {
|
|
return e.Reason
|
|
}
|
|
|
|
func (e errParamSingleFailed) writeMessage(w io.Writer, _ string) {
|
|
fmt.Fprintf(w, "failed to build %v", e.Key)
|
|
}
|
|
|
|
func (e errParamSingleFailed) Error() string { return fmt.Sprint(e) }
|
|
func (e errParamSingleFailed) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
func (e errParamSingleFailed) updateGraph(g *dot.Graph) {
|
|
failed := &dot.Result{
|
|
Node: &dot.Node{
|
|
Name: e.Key.name,
|
|
Group: e.Key.group,
|
|
Type: e.Key.t,
|
|
},
|
|
}
|
|
g.FailNodes([]*dot.Result{failed}, e.CtorID)
|
|
}
|
|
|
|
// errParamGroupFailed is returned when a value group cannot be built because
|
|
// any of the values in the group failed to build.
|
|
type errParamGroupFailed struct {
|
|
Key key
|
|
Reason error
|
|
CtorID dot.CtorID
|
|
}
|
|
|
|
var _ causer = errParamGroupFailed{}
|
|
|
|
func (e errParamGroupFailed) cause() error {
|
|
return e.Reason
|
|
}
|
|
|
|
func (e errParamGroupFailed) writeMessage(w io.Writer, _ string) {
|
|
fmt.Fprintf(w, "could not build value group %v", e.Key)
|
|
}
|
|
|
|
func (e errParamGroupFailed) Error() string { return fmt.Sprint(e) }
|
|
func (e errParamGroupFailed) Format(w fmt.State, c rune) {
|
|
formatCauser(e, w, c)
|
|
}
|
|
|
|
func (e errParamGroupFailed) updateGraph(g *dot.Graph) {
|
|
g.FailGroupNodes(e.Key.group, e.Key.t, e.CtorID)
|
|
}
|
|
|
|
// missingType holds information about a type that was missing in the
|
|
// container.
|
|
type missingType struct {
|
|
Key key // item that was missing
|
|
|
|
// If non-empty, we will include suggestions for what the user may have
|
|
// meant.
|
|
suggestions []key
|
|
}
|
|
|
|
// Format prints a string representation of missingType.
|
|
//
|
|
// With %v, it prints a short representation ideal for an itemized list.
|
|
//
|
|
// io.Writer
|
|
// io.Writer: did you mean *bytes.Buffer?
|
|
// io.Writer: did you mean *bytes.Buffer, or *os.File?
|
|
//
|
|
// With %+v, it prints a longer representation ideal for standalone output.
|
|
//
|
|
// io.Writer: did you mean to Provide it?
|
|
// io.Writer: did you mean to use *bytes.Buffer?
|
|
// io.Writer: did you mean to use one of *bytes.Buffer, or *os.File?
|
|
func (mt missingType) Format(w fmt.State, v rune) {
|
|
plusV := w.Flag('+') && v == 'v'
|
|
|
|
fmt.Fprint(w, mt.Key)
|
|
switch len(mt.suggestions) {
|
|
case 0:
|
|
if plusV {
|
|
io.WriteString(w, " (did you mean to Provide it?)")
|
|
}
|
|
case 1:
|
|
sug := mt.suggestions[0]
|
|
if plusV {
|
|
fmt.Fprintf(w, " (did you mean to use %v?)", sug)
|
|
} else {
|
|
fmt.Fprintf(w, " (did you mean %v?)", sug)
|
|
}
|
|
default:
|
|
if plusV {
|
|
io.WriteString(w, " (did you mean to use one of ")
|
|
} else {
|
|
io.WriteString(w, " (did you mean ")
|
|
}
|
|
|
|
lastIdx := len(mt.suggestions) - 1
|
|
for i, sug := range mt.suggestions {
|
|
if i > 0 {
|
|
io.WriteString(w, ", ")
|
|
if i == lastIdx {
|
|
io.WriteString(w, "or ")
|
|
}
|
|
}
|
|
fmt.Fprint(w, sug)
|
|
}
|
|
io.WriteString(w, "?)")
|
|
}
|
|
}
|
|
|
|
// errMissingType is returned when one or more values that were expected in
|
|
// the container were not available.
|
|
//
|
|
// Multiple instances of this error may be merged together by appending them.
|
|
type errMissingTypes []missingType // inv: len > 0
|
|
|
|
func newErrMissingTypes(c containerStore, k key) errMissingTypes {
|
|
// Possible types we will look for in the container. We will always look
|
|
// for pointers to the requested type and some extras on a per-Kind basis.
|
|
suggestions := []reflect.Type{reflect.PtrTo(k.t)}
|
|
|
|
if k.t.Kind() == reflect.Ptr {
|
|
// The user requested a pointer but maybe we have a value.
|
|
suggestions = append(suggestions, k.t.Elem())
|
|
}
|
|
|
|
knownTypes := c.knownTypes()
|
|
if k.t.Kind() == reflect.Interface {
|
|
// Maybe we have an implementation of the interface.
|
|
for _, t := range knownTypes {
|
|
if t.Implements(k.t) {
|
|
suggestions = append(suggestions, t)
|
|
}
|
|
}
|
|
} else {
|
|
// Maybe we have an interface that this type implements.
|
|
for _, t := range knownTypes {
|
|
if t.Kind() == reflect.Interface {
|
|
if k.t.Implements(t) {
|
|
suggestions = append(suggestions, t)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// range through c.providers is non-deterministic. Let's sort the list of
|
|
// suggestions.
|
|
sort.Sort(byTypeName(suggestions))
|
|
|
|
mt := missingType{Key: k}
|
|
for _, t := range suggestions {
|
|
if len(c.getValueProviders(k.name, t)) > 0 {
|
|
k.t = t
|
|
mt.suggestions = append(mt.suggestions, k)
|
|
}
|
|
}
|
|
|
|
return errMissingTypes{mt}
|
|
}
|
|
|
|
func (e errMissingTypes) Error() string {
|
|
return fmt.Sprint(e)
|
|
}
|
|
|
|
func (e errMissingTypes) Format(w fmt.State, v rune) {
|
|
multiline := w.Flag('+') && v == 'v'
|
|
|
|
if len(e) == 1 {
|
|
io.WriteString(w, "missing type:")
|
|
} else {
|
|
io.WriteString(w, "missing types:")
|
|
}
|
|
|
|
if !multiline {
|
|
// With %v, we need a space between : since the error
|
|
// won't be on a new line.
|
|
io.WriteString(w, " ")
|
|
}
|
|
|
|
for i, mt := range e {
|
|
if multiline {
|
|
io.WriteString(w, "\n\t- ")
|
|
} else if i > 0 {
|
|
io.WriteString(w, "; ")
|
|
}
|
|
|
|
if multiline {
|
|
fmt.Fprintf(w, "%+v", mt)
|
|
} else {
|
|
fmt.Fprintf(w, "%v", mt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e errMissingTypes) updateGraph(g *dot.Graph) {
|
|
missing := make([]*dot.Result, len(e))
|
|
|
|
for i, mt := range e {
|
|
missing[i] = &dot.Result{
|
|
Node: &dot.Node{
|
|
Name: mt.Key.name,
|
|
Group: mt.Key.group,
|
|
Type: mt.Key.t,
|
|
},
|
|
}
|
|
}
|
|
g.AddMissingNodes(missing)
|
|
}
|
|
|
|
type errVisualizer interface {
|
|
updateGraph(*dot.Graph)
|
|
}
|