github.com/hashicorp/hcl/v2@v2.20.0/diagnostic.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcl 5 6 import ( 7 "fmt" 8 ) 9 10 // DiagnosticSeverity represents the severity of a diagnostic. 11 type DiagnosticSeverity int 12 13 const ( 14 // DiagInvalid is the invalid zero value of DiagnosticSeverity 15 DiagInvalid DiagnosticSeverity = iota 16 17 // DiagError indicates that the problem reported by a diagnostic prevents 18 // further progress in parsing and/or evaluating the subject. 19 DiagError 20 21 // DiagWarning indicates that the problem reported by a diagnostic warrants 22 // user attention but does not prevent further progress. It is most 23 // commonly used for showing deprecation notices. 24 DiagWarning 25 ) 26 27 // Diagnostic represents information to be presented to a user about an 28 // error or anomaly in parsing or evaluating configuration. 29 type Diagnostic struct { 30 Severity DiagnosticSeverity 31 32 // Summary and Detail contain the English-language description of the 33 // problem. Summary is a terse description of the general problem and 34 // detail is a more elaborate, often-multi-sentence description of 35 // the problem and what might be done to solve it. 36 Summary string 37 Detail string 38 39 // Subject and Context are both source ranges relating to the diagnostic. 40 // 41 // Subject is a tight range referring to exactly the construct that 42 // is problematic, while Context is an optional broader range (which should 43 // fully contain Subject) that ought to be shown around Subject when 44 // generating isolated source-code snippets in diagnostic messages. 45 // If Context is nil, the Subject is also the Context. 46 // 47 // Some diagnostics have no source ranges at all. If Context is set then 48 // Subject should always also be set. 49 Subject *Range 50 Context *Range 51 52 // For diagnostics that occur when evaluating an expression, Expression 53 // may refer to that expression and EvalContext may point to the 54 // EvalContext that was active when evaluating it. This may allow for the 55 // inclusion of additional useful information when rendering a diagnostic 56 // message to the user. 57 // 58 // It is not always possible to select a single EvalContext for a 59 // diagnostic, and so in some cases this field may be nil even when an 60 // expression causes a problem. 61 // 62 // EvalContexts form a tree, so the given EvalContext may refer to a parent 63 // which in turn refers to another parent, etc. For a full picture of all 64 // of the active variables and functions the caller must walk up this 65 // chain, preferring definitions that are "closer" to the expression in 66 // case of colliding names. 67 Expression Expression 68 EvalContext *EvalContext 69 70 // Extra is an extension point for additional machine-readable information 71 // about this problem. 72 // 73 // Recipients of diagnostic objects may type-assert this value with 74 // specific interface types they know about to discover if any additional 75 // information is available that is interesting for their use-case. 76 // 77 // Extra is always considered to be optional extra information and so a 78 // diagnostic message should still always be fully described (from the 79 // perspective of a human who understands the language the messages are 80 // written in) by the other fields in case a particular recipient. 81 // 82 // Functions that return diagnostics with Extra populated should typically 83 // document that they place values implementing a particular interface, 84 // rather than a concrete type, and define that interface such that its 85 // methods can dynamically indicate a lack of support at runtime even 86 // if the interface happens to be statically available. An Extra 87 // type that wraps other Extra values should additionally implement 88 // interface DiagnosticExtraUnwrapper to return the value they are wrapping 89 // so that callers can access inner values to type-assert against. 90 Extra interface{} 91 } 92 93 // Diagnostics is a list of Diagnostic instances. 94 type Diagnostics []*Diagnostic 95 96 // error implementation, so that diagnostics can be returned via APIs 97 // that normally deal in vanilla Go errors. 98 // 99 // This presents only minimal context about the error, for compatibility 100 // with usual expectations about how errors will present as strings. 101 func (d *Diagnostic) Error() string { 102 return fmt.Sprintf("%s: %s; %s", d.Subject, d.Summary, d.Detail) 103 } 104 105 // error implementation, so that sets of diagnostics can be returned via 106 // APIs that normally deal in vanilla Go errors. 107 func (d Diagnostics) Error() string { 108 count := len(d) 109 switch { 110 case count == 0: 111 return "no diagnostics" 112 case count == 1: 113 return d[0].Error() 114 default: 115 return fmt.Sprintf("%s, and %d other diagnostic(s)", d[0].Error(), count-1) 116 } 117 } 118 119 // Append appends a new error to a Diagnostics and return the whole Diagnostics. 120 // 121 // This is provided as a convenience for returning from a function that 122 // collects and then returns a set of diagnostics: 123 // 124 // return nil, diags.Append(&hcl.Diagnostic{ ... }) 125 // 126 // Note that this modifies the array underlying the diagnostics slice, so 127 // must be used carefully within a single codepath. It is incorrect (and rude) 128 // to extend a diagnostics created by a different subsystem. 129 func (d Diagnostics) Append(diag *Diagnostic) Diagnostics { 130 return append(d, diag) 131 } 132 133 // Extend concatenates the given Diagnostics with the receiver and returns 134 // the whole new Diagnostics. 135 // 136 // This is similar to Append but accepts multiple diagnostics to add. It has 137 // all the same caveats and constraints. 138 func (d Diagnostics) Extend(diags Diagnostics) Diagnostics { 139 return append(d, diags...) 140 } 141 142 // HasErrors returns true if the receiver contains any diagnostics of 143 // severity DiagError. 144 func (d Diagnostics) HasErrors() bool { 145 for _, diag := range d { 146 if diag.Severity == DiagError { 147 return true 148 } 149 } 150 return false 151 } 152 153 func (d Diagnostics) Errs() []error { 154 var errs []error 155 for _, diag := range d { 156 if diag.Severity == DiagError { 157 errs = append(errs, diag) 158 } 159 } 160 161 return errs 162 } 163 164 // A DiagnosticWriter emits diagnostics somehow. 165 type DiagnosticWriter interface { 166 WriteDiagnostic(*Diagnostic) error 167 WriteDiagnostics(Diagnostics) error 168 } 169 170 // DiagnosticExtraUnwrapper is an interface implemented by values in the 171 // Extra field of Diagnostic when they are wrapping another "Extra" value that 172 // was generated downstream. 173 // 174 // Diagnostic recipients which want to examine "Extra" values to sniff for 175 // particular types of extra data can either type-assert this interface 176 // directly and repeatedly unwrap until they recieve nil, or can use the 177 // helper function DiagnosticExtra. 178 type DiagnosticExtraUnwrapper interface { 179 // If the reciever is wrapping another "diagnostic extra" value, returns 180 // that value. Otherwise returns nil to indicate dynamically that nothing 181 // is wrapped. 182 // 183 // The "nothing is wrapped" condition can be signalled either by this 184 // method returning nil or by a type not implementing this interface at all. 185 // 186 // Implementers should never create unwrap "cycles" where a nested extra 187 // value returns a value that was also wrapping it. 188 UnwrapDiagnosticExtra() interface{} 189 }