github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/tfdiags/diagnostic_extra.go (about) 1 package tfdiags 2 3 // This "Extra" idea is something we've inherited from HCL's diagnostic model, 4 // and so it's primarily to expose that functionality from wrapped HCL 5 // diagnostics but other diagnostic types could potentially implement this 6 // protocol too, if needed. 7 8 // ExtraInfo tries to retrieve extra information of interface type T from 9 // the given diagnostic. 10 // 11 // "Extra information" is situation-specific additional contextual data which 12 // might allow for some special tailored reporting of particular 13 // diagnostics in the UI. Conventionally the extra information is provided 14 // as a hidden type that implements one or more interfaces which a caller 15 // can pass as type parameter T to retrieve a value of that type when the 16 // diagnostic has such an implementation. 17 // 18 // If the given diagnostic's extra value has an implementation of interface T 19 // then ExtraInfo returns a non-nil interface value. If there is no such 20 // implementation, ExtraInfo returns a nil T. 21 // 22 // Although the signature of this function does not constrain T to be an 23 // interface type, our convention is to only use interface types to access 24 // extra info in order to allow for alternative or wrapping implementations 25 // of the interface. 26 func ExtraInfo[T any](diag Diagnostic) T { 27 extra := diag.ExtraInfo() 28 if ret, ok := extra.(T); ok { 29 return ret 30 } 31 32 // If "extra" doesn't implement T directly then we'll delegate to 33 // our ExtraInfoNext helper to try iteratively unwrapping it. 34 return ExtraInfoNext[T](extra) 35 } 36 37 // ExtraInfoNext takes a value previously returned by ExtraInfo and attempts 38 // to find an implementation of interface T wrapped inside of it. The return 39 // value meaning is the same as for ExtraInfo. 40 // 41 // This is to help with the less common situation where a particular "extra" 42 // value might be wrapping another value implementing the same interface, 43 // and so callers can peel away one layer at a time until there are no more 44 // nested layers. 45 // 46 // Because this function is intended for searching for _nested_ implementations 47 // of T, ExtraInfoNext does not consider whether value "previous" directly 48 // implements interface T, on the assumption that the previous call to ExtraInfo 49 // with the same T caused "previous" to already be that result. 50 func ExtraInfoNext[T any](previous interface{}) T { 51 // As long as T is an interface type as documented, zero will always be 52 // a nil interface value for us to return in the non-matching case. 53 var zero T 54 55 unwrapper, ok := previous.(DiagnosticExtraUnwrapper) 56 // If the given value isn't unwrappable then it can't possibly have 57 // any other info nested inside of it. 58 if !ok { 59 return zero 60 } 61 62 extra := unwrapper.UnwrapDiagnosticExtra() 63 64 // We'll keep unwrapping until we either find the interface we're 65 // looking for or we run out of layers of unwrapper. 66 for { 67 if ret, ok := extra.(T); ok { 68 return ret 69 } 70 71 if unwrapper, ok := extra.(DiagnosticExtraUnwrapper); ok { 72 extra = unwrapper.UnwrapDiagnosticExtra() 73 } else { 74 return zero 75 } 76 } 77 } 78 79 // DiagnosticExtraUnwrapper is an interface implemented by values in the 80 // Extra field of Diagnostic when they are wrapping another "Extra" value that 81 // was generated downstream. 82 // 83 // Diagnostic recipients which want to examine "Extra" values to sniff for 84 // particular types of extra data can either type-assert this interface 85 // directly and repeatedly unwrap until they recieve nil, or can use the 86 // helper function DiagnosticExtra. 87 // 88 // This interface intentionally matches hcl.DiagnosticExtraUnwrapper, so that 89 // wrapping extra values implemented using HCL's API will also work with the 90 // tfdiags API, but that non-HCL uses of this will not need to implement HCL 91 // just to get this interface. 92 type DiagnosticExtraUnwrapper interface { 93 // If the reciever is wrapping another "diagnostic extra" value, returns 94 // that value. Otherwise returns nil to indicate dynamically that nothing 95 // is wrapped. 96 // 97 // The "nothing is wrapped" condition can be signalled either by this 98 // method returning nil or by a type not implementing this interface at all. 99 // 100 // Implementers should never create unwrap "cycles" where a nested extra 101 // value returns a value that was also wrapping it. 102 UnwrapDiagnosticExtra() interface{} 103 } 104 105 // DiagnosticExtraBecauseUnknown is an interface implemented by values in 106 // the Extra field of Diagnostic when the diagnostic is potentially caused by 107 // the presence of unknown values in an expression evaluation. 108 // 109 // Just implementing this interface is not sufficient signal, though. Callers 110 // must also call the DiagnosticCausedByUnknown method in order to confirm 111 // the result, or use the package-level function DiagnosticCausedByUnknown 112 // as a convenient wrapper. 113 type DiagnosticExtraBecauseUnknown interface { 114 // DiagnosticCausedByUnknown returns true if the associated diagnostic 115 // was caused by the presence of unknown values during an expression 116 // evaluation, or false otherwise. 117 // 118 // Callers might use this to tailor what contextual information they show 119 // alongside an error report in the UI, to avoid potential confusion 120 // caused by talking about the presence of unknown values if that was 121 // immaterial to the error. 122 DiagnosticCausedByUnknown() bool 123 } 124 125 // DiagnosticCausedByUnknown returns true if the given diagnostic has an 126 // indication that it was caused by the presence of unknown values during 127 // an expression evaluation. 128 // 129 // This is a wrapper around checking if the diagnostic's extra info implements 130 // interface DiagnosticExtraBecauseUnknown and then calling its method if so. 131 func DiagnosticCausedByUnknown(diag Diagnostic) bool { 132 maybe := ExtraInfo[DiagnosticExtraBecauseUnknown](diag) 133 if maybe == nil { 134 return false 135 } 136 return maybe.DiagnosticCausedByUnknown() 137 } 138 139 // DiagnosticExtraBecauseSensitive is an interface implemented by values in 140 // the Extra field of Diagnostic when the diagnostic is potentially caused by 141 // the presence of sensitive values in an expression evaluation. 142 // 143 // Just implementing this interface is not sufficient signal, though. Callers 144 // must also call the DiagnosticCausedBySensitive method in order to confirm 145 // the result, or use the package-level function DiagnosticCausedBySensitive 146 // as a convenient wrapper. 147 type DiagnosticExtraBecauseSensitive interface { 148 // DiagnosticCausedBySensitive returns true if the associated diagnostic 149 // was caused by the presence of sensitive values during an expression 150 // evaluation, or false otherwise. 151 // 152 // Callers might use this to tailor what contextual information they show 153 // alongside an error report in the UI, to avoid potential confusion 154 // caused by talking about the presence of sensitive values if that was 155 // immaterial to the error. 156 DiagnosticCausedBySensitive() bool 157 } 158 159 // DiagnosticCausedBySensitive returns true if the given diagnostic has an 160 // indication that it was caused by the presence of sensitive values during 161 // an expression evaluation. 162 // 163 // This is a wrapper around checking if the diagnostic's extra info implements 164 // interface DiagnosticExtraBecauseSensitive and then calling its method if so. 165 func DiagnosticCausedBySensitive(diag Diagnostic) bool { 166 maybe := ExtraInfo[DiagnosticExtraBecauseSensitive](diag) 167 if maybe == nil { 168 return false 169 } 170 return maybe.DiagnosticCausedBySensitive() 171 }