diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go new file mode 100644 index 0000000..667ba80 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go | |||
@@ -0,0 +1,181 @@ | |||
1 | package tfdiags | ||
2 | |||
3 | import ( | ||
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. | ||
19 | type 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. | ||
44 | func (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. | ||
96 | func (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. | ||
115 | func (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() | ||
132 | func (diags Diagnostics) Err() error { | ||
133 | if !diags.HasErrors() { | ||
134 | return nil | ||
135 | } | ||
136 | return diagnosticsAsError{diags} | ||
137 | } | ||
138 | |||
139 | type diagnosticsAsError struct { | ||
140 | Diagnostics | ||
141 | } | ||
142 | |||
143 | func (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. | ||
173 | func (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 | } | ||