-
Notifications
You must be signed in to change notification settings - Fork 235
/
template.go
162 lines (143 loc) · 5.2 KB
/
template.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package errors
import (
goAst "go/ast"
goToken "go/token"
"reflect"
"strings"
)
// Template represents a template for a new error.
//
// It itself is not an error, but can be used to initialize a new [errorinsrc.ErrInSrc].
type Template struct {
Code int
Title string
Summary string
Detail string
Cause error
Locations []SrcLocation
AlwaysIncludeStack bool
}
// TemplateOption can be passed into the [Range] when creating a new Template
type TemplateOption func(*Template)
// AlwaysIncludeStack will setup a Template so it always includes a stack trace
// even in a production build of Encore.
func AlwaysIncludeStack() TemplateOption {
return func(template *Template) {
template.AlwaysIncludeStack = true
}
}
// WithDetails will setup a template so it uses a different details to the
// range default
func WithDetails(details string) TemplateOption {
return func(template *Template) {
template.Detail = details
}
}
// PrependDetails will setup a template so it prepends the given details to the
// range default
func PrependDetails(details string) TemplateOption {
return func(template *Template) {
if template.Detail != "" {
details = details + "\n\n" + template.Detail
}
template.Detail = details
}
}
// MarkAsInternalError will setup a template so it is reported as an internal error.
//
// This means that the error will be reported to the user as an internal error
// with a link to the Encore issue tracker and will include a stack trace.
func MarkAsInternalError() TemplateOption {
return func(template *Template) {
template.AlwaysIncludeStack = true
template.Title = "Internal Error: " + template.Title
template.Detail = "This is a bug in Encore and should not have occurred. Please report this issue to the " +
"Encore team either on Github at https://github.com/encoredev/encore/issues/new and include this error."
}
}
// WithDetails will replace the details of the template with the given details
func (t Template) WithDetails(details string) Template {
t.Detail = details
return t
}
// Wrapping wraps the given error with the template.
//
// It will append the given error to the summary of the template
// as well as setting the cause of the template to the given error.
func (t Template) Wrapping(err error) Template {
if err == nil {
return t
}
t.Summary += "\n\n" + err.Error()
t.Summary = strings.TrimSpace(t.Summary)
t.Cause = err
return t
}
// atLocation is a helper method for the various with methods
func (t Template) atLocation(location SrcLocation, options []LocationOption) Template {
for _, o := range options {
o(&location)
}
t.Locations = append([]SrcLocation{location}, t.Locations...)
return t
}
// InFile adds the given file as a src of the error location
//
// Note: It is preferable to use one of the other location functions
// as they will render the source around the error, not just the file name
func (t Template) InFile(filepath string, options ...LocationOption) Template {
if filepath == "" {
return t
}
return t.atLocation(SrcLocation{Kind: LocFile, Filepath: filepath}, options)
}
// AtGoNode adds the given Go node to the template. If the node is nil, nothing happens.
//
// You can use the [LocationOption]s to add additional information to the location.
//
// Example:
//
// errMyErrorTemplate.AtGoNode(node, errtmp.AsHelp("this is where it was defined before"))
func (t Template) AtGoNode(node goAst.Node, options ...LocationOption) Template {
if node == nil {
return t
}
// In some cases the node may be a "typed nil" which isn't caught by the check above.
// Handle that separately, as otherwise we get unexpected panics later when
// trying to call .Pos().
if v := reflect.ValueOf(node); v.Kind() == reflect.Pointer && v.IsNil() {
return t
}
return t.atLocation(SrcLocation{Kind: LocGoNode, GoNode: node}, options)
}
// AtGoPos adds the given start and end [token.Pos] to the template. If both positions are token.NoPos, nothing happens.
// If one of the two positions are token.NoPos, the other position will be used.
//
// It is valid to use the same value for start and end positions, in which case the error will estimate which Node
// you are referencing.
//
// Example:
//
// errMyErrorTemplate.AtGoPos(start, token.NoPos, errtmp.AsHelp("this is where it was defined before"))
func (t Template) AtGoPos(start, end goToken.Pos, options ...LocationOption) Template {
switch {
case start == goToken.NoPos && end == goToken.NoPos:
return t
case start == goToken.NoPos && end != goToken.NoPos:
start = end
case end == goToken.NoPos && start != goToken.NoPos:
end = start
}
return t.atLocation(SrcLocation{Kind: LocGoPos, GoStartPos: start, GoEndPos: end}, options)
}
// AtGoPosition adds the given Go positions to the template.
//
// It is valid to use the same value for start and end positions, in which case the error will estimate which Node
// you are referencing.
//
// Example:
//
// errMyErrorTemplate.AtGoPosition(start, end, errtmp.AsHelp("this is where it was defined before"))
func (t Template) AtGoPosition(start, end goToken.Position, options ...LocationOption) Template {
return t.atLocation(SrcLocation{Kind: LocGoPositions, GoStartPosition: start, GoEndPosition: end}, options)
}