]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / tfdiags / diagnostics.go
1 package tfdiags
2
3 import (
4 "bytes"
5 "fmt"
6 "path/filepath"
7 "sort"
8 "strings"
9
10 "github.com/hashicorp/errwrap"
11 multierror "github.com/hashicorp/go-multierror"
12 "github.com/hashicorp/hcl2/hcl"
13 )
14
15 // Diagnostics is a list of diagnostics. Diagnostics is intended to be used
16 // where a Go "error" might normally be used, allowing richer information
17 // to be conveyed (more context, support for warnings).
18 //
19 // A nil Diagnostics is a valid, empty diagnostics list, thus allowing
20 // heap allocation to be avoided in the common case where there are no
21 // diagnostics to report at all.
22 type Diagnostics []Diagnostic
23
24 // Append is the main interface for constructing Diagnostics lists, taking
25 // an existing list (which may be nil) and appending the new objects to it
26 // after normalizing them to be implementations of Diagnostic.
27 //
28 // The usual pattern for a function that natively "speaks" diagnostics is:
29 //
30 // // Create a nil Diagnostics at the start of the function
31 // var diags diag.Diagnostics
32 //
33 // // At later points, build on it if errors / warnings occur:
34 // foo, err := DoSomethingRisky()
35 // if err != nil {
36 // diags = diags.Append(err)
37 // }
38 //
39 // // Eventually return the result and diagnostics in place of error
40 // return result, diags
41 //
42 // Append accepts a variety of different diagnostic-like types, including
43 // native Go errors and HCL diagnostics. It also knows how to unwrap
44 // a multierror.Error into separate error diagnostics. It can be passed
45 // another Diagnostics to concatenate the two lists. If given something
46 // it cannot handle, this function will panic.
47 func (diags Diagnostics) Append(new ...interface{}) Diagnostics {
48 for _, item := range new {
49 if item == nil {
50 continue
51 }
52
53 switch ti := item.(type) {
54 case Diagnostic:
55 diags = append(diags, ti)
56 case Diagnostics:
57 diags = append(diags, ti...) // flatten
58 case diagnosticsAsError:
59 diags = diags.Append(ti.Diagnostics) // unwrap
60 case NonFatalError:
61 diags = diags.Append(ti.Diagnostics) // unwrap
62 case hcl.Diagnostics:
63 for _, hclDiag := range ti {
64 diags = append(diags, hclDiagnostic{hclDiag})
65 }
66 case *hcl.Diagnostic:
67 diags = append(diags, hclDiagnostic{ti})
68 case *multierror.Error:
69 for _, err := range ti.Errors {
70 diags = append(diags, nativeError{err})
71 }
72 case error:
73 switch {
74 case errwrap.ContainsType(ti, Diagnostics(nil)):
75 // If we have an errwrap wrapper with a Diagnostics hiding
76 // inside then we'll unpick it here to get access to the
77 // individual diagnostics.
78 diags = diags.Append(errwrap.GetType(ti, Diagnostics(nil)))
79 case errwrap.ContainsType(ti, hcl.Diagnostics(nil)):
80 // Likewise, if we have HCL diagnostics we'll unpick that too.
81 diags = diags.Append(errwrap.GetType(ti, hcl.Diagnostics(nil)))
82 default:
83 diags = append(diags, nativeError{ti})
84 }
85 default:
86 panic(fmt.Errorf("can't construct diagnostic(s) from %T", item))
87 }
88 }
89
90 // Given the above, we should never end up with a non-nil empty slice
91 // here, but we'll make sure of that so callers can rely on empty == nil
92 if len(diags) == 0 {
93 return nil
94 }
95
96 return diags
97 }
98
99 // HasErrors returns true if any of the diagnostics in the list have
100 // a severity of Error.
101 func (diags Diagnostics) HasErrors() bool {
102 for _, diag := range diags {
103 if diag.Severity() == Error {
104 return true
105 }
106 }
107 return false
108 }
109
110 // ForRPC returns a version of the receiver that has been simplified so that
111 // it is friendly to RPC protocols.
112 //
113 // Currently this means that it can be serialized with encoding/gob and
114 // subsequently re-inflated. It may later grow to include other serialization
115 // formats.
116 //
117 // Note that this loses information about the original objects used to
118 // construct the diagnostics, so e.g. the errwrap API will not work as
119 // expected on an error-wrapped Diagnostics that came from ForRPC.
120 func (diags Diagnostics) ForRPC() Diagnostics {
121 ret := make(Diagnostics, len(diags))
122 for i := range diags {
123 ret[i] = makeRPCFriendlyDiag(diags[i])
124 }
125 return ret
126 }
127
128 // Err flattens a diagnostics list into a single Go error, or to nil
129 // if the diagnostics list does not include any error-level diagnostics.
130 //
131 // This can be used to smuggle diagnostics through an API that deals in
132 // native errors, but unfortunately it will lose naked warnings (warnings
133 // that aren't accompanied by at least one error) since such APIs have no
134 // mechanism through which to report these.
135 //
136 // return result, diags.Error()
137 func (diags Diagnostics) Err() error {
138 if !diags.HasErrors() {
139 return nil
140 }
141 return diagnosticsAsError{diags}
142 }
143
144 // ErrWithWarnings is similar to Err except that it will also return a non-nil
145 // error if the receiver contains only warnings.
146 //
147 // In the warnings-only situation, the result is guaranteed to be of dynamic
148 // type NonFatalError, allowing diagnostics-aware callers to type-assert
149 // and unwrap it, treating it as non-fatal.
150 //
151 // This should be used only in contexts where the caller is able to recognize
152 // and handle NonFatalError. For normal callers that expect a lack of errors
153 // to be signaled by nil, use just Diagnostics.Err.
154 func (diags Diagnostics) ErrWithWarnings() error {
155 if len(diags) == 0 {
156 return nil
157 }
158 if diags.HasErrors() {
159 return diags.Err()
160 }
161 return NonFatalError{diags}
162 }
163
164 // NonFatalErr is similar to Err except that it always returns either nil
165 // (if there are no diagnostics at all) or NonFatalError.
166 //
167 // This allows diagnostics to be returned over an error return channel while
168 // being explicit that the diagnostics should not halt processing.
169 //
170 // This should be used only in contexts where the caller is able to recognize
171 // and handle NonFatalError. For normal callers that expect a lack of errors
172 // to be signaled by nil, use just Diagnostics.Err.
173 func (diags Diagnostics) NonFatalErr() error {
174 if len(diags) == 0 {
175 return nil
176 }
177 return NonFatalError{diags}
178 }
179
180 // Sort applies an ordering to the diagnostics in the receiver in-place.
181 //
182 // The ordering is: warnings before errors, sourceless before sourced,
183 // short source paths before long source paths, and then ordering by
184 // position within each file.
185 //
186 // Diagnostics that do not differ by any of these sortable characteristics
187 // will remain in the same relative order after this method returns.
188 func (diags Diagnostics) Sort() {
189 sort.Stable(sortDiagnostics(diags))
190 }
191
192 type diagnosticsAsError struct {
193 Diagnostics
194 }
195
196 func (dae diagnosticsAsError) Error() string {
197 diags := dae.Diagnostics
198 switch {
199 case len(diags) == 0:
200 // should never happen, since we don't create this wrapper if
201 // there are no diagnostics in the list.
202 return "no errors"
203 case len(diags) == 1:
204 desc := diags[0].Description()
205 if desc.Detail == "" {
206 return desc.Summary
207 }
208 return fmt.Sprintf("%s: %s", desc.Summary, desc.Detail)
209 default:
210 var ret bytes.Buffer
211 fmt.Fprintf(&ret, "%d problems:\n", len(diags))
212 for _, diag := range dae.Diagnostics {
213 desc := diag.Description()
214 if desc.Detail == "" {
215 fmt.Fprintf(&ret, "\n- %s", desc.Summary)
216 } else {
217 fmt.Fprintf(&ret, "\n- %s: %s", desc.Summary, desc.Detail)
218 }
219 }
220 return ret.String()
221 }
222 }
223
224 // WrappedErrors is an implementation of errwrap.Wrapper so that an error-wrapped
225 // diagnostics object can be picked apart by errwrap-aware code.
226 func (dae diagnosticsAsError) WrappedErrors() []error {
227 var errs []error
228 for _, diag := range dae.Diagnostics {
229 if wrapper, isErr := diag.(nativeError); isErr {
230 errs = append(errs, wrapper.err)
231 }
232 }
233 return errs
234 }
235
236 // NonFatalError is a special error type, returned by
237 // Diagnostics.ErrWithWarnings and Diagnostics.NonFatalErr,
238 // that indicates that the wrapped diagnostics should be treated as non-fatal.
239 // Callers can conditionally type-assert an error to this type in order to
240 // detect the non-fatal scenario and handle it in a different way.
241 type NonFatalError struct {
242 Diagnostics
243 }
244
245 func (woe NonFatalError) Error() string {
246 diags := woe.Diagnostics
247 switch {
248 case len(diags) == 0:
249 // should never happen, since we don't create this wrapper if
250 // there are no diagnostics in the list.
251 return "no errors or warnings"
252 case len(diags) == 1:
253 desc := diags[0].Description()
254 if desc.Detail == "" {
255 return desc.Summary
256 }
257 return fmt.Sprintf("%s: %s", desc.Summary, desc.Detail)
258 default:
259 var ret bytes.Buffer
260 if diags.HasErrors() {
261 fmt.Fprintf(&ret, "%d problems:\n", len(diags))
262 } else {
263 fmt.Fprintf(&ret, "%d warnings:\n", len(diags))
264 }
265 for _, diag := range woe.Diagnostics {
266 desc := diag.Description()
267 if desc.Detail == "" {
268 fmt.Fprintf(&ret, "\n- %s", desc.Summary)
269 } else {
270 fmt.Fprintf(&ret, "\n- %s: %s", desc.Summary, desc.Detail)
271 }
272 }
273 return ret.String()
274 }
275 }
276
277 // sortDiagnostics is an implementation of sort.Interface
278 type sortDiagnostics []Diagnostic
279
280 var _ sort.Interface = sortDiagnostics(nil)
281
282 func (sd sortDiagnostics) Len() int {
283 return len(sd)
284 }
285
286 func (sd sortDiagnostics) Less(i, j int) bool {
287 iD, jD := sd[i], sd[j]
288 iSev, jSev := iD.Severity(), jD.Severity()
289 iSrc, jSrc := iD.Source(), jD.Source()
290
291 switch {
292
293 case iSev != jSev:
294 return iSev == Warning
295
296 case (iSrc.Subject == nil) != (jSrc.Subject == nil):
297 return iSrc.Subject == nil
298
299 case iSrc.Subject != nil && *iSrc.Subject != *jSrc.Subject:
300 iSubj := iSrc.Subject
301 jSubj := jSrc.Subject
302 switch {
303 case iSubj.Filename != jSubj.Filename:
304 // Path with fewer segments goes first if they are different lengths
305 sep := string(filepath.Separator)
306 iCount := strings.Count(iSubj.Filename, sep)
307 jCount := strings.Count(jSubj.Filename, sep)
308 if iCount != jCount {
309 return iCount < jCount
310 }
311 return iSubj.Filename < jSubj.Filename
312 case iSubj.Start.Byte != jSubj.Start.Byte:
313 return iSubj.Start.Byte < jSubj.Start.Byte
314 case iSubj.End.Byte != jSubj.End.Byte:
315 return iSubj.End.Byte < jSubj.End.Byte
316 }
317 fallthrough
318
319 default:
320 // The remaining properties do not have a defined ordering, so
321 // we'll leave it unspecified. Since we use sort.Stable in
322 // the caller of this, the ordering of remaining items will
323 // be preserved.
324 return false
325 }
326 }
327
328 func (sd sortDiagnostics) Swap(i, j int) {
329 sd[i], sd[j] = sd[j], sd[i]
330 }