mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
HCL's diagnostic model now includes the idea of "extra information" which works by attaching an initially-opaque interface value to each diagnostic and then asking callers to type-assert against that value to sniff for particular interfaces in order to discover additional machine-readable context about a certain diagnostic message. This commit echoes that idea into our tfdiags API, for now only for diagnostics that are backed by an hcl.Diagnostic. All other implementations of the diagnostic interface just always return nil, which means they never carry any "extra information". As is typical for our wrapping abstraction, we have here also a modified copy of HCL's helper function for conveniently probing a diagnostic for information of a particular type, designed to work with our diagnostic interface instead of HCL's concrete diagnostic type.
104 lines
4.2 KiB
Go
104 lines
4.2 KiB
Go
package tfdiags
|
|
|
|
// This "Extra" idea is something we've inherited from HCL's diagnostic model,
|
|
// and so it's primarily to expose that functionality from wrapped HCL
|
|
// diagnostics but other diagnostic types could potentially implement this
|
|
// protocol too, if needed.
|
|
|
|
// ExtraInfo tries to retrieve extra information of interface type T from
|
|
// the given diagnostic.
|
|
//
|
|
// "Extra information" is situation-specific additional contextual data which
|
|
// might allow for some special tailored reporting of particular
|
|
// diagnostics in the UI. Conventionally the extra information is provided
|
|
// as a hidden type that implements one or more interfaces which a caller
|
|
// can pass as type parameter T to retrieve a value of that type when the
|
|
// diagnostic has such an implementation.
|
|
//
|
|
// If the given diagnostic's extra value has an implementation of interface T
|
|
// then ExtraInfo returns a non-nil interface value. If there is no such
|
|
// implementation, ExtraInfo returns a nil T.
|
|
//
|
|
// Although the signature of this function does not constrain T to be an
|
|
// interface type, our convention is to only use interface types to access
|
|
// extra info in order to allow for alternative or wrapping implementations
|
|
// of the interface.
|
|
func ExtraInfo[T any](diag Diagnostic) T {
|
|
extra := diag.ExtraInfo()
|
|
if ret, ok := extra.(T); ok {
|
|
return ret
|
|
}
|
|
|
|
// If "extra" doesn't implement T directly then we'll delegate to
|
|
// our ExtraInfoNext helper to try iteratively unwrapping it.
|
|
return ExtraInfoNext[T](extra)
|
|
}
|
|
|
|
// ExtraInfoNext takes a value previously returned by ExtraInfo and attempts
|
|
// to find an implementation of interface T wrapped inside of it. The return
|
|
// value meaning is the same as for ExtraInfo.
|
|
//
|
|
// This is to help with the less common situation where a particular "extra"
|
|
// value might be wrapping another value implementing the same interface,
|
|
// and so callers can peel away one layer at a time until there are no more
|
|
// nested layers.
|
|
//
|
|
// Because this function is intended for searching for _nested_ implementations
|
|
// of T, ExtraInfoNext does not consider whether value "previous" directly
|
|
// implements interface T, on the assumption that the previous call to ExtraInfo
|
|
// with the same T caused "previous" to already be that result.
|
|
func ExtraInfoNext[T any](previous interface{}) T {
|
|
// As long as T is an interface type as documented, zero will always be
|
|
// a nil interface value for us to return in the non-matching case.
|
|
var zero T
|
|
|
|
unwrapper, ok := previous.(DiagnosticExtraUnwrapper)
|
|
// If the given value isn't unwrappable then it can't possibly have
|
|
// any other info nested inside of it.
|
|
if !ok {
|
|
return zero
|
|
}
|
|
|
|
extra := unwrapper.UnwrapDiagnosticExtra()
|
|
|
|
// We'll keep unwrapping until we either find the interface we're
|
|
// looking for or we run out of layers of unwrapper.
|
|
for {
|
|
if ret, ok := extra.(T); ok {
|
|
return ret
|
|
}
|
|
|
|
if unwrapper, ok := extra.(DiagnosticExtraUnwrapper); ok {
|
|
extra = unwrapper.UnwrapDiagnosticExtra()
|
|
} else {
|
|
return zero
|
|
}
|
|
}
|
|
}
|
|
|
|
// DiagnosticExtraUnwrapper is an interface implemented by values in the
|
|
// Extra field of Diagnostic when they are wrapping another "Extra" value that
|
|
// was generated downstream.
|
|
//
|
|
// Diagnostic recipients which want to examine "Extra" values to sniff for
|
|
// particular types of extra data can either type-assert this interface
|
|
// directly and repeatedly unwrap until they recieve nil, or can use the
|
|
// helper function DiagnosticExtra.
|
|
//
|
|
// This interface intentionally matches hcl.DiagnosticExtraUnwrapper, so that
|
|
// wrapping extra values implemented using HCL's API will also work with the
|
|
// tfdiags API, but that non-HCL uses of this will not need to implement HCL
|
|
// just to get this interface.
|
|
type DiagnosticExtraUnwrapper interface {
|
|
// If the reciever is wrapping another "diagnostic extra" value, returns
|
|
// that value. Otherwise returns nil to indicate dynamically that nothing
|
|
// is wrapped.
|
|
//
|
|
// The "nothing is wrapped" condition can be signalled either by this
|
|
// method returning nil or by a type not implementing this interface at all.
|
|
//
|
|
// Implementers should never create unwrap "cycles" where a nested extra
|
|
// value returns a value that was also wrapping it.
|
|
UnwrapDiagnosticExtra() interface{}
|
|
}
|