-
Notifications
You must be signed in to change notification settings - Fork 10
/
diagnostic_extra.go
171 lines (158 loc) · 7.15 KB
/
diagnostic_extra.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
163
164
165
166
167
168
169
170
171
package tfdiags
// This "Extra" idea is something we've inherited from HCL's diagnostic model,
// and so it's primarily to expose that functionality from wrapped HCL
// diagnostics but other diagnostic types could potentially implement this
// protocol too, if needed.
// ExtraInfo tries to retrieve extra information of interface type T from
// the given diagnostic.
//
// "Extra information" is situation-specific additional contextual data which
// might allow for some special tailored reporting of particular
// diagnostics in the UI. Conventionally the extra information is provided
// as a hidden type that implements one or more interfaces which a caller
// can pass as type parameter T to retrieve a value of that type when the
// diagnostic has such an implementation.
//
// If the given diagnostic's extra value has an implementation of interface T
// then ExtraInfo returns a non-nil interface value. If there is no such
// implementation, ExtraInfo returns a nil T.
//
// Although the signature of this function does not constrain T to be an
// interface type, our convention is to only use interface types to access
// extra info in order to allow for alternative or wrapping implementations
// of the interface.
func ExtraInfo[T any](diag Diagnostic) T {
extra := diag.ExtraInfo()
if ret, ok := extra.(T); ok {
return ret
}
// If "extra" doesn't implement T directly then we'll delegate to
// our ExtraInfoNext helper to try iteratively unwrapping it.
return ExtraInfoNext[T](extra)
}
// ExtraInfoNext takes a value previously returned by ExtraInfo and attempts
// to find an implementation of interface T wrapped inside of it. The return
// value meaning is the same as for ExtraInfo.
//
// This is to help with the less common situation where a particular "extra"
// value might be wrapping another value implementing the same interface,
// and so callers can peel away one layer at a time until there are no more
// nested layers.
//
// Because this function is intended for searching for _nested_ implementations
// of T, ExtraInfoNext does not consider whether value "previous" directly
// implements interface T, on the assumption that the previous call to ExtraInfo
// with the same T caused "previous" to already be that result.
func ExtraInfoNext[T any](previous interface{}) T {
// As long as T is an interface type as documented, zero will always be
// a nil interface value for us to return in the non-matching case.
var zero T
unwrapper, ok := previous.(DiagnosticExtraUnwrapper)
// If the given value isn't unwrappable then it can't possibly have
// any other info nested inside of it.
if !ok {
return zero
}
extra := unwrapper.UnwrapDiagnosticExtra()
// We'll keep unwrapping until we either find the interface we're
// looking for or we run out of layers of unwrapper.
for {
if ret, ok := extra.(T); ok {
return ret
}
if unwrapper, ok := extra.(DiagnosticExtraUnwrapper); ok {
extra = unwrapper.UnwrapDiagnosticExtra()
} else {
return zero
}
}
}
// DiagnosticExtraUnwrapper is an interface implemented by values in the
// Extra field of Diagnostic when they are wrapping another "Extra" value that
// was generated downstream.
//
// Diagnostic recipients which want to examine "Extra" values to sniff for
// particular types of extra data can either type-assert this interface
// directly and repeatedly unwrap until they recieve nil, or can use the
// helper function DiagnosticExtra.
//
// This interface intentionally matches hcl.DiagnosticExtraUnwrapper, so that
// wrapping extra values implemented using HCL's API will also work with the
// tfdiags API, but that non-HCL uses of this will not need to implement HCL
// just to get this interface.
type DiagnosticExtraUnwrapper interface {
// If the reciever is wrapping another "diagnostic extra" value, returns
// that value. Otherwise returns nil to indicate dynamically that nothing
// is wrapped.
//
// The "nothing is wrapped" condition can be signalled either by this
// method returning nil or by a type not implementing this interface at all.
//
// Implementers should never create unwrap "cycles" where a nested extra
// value returns a value that was also wrapping it.
UnwrapDiagnosticExtra() interface{}
}
// DiagnosticExtraBecauseUnknown is an interface implemented by values in
// the Extra field of Diagnostic when the diagnostic is potentially caused by
// the presence of unknown values in an expression evaluation.
//
// Just implementing this interface is not sufficient signal, though. Callers
// must also call the DiagnosticCausedByUnknown method in order to confirm
// the result, or use the package-level function DiagnosticCausedByUnknown
// as a convenient wrapper.
type DiagnosticExtraBecauseUnknown interface {
// DiagnosticCausedByUnknown returns true if the associated diagnostic
// was caused by the presence of unknown values during an expression
// evaluation, or false otherwise.
//
// Callers might use this to tailor what contextual information they show
// alongside an error report in the UI, to avoid potential confusion
// caused by talking about the presence of unknown values if that was
// immaterial to the error.
DiagnosticCausedByUnknown() bool
}
// DiagnosticCausedByUnknown returns true if the given diagnostic has an
// indication that it was caused by the presence of unknown values during
// an expression evaluation.
//
// This is a wrapper around checking if the diagnostic's extra info implements
// interface DiagnosticExtraBecauseUnknown and then calling its method if so.
func DiagnosticCausedByUnknown(diag Diagnostic) bool {
maybe := ExtraInfo[DiagnosticExtraBecauseUnknown](diag)
if maybe == nil {
return false
}
return maybe.DiagnosticCausedByUnknown()
}
// DiagnosticExtraBecauseSensitive is an interface implemented by values in
// the Extra field of Diagnostic when the diagnostic is potentially caused by
// the presence of sensitive values in an expression evaluation.
//
// Just implementing this interface is not sufficient signal, though. Callers
// must also call the DiagnosticCausedBySensitive method in order to confirm
// the result, or use the package-level function DiagnosticCausedBySensitive
// as a convenient wrapper.
type DiagnosticExtraBecauseSensitive interface {
// DiagnosticCausedBySensitive returns true if the associated diagnostic
// was caused by the presence of sensitive values during an expression
// evaluation, or false otherwise.
//
// Callers might use this to tailor what contextual information they show
// alongside an error report in the UI, to avoid potential confusion
// caused by talking about the presence of sensitive values if that was
// immaterial to the error.
DiagnosticCausedBySensitive() bool
}
// DiagnosticCausedBySensitive returns true if the given diagnostic has an
// indication that it was caused by the presence of sensitive values during
// an expression evaluation.
//
// This is a wrapper around checking if the diagnostic's extra info implements
// interface DiagnosticExtraBecauseSensitive and then calling its method if so.
func DiagnosticCausedBySensitive(diag Diagnostic) bool {
maybe := ExtraInfo[DiagnosticExtraBecauseSensitive](diag)
if maybe == nil {
return false
}
return maybe.DiagnosticCausedBySensitive()
}