uber-go / dig
1
// Copyright (c) 2019 Uber Technologies, Inc.
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
// of this software and associated documentation files (the "Software"), to deal
5
// in the Software without restriction, including without limitation the rights
6
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
// copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in
11
// all copies or substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
// THE SOFTWARE.
20

21
package dig
22

23
import (
24
	"io"
25
	"strconv"
26
	"text/template"
27

28
	"go.uber.org/dig/internal/dot"
29
)
30

31
// A VisualizeOption modifies the default behavior of Visualize.
32
type VisualizeOption interface {
33
	applyVisualizeOption(*visualizeOptions)
34
}
35

36
type visualizeOptions struct {
37
	VisualizeError error
38
}
39

40
type visualizeOptionFunc func(*visualizeOptions)
41

42 2
func (f visualizeOptionFunc) applyVisualizeOption(opts *visualizeOptions) { f(opts) }
43

44
// VisualizeError includes a visualization of the given error in the output of
45
// Visualize if an error was returned by Invoke or Provide.
46
//
47
//   if err := c.Provide(...); err != nil {
48
//     dig.Visualize(c, w, dig.VisualizeError(err))
49
//   }
50
//
51
// This option has no effect if the error was nil or if it didn't contain any
52
// information to visualize.
53
func VisualizeError(err error) VisualizeOption {
54 2
	return visualizeOptionFunc(func(opts *visualizeOptions) {
55 2
		opts.VisualizeError = err
56 2
	})
57
}
58

59
func updateGraph(dg *dot.Graph, err error) error {
60 2
	var errors []errVisualizer
61
	// Unwrap error to find the root cause.
62
	for {
63 2
		if ev, ok := err.(errVisualizer); ok {
64 2
			errors = append(errors, ev)
65
		}
66 2
		e, ok := err.(causer)
67 2
		if !ok {
68 2
			break
69
		}
70 2
		err = e.cause()
71
	}
72

73
	// If there are no errVisualizers included, we do not modify the graph.
74 2
	if len(errors) == 0 {
75 0
		return nil
76
	}
77

78
	// We iterate in reverse because the last element is the root cause.
79
	for i := len(errors) - 1; i >= 0; i-- {
80 2
		errors[i].updateGraph(dg)
81
	}
82

83
	// Remove non-error entries from the graph for readability.
84 2
	dg.PruneSuccess()
85

86 2
	return nil
87
}
88

89
var _graphTmpl = template.Must(
90
	template.New("DotGraph").
91
		Funcs(template.FuncMap{
92
			"quote": strconv.Quote,
93
		}).
94
		Parse(`digraph {
95
	rankdir=RL;
96
	graph [compound=true];
97
	{{range $g := .Groups}}
98
		{{- quote .String}} [{{.Attributes}}];
99
		{{range .Results}}
100
			{{- quote $g.String}} -> {{quote .String}};
101
		{{end}}
102
	{{end -}}
103
	{{range $index, $ctor := .Ctors}}
104
		subgraph cluster_{{$index}} {
105
			{{ with .Package }}label = {{ quote .}};
106
			{{ end -}}
107

108
			constructor_{{$index}} [shape=plaintext label={{quote .Name}}];
109
			{{with .ErrorType}}color={{.Color}};{{end}}
110
			{{range .Results}}
111
				{{- quote .String}} [{{.Attributes}}];
112
			{{end}}
113
		}
114
		{{range .Params}}
115
			constructor_{{$index}} -> {{quote .String}} [ltail=cluster_{{$index}}{{if .Optional}} style=dashed{{end}}];
116
		{{end}}
117
		{{range .GroupParams}}
118
			constructor_{{$index}} -> {{quote .String}} [ltail=cluster_{{$index}}];
119
		{{end -}}
120
	{{end}}
121
	{{range .Failed.TransitiveFailures}}
122
		{{- quote .String}} [color=orange];
123
	{{end -}}
124
	{{range .Failed.RootCauses}}
125
		{{- quote .String}} [color=red];
126
	{{end}}
127
}`))
128

129
// Visualize parses the graph in Container c into DOT format and writes it to
130
// io.Writer w.
131
func Visualize(c *Container, w io.Writer, opts ...VisualizeOption) error {
132 2
	dg := c.createGraph()
133

134 2
	var options visualizeOptions
135
	for _, o := range opts {
136 2
		o.applyVisualizeOption(&options)
137
	}
138

139 2
	if options.VisualizeError != nil {
140 2
		if err := updateGraph(dg, options.VisualizeError); err != nil {
141 0
			return err
142
		}
143
	}
144

145 2
	return _graphTmpl.Execute(w, dg)
146
}
147

148
// CanVisualizeError returns true if the error is an errVisualizer.
149
func CanVisualizeError(err error) bool {
150
	for {
151 2
		if _, ok := err.(errVisualizer); ok {
152 2
			return true
153
		}
154 2
		e, ok := err.(causer)
155 2
		if !ok {
156 2
			break
157
		}
158 2
		err = e.cause()
159
	}
160

161 2
	return false
162
}
163

164
func (c *Container) createGraph() *dot.Graph {
165 2
	dg := dot.NewGraph()
166

167
	for _, n := range c.nodes {
168 2
		dg.AddCtor(newDotCtor(n), n.paramList.DotParam(), n.resultList.DotResult())
169
	}
170

171 2
	return dg
172
}
173

174
func newDotCtor(n *node) *dot.Ctor {
175 2
	return &dot.Ctor{
176 2
		ID:      n.id,
177 2
		Name:    n.location.Name,
178 2
		Package: n.location.Package,
179 2
		File:    n.location.File,
180 2
		Line:    n.location.Line,
181
	}
182
}

Read our documentation on viewing source code .

Loading