]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go
deps: github.com/hashicorp/terraform@sdk-v0.11-with-go-modules
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / tfdiags / diagnostics.go
CommitLineData
15c0b25d
AP
1package tfdiags
2
3import (
4 "bytes"
5 "fmt"
6
7 "github.com/hashicorp/errwrap"
8 multierror "github.com/hashicorp/go-multierror"
9 "github.com/hashicorp/hcl2/hcl"
10)
11
12// Diagnostics is a list of diagnostics. Diagnostics is intended to be used
13// where a Go "error" might normally be used, allowing richer information
14// to be conveyed (more context, support for warnings).
15//
16// A nil Diagnostics is a valid, empty diagnostics list, thus allowing
17// heap allocation to be avoided in the common case where there are no
18// diagnostics to report at all.
19type Diagnostics []Diagnostic
20
21// Append is the main interface for constructing Diagnostics lists, taking
22// an existing list (which may be nil) and appending the new objects to it
23// after normalizing them to be implementations of Diagnostic.
24//
25// The usual pattern for a function that natively "speaks" diagnostics is:
26//
27// // Create a nil Diagnostics at the start of the function
28// var diags diag.Diagnostics
29//
30// // At later points, build on it if errors / warnings occur:
31// foo, err := DoSomethingRisky()
32// if err != nil {
33// diags = diags.Append(err)
34// }
35//
36// // Eventually return the result and diagnostics in place of error
37// return result, diags
38//
39// Append accepts a variety of different diagnostic-like types, including
40// native Go errors and HCL diagnostics. It also knows how to unwrap
41// a multierror.Error into separate error diagnostics. It can be passed
42// another Diagnostics to concatenate the two lists. If given something
43// it cannot handle, this function will panic.
44func (diags Diagnostics) Append(new ...interface{}) Diagnostics {
45 for _, item := range new {
46 if item == nil {
47 continue
48 }
49
50 switch ti := item.(type) {
51 case Diagnostic:
52 diags = append(diags, ti)
53 case Diagnostics:
54 diags = append(diags, ti...) // flatten
55 case diagnosticsAsError:
56 diags = diags.Append(ti.Diagnostics) // unwrap
57 case hcl.Diagnostics:
58 for _, hclDiag := range ti {
59 diags = append(diags, hclDiagnostic{hclDiag})
60 }
61 case *hcl.Diagnostic:
62 diags = append(diags, hclDiagnostic{ti})
63 case *multierror.Error:
64 for _, err := range ti.Errors {
65 diags = append(diags, nativeError{err})
66 }
67 case error:
68 switch {
69 case errwrap.ContainsType(ti, Diagnostics(nil)):
70 // If we have an errwrap wrapper with a Diagnostics hiding
71 // inside then we'll unpick it here to get access to the
72 // individual diagnostics.
73 diags = diags.Append(errwrap.GetType(ti, Diagnostics(nil)))
74 case errwrap.ContainsType(ti, hcl.Diagnostics(nil)):
75 // Likewise, if we have HCL diagnostics we'll unpick that too.
76 diags = diags.Append(errwrap.GetType(ti, hcl.Diagnostics(nil)))
77 default:
78 diags = append(diags, nativeError{ti})
79 }
80 default:
81 panic(fmt.Errorf("can't construct diagnostic(s) from %T", item))
82 }
83 }
84
85 // Given the above, we should never end up with a non-nil empty slice
86 // here, but we'll make sure of that so callers can rely on empty == nil
87 if len(diags) == 0 {
88 return nil
89 }
90
91 return diags
92}
93
94// HasErrors returns true if any of the diagnostics in the list have
95// a severity of Error.
96func (diags Diagnostics) HasErrors() bool {
97 for _, diag := range diags {
98 if diag.Severity() == Error {
99 return true
100 }
101 }
102 return false
103}
104
105// ForRPC returns a version of the receiver that has been simplified so that
106// it is friendly to RPC protocols.
107//
108// Currently this means that it can be serialized with encoding/gob and
109// subsequently re-inflated. It may later grow to include other serialization
110// formats.
111//
112// Note that this loses information about the original objects used to
113// construct the diagnostics, so e.g. the errwrap API will not work as
114// expected on an error-wrapped Diagnostics that came from ForRPC.
115func (diags Diagnostics) ForRPC() Diagnostics {
116 ret := make(Diagnostics, len(diags))
117 for i := range diags {
118 ret[i] = makeRPCFriendlyDiag(diags[i])
119 }
120 return ret
121}
122
123// Err flattens a diagnostics list into a single Go error, or to nil
124// if the diagnostics list does not include any error-level diagnostics.
125//
126// This can be used to smuggle diagnostics through an API that deals in
127// native errors, but unfortunately it will lose naked warnings (warnings
128// that aren't accompanied by at least one error) since such APIs have no
129// mechanism through which to report these.
130//
131// return result, diags.Error()
132func (diags Diagnostics) Err() error {
133 if !diags.HasErrors() {
134 return nil
135 }
136 return diagnosticsAsError{diags}
137}
138
139type diagnosticsAsError struct {
140 Diagnostics
141}
142
143func (dae diagnosticsAsError) Error() string {
144 diags := dae.Diagnostics
145 switch {
146 case len(diags) == 0:
147 // should never happen, since we don't create this wrapper if
148 // there are no diagnostics in the list.
149 return "no errors"
150 case len(diags) == 1:
151 desc := diags[0].Description()
152 if desc.Detail == "" {
153 return desc.Summary
154 }
155 return fmt.Sprintf("%s: %s", desc.Summary, desc.Detail)
156 default:
157 var ret bytes.Buffer
158 fmt.Fprintf(&ret, "%d problems:\n", len(diags))
159 for _, diag := range dae.Diagnostics {
160 desc := diag.Description()
161 if desc.Detail == "" {
162 fmt.Fprintf(&ret, "\n- %s", desc.Summary)
163 } else {
164 fmt.Fprintf(&ret, "\n- %s: %s", desc.Summary, desc.Detail)
165 }
166 }
167 return ret.String()
168 }
169}
170
171// WrappedErrors is an implementation of errwrap.Wrapper so that an error-wrapped
172// diagnostics object can be picked apart by errwrap-aware code.
173func (dae diagnosticsAsError) WrappedErrors() []error {
174 var errs []error
175 for _, diag := range dae.Diagnostics {
176 if wrapper, isErr := diag.(nativeError); isErr {
177 errs = append(errs, wrapper.err)
178 }
179 }
180 return errs
181}