diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go index 667ba80..465b230 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go | |||
@@ -3,6 +3,9 @@ package tfdiags | |||
3 | import ( | 3 | import ( |
4 | "bytes" | 4 | "bytes" |
5 | "fmt" | 5 | "fmt" |
6 | "path/filepath" | ||
7 | "sort" | ||
8 | "strings" | ||
6 | 9 | ||
7 | "github.com/hashicorp/errwrap" | 10 | "github.com/hashicorp/errwrap" |
8 | multierror "github.com/hashicorp/go-multierror" | 11 | multierror "github.com/hashicorp/go-multierror" |
@@ -54,6 +57,8 @@ func (diags Diagnostics) Append(new ...interface{}) Diagnostics { | |||
54 | diags = append(diags, ti...) // flatten | 57 | diags = append(diags, ti...) // flatten |
55 | case diagnosticsAsError: | 58 | case diagnosticsAsError: |
56 | diags = diags.Append(ti.Diagnostics) // unwrap | 59 | diags = diags.Append(ti.Diagnostics) // unwrap |
60 | case NonFatalError: | ||
61 | diags = diags.Append(ti.Diagnostics) // unwrap | ||
57 | case hcl.Diagnostics: | 62 | case hcl.Diagnostics: |
58 | for _, hclDiag := range ti { | 63 | for _, hclDiag := range ti { |
59 | diags = append(diags, hclDiagnostic{hclDiag}) | 64 | diags = append(diags, hclDiagnostic{hclDiag}) |
@@ -136,6 +141,54 @@ func (diags Diagnostics) Err() error { | |||
136 | return diagnosticsAsError{diags} | 141 | return diagnosticsAsError{diags} |
137 | } | 142 | } |
138 | 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 | |||
139 | type diagnosticsAsError struct { | 192 | type diagnosticsAsError struct { |
140 | Diagnostics | 193 | Diagnostics |
141 | } | 194 | } |
@@ -179,3 +232,99 @@ func (dae diagnosticsAsError) WrappedErrors() []error { | |||
179 | } | 232 | } |
180 | return errs | 233 | return errs |
181 | } | 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 | } | ||