Documentation ¶
Overview ¶
Package tfdiags is a utility package for representing errors and warnings in a manner that allows us to produce good messages for the user.
"diag" is short for "diagnostics", and is meant as a general word for feedback to a user about potential or actual problems.
A design goal for this package is for it to be able to provide rich messaging where possible but to also be pragmatic about dealing with generic errors produced by system components that _can't_ provide such rich messaging. As a consequence, the main types in this package -- Diagnostics and Diagnostic -- are designed so that they can be "smuggled" over an error channel and then be unpacked at the other end, so that error diagnostics (at least) can transit through APIs that are not aware of this package.
Index ¶
- func DiagnosticCausedBySensitive(diag Diagnostic) bool
- func DiagnosticCausedByUnknown(diag Diagnostic) bool
- func DoNotConsolidateDiagnostic(diag Diagnostic) bool
- func ExtraInfo[T any](diag Diagnostic) T
- func ExtraInfoNext[T any](previous interface{}) T
- func FormatCtyPath(path cty.Path) string
- func FormatError(err error) string
- func FormatErrorPrefixed(err error, prefix string) string
- func GetAttribute(d Diagnostic) cty.Path
- type Description
- type Diagnostic
- func AttributeValue(severity Severity, summary, detail string, attrPath cty.Path) Diagnostic
- func Override(original Diagnostic, severity Severity, ...) Diagnostic
- func SimpleWarning(msg string) Diagnostic
- func Sourceless(severity Severity, summary, detail string) Diagnostic
- func UndoOverride(diag Diagnostic) Diagnostic
- func WholeContainingBody(severity Severity, summary, detail string) Diagnostic
- type DiagnosticExtraBecauseSensitive
- type DiagnosticExtraBecauseUnknown
- type DiagnosticExtraDoNotConsolidate
- type DiagnosticExtraUnwrapper
- type DiagnosticExtraWrapper
- type Diagnostics
- func (diags Diagnostics) Append(new ...interface{}) Diagnostics
- func (diags Diagnostics) ConsolidateWarnings(threshold int) Diagnostics
- func (diags Diagnostics) Err() error
- func (diags Diagnostics) ErrWithWarnings() error
- func (diags Diagnostics) ForRPC() Diagnostics
- func (diags Diagnostics) HasErrors() bool
- func (diags Diagnostics) InConfigBody(body hcl.Body, addr string) Diagnostics
- func (diags Diagnostics) NonFatalErr() error
- func (diags Diagnostics) Sort()
- func (diags Diagnostics) ToHCL() hcl.Diagnostics
- type FromExpr
- type NonFatalError
- type Severity
- type Source
- type SourcePos
- type SourceRange
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DiagnosticCausedBySensitive ¶
func DiagnosticCausedBySensitive(diag Diagnostic) bool
DiagnosticCausedBySensitive returns true if the given diagnostic has an indication that it was caused by the presence of sensitive values during an expression evaluation.
This is a wrapper around checking if the diagnostic's extra info implements interface DiagnosticExtraBecauseSensitive and then calling its method if so.
func DiagnosticCausedByUnknown ¶
func DiagnosticCausedByUnknown(diag Diagnostic) bool
DiagnosticCausedByUnknown returns true if the given diagnostic has an indication that it was caused by the presence of unknown values during an expression evaluation.
This is a wrapper around checking if the diagnostic's extra info implements interface DiagnosticExtraBecauseUnknown and then calling its method if so.
func DoNotConsolidateDiagnostic ¶
func DoNotConsolidateDiagnostic(diag Diagnostic) bool
DoNotConsolidateDiagnostic returns true if the given diagnostic should not be consolidated by the Diagnostics.ConsolidateWarnings function.
func ExtraInfo ¶
func ExtraInfo[T any](diag Diagnostic) T
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 ExtraInfoNext ¶
func ExtraInfoNext[T any](previous interface{}) T
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 FormatCtyPath ¶
FormatCtyPath is a helper function to produce a user-friendly string representation of a cty.Path. The result uses a syntax similar to the HCL expression language in the hope of it being familiar to users.
func FormatError ¶
FormatError is a helper function to produce a user-friendly string representation of certain special error types that we might want to include in diagnostic messages.
This currently has special behavior only for cty.PathError, where a non-empty path is rendered in a HCL-like syntax as context.
func FormatErrorPrefixed ¶
FormatErrorPrefixed is like FormatError except that it presents any path information after the given prefix string, which is assumed to contain an HCL syntax representation of the value that errors are relative to.
func GetAttribute ¶
func GetAttribute(d Diagnostic) cty.Path
GetAttribute extracts an attribute cty.Path from a diagnostic if it contains one. Normally this is not accessed directly, and instead the config body is added to the Diagnostic to create a more complete message for the user. In some cases however, we may want to know just the name of the attribute that generated the Diagnostic message. This returns a nil cty.Path if it does not exist in the Diagnostic.
Types ¶
type Description ¶
type Diagnostic ¶
type Diagnostic interface { Severity() Severity Description() Description Source() Source // FromExpr returns the expression-related context for the diagnostic, if // available. Returns nil if the diagnostic is not related to an // expression evaluation. FromExpr() *FromExpr // ExtraInfo returns the raw extra information value. This is a low-level // API which requires some work on the part of the caller to properly // access associated information, so in most cases it'll be more convienient // to use the package-level ExtraInfo function to try to unpack a particular // specialized interface from this value. ExtraInfo() interface{} }
func AttributeValue ¶
func AttributeValue(severity Severity, summary, detail string, attrPath cty.Path) Diagnostic
AttributeValue returns a diagnostic about an attribute value in an implied current configuration context. This should be returned only from functions whose interface specifies a clear configuration context that this will be resolved in.
The given path is relative to the implied configuration context. To describe a top-level attribute, it should be a single-element cty.Path with a cty.GetAttrStep. It's assumed that the path is returning into a structure that would be produced by our conventions in the configschema package; it may return unexpected results for structures that can't be represented by configschema.
Since mapping attribute paths back onto configuration is an imprecise operation (e.g. dynamic block generation may cause the same block to be evaluated multiple times) the diagnostic detail should include the attribute name and other context required to help the user understand what is being referenced in case the identified source range is not unique.
The returned attribute will not have source location information until context is applied to the containing diagnostics using diags.InConfigBody. After context is applied, the source location is the value assigned to the named attribute, or the containing body's "missing item range" if no value is present.
func Override ¶
func Override(original Diagnostic, severity Severity, createExtra func() DiagnosticExtraWrapper) Diagnostic
Override matches OverrideAll except it operates over a single Diagnostic rather than multiple Diagnostics.
func SimpleWarning ¶
func SimpleWarning(msg string) Diagnostic
SimpleWarning constructs a simple (summary-only) warning diagnostic.
func Sourceless ¶
func Sourceless(severity Severity, summary, detail string) Diagnostic
Sourceless creates and returns a diagnostic with no source location information. This is generally used for operational-type errors that are caused by or relate to the environment where OpenTofu is running rather than to the provided configuration.
func UndoOverride ¶
func UndoOverride(diag Diagnostic) Diagnostic
UndoOverride will return the original diagnostic that was overridden within the OverrideAll function.
If the provided Diagnostic was never overridden then it is simply returned unchanged.
func WholeContainingBody ¶
func WholeContainingBody(severity Severity, summary, detail string) Diagnostic
WholeContainingBody returns a diagnostic about the body that is an implied current configuration context. This should be returned only from functions whose interface specifies a clear configuration context that this will be resolved in.
The returned attribute will not have source location information until context is applied to the containing diagnostics using diags.InConfigBody. After context is applied, the source location is currently the missing item range of the body. In future, this may change to some other suitable part of the containing body.
type DiagnosticExtraBecauseSensitive ¶
type DiagnosticExtraBecauseSensitive interface { // DiagnosticCausedBySensitive returns true if the associated diagnostic // was caused by the presence of sensitive values during an expression // evaluation, or false otherwise. // // Callers might use this to tailor what contextual information they show // alongside an error report in the UI, to avoid potential confusion // caused by talking about the presence of sensitive values if that was // immaterial to the error. DiagnosticCausedBySensitive() bool }
DiagnosticExtraBecauseSensitive is an interface implemented by values in the Extra field of Diagnostic when the diagnostic is potentially caused by the presence of sensitive values in an expression evaluation.
Just implementing this interface is not sufficient signal, though. Callers must also call the DiagnosticCausedBySensitive method in order to confirm the result, or use the package-level function DiagnosticCausedBySensitive as a convenient wrapper.
type DiagnosticExtraBecauseUnknown ¶
type DiagnosticExtraBecauseUnknown interface { // DiagnosticCausedByUnknown returns true if the associated diagnostic // was caused by the presence of unknown values during an expression // evaluation, or false otherwise. // // Callers might use this to tailor what contextual information they show // alongside an error report in the UI, to avoid potential confusion // caused by talking about the presence of unknown values if that was // immaterial to the error. DiagnosticCausedByUnknown() bool }
DiagnosticExtraBecauseUnknown is an interface implemented by values in the Extra field of Diagnostic when the diagnostic is potentially caused by the presence of unknown values in an expression evaluation.
Just implementing this interface is not sufficient signal, though. Callers must also call the DiagnosticCausedByUnknown method in order to confirm the result, or use the package-level function DiagnosticCausedByUnknown as a convenient wrapper.
type DiagnosticExtraDoNotConsolidate ¶
type DiagnosticExtraDoNotConsolidate interface { // DoNotConsolidateDiagnostic returns true if the associated diagnostic // should not be consolidated by the Diagnostics.ConsolidateWarnings // function. DoNotConsolidateDiagnostic() bool }
DiagnosticExtraDoNotConsolidate tells the Diagnostics.ConsolidateWarnings function not to consolidate this diagnostic if it otherwise would.
type DiagnosticExtraUnwrapper ¶
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{} }
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 DiagnosticExtraWrapper ¶
type DiagnosticExtraWrapper interface { // WrapDiagnosticExtra accepts an ExtraInfo that it should add within the // current ExtraInfo. WrapDiagnosticExtra(inner interface{}) }
DiagnosticExtraWrapper is an interface implemented by values that can be dynamically updated to wrap other extra info.
type Diagnostics ¶
type Diagnostics []Diagnostic
Diagnostics is a list of diagnostics. Diagnostics is intended to be used where a Go "error" might normally be used, allowing richer information to be conveyed (more context, support for warnings).
A nil Diagnostics is a valid, empty diagnostics list, thus allowing heap allocation to be avoided in the common case where there are no diagnostics to report at all.
func OverrideAll ¶
func OverrideAll(originals Diagnostics, severity Severity, createExtra func() DiagnosticExtraWrapper) Diagnostics
OverrideAll accepts a set of Diagnostics and wraps them with a new severity and, optionally, a new ExtraInfo.
func (Diagnostics) Append ¶
func (diags Diagnostics) Append(new ...interface{}) Diagnostics
Append is the main interface for constructing Diagnostics lists, taking an existing list (which may be nil) and appending the new objects to it after normalizing them to be implementations of Diagnostic.
The usual pattern for a function that natively "speaks" diagnostics is:
// Create a nil Diagnostics at the start of the function var diags diag.Diagnostics // At later points, build on it if errors / warnings occur: foo, err := DoSomethingRisky() if err != nil { diags = diags.Append(err) } // Eventually return the result and diagnostics in place of error return result, diags
Append accepts a variety of different diagnostic-like types, including native Go errors and HCL diagnostics. It also knows how to unwrap a multierror.Error into separate error diagnostics. It can be passed another Diagnostics to concatenate the two lists. If given something it cannot handle, this function will panic.
func (Diagnostics) ConsolidateWarnings ¶
func (diags Diagnostics) ConsolidateWarnings(threshold int) Diagnostics
ConsolidateWarnings checks if there is an unreasonable amount of warnings with the same summary in the receiver and, if so, returns a new diagnostics with some of those warnings consolidated into a single warning in order to reduce the verbosity of the output.
This mechanism is here primarily for diagnostics printed out at the CLI. In other contexts it is likely better to just return the warnings directly, particularly if they are going to be interpreted by software rather than by a human reader.
The returned slice always has a separate backing array from the reciever, but some diagnostic values themselves might be shared.
The definition of "unreasonable" is given as the threshold argument. At most that many warnings with the same summary will be shown.
func (Diagnostics) Err ¶
func (diags Diagnostics) Err() error
Err flattens a diagnostics list into a single Go error, or to nil if the diagnostics list does not include any error-level diagnostics.
This can be used to smuggle diagnostics through an API that deals in native errors, but unfortunately it will lose any warnings that aren't accompanied by at least one error since such APIs have no mechanism through which to report those.
return result, diags.Error()
func (Diagnostics) ErrWithWarnings ¶
func (diags Diagnostics) ErrWithWarnings() error
ErrWithWarnings is similar to Err except that it will also return a non-nil error if the receiver contains only warnings.
In the warnings-only situation, the result is guaranteed to be of dynamic type NonFatalError, allowing diagnostics-aware callers to type-assert and unwrap it, treating it as non-fatal.
This should be used only in contexts where the caller is able to recognize and handle NonFatalError. For normal callers that expect a lack of errors to be signaled by nil, use just Diagnostics.Err.
func (Diagnostics) ForRPC ¶
func (diags Diagnostics) ForRPC() Diagnostics
ForRPC returns a version of the receiver that has been simplified so that it is friendly to RPC protocols.
Currently this means that it can be serialized with encoding/gob and subsequently re-inflated. It may later grow to include other serialization formats.
Note that this loses information about the original objects used to construct the diagnostics, so e.g. the errwrap API will not work as expected on an error-wrapped Diagnostics that came from ForRPC.
func (Diagnostics) HasErrors ¶
func (diags Diagnostics) HasErrors() bool
HasErrors returns true if any of the diagnostics in the list have a severity of Error.
func (Diagnostics) InConfigBody ¶
func (diags Diagnostics) InConfigBody(body hcl.Body, addr string) Diagnostics
InConfigBody returns a copy of the receiver with any config-contextual diagnostics elaborated in the context of the given body. An optional address argument may be added to indicate which instance of the configuration the error related to.
func (Diagnostics) NonFatalErr ¶
func (diags Diagnostics) NonFatalErr() error
NonFatalErr is similar to Err except that it always returns either nil (if there are no diagnostics at all) or NonFatalError.
This allows diagnostics to be returned over an error return channel while being explicit that the diagnostics should not halt processing.
This should be used only in contexts where the caller is able to recognize and handle NonFatalError. For normal callers that expect a lack of errors to be signaled by nil, use just Diagnostics.Err.
func (Diagnostics) Sort ¶
func (diags Diagnostics) Sort()
Sort applies an ordering to the diagnostics in the receiver in-place.
The ordering is: warnings before errors, sourceless before sourced, short source paths before long source paths, and then ordering by position within each file.
Diagnostics that do not differ by any of these sortable characteristics will remain in the same relative order after this method returns.
func (Diagnostics) ToHCL ¶
func (diags Diagnostics) ToHCL() hcl.Diagnostics
ToHCL constructs a hcl.Diagnostics containing the same diagnostic messages as the receiving tfdiags.Diagnostics.
This conversion preserves the data that HCL diagnostics are able to preserve but would be lossy in a round trip from tfdiags to HCL and then back to tfdiags, because it will lose the specific type information of the source diagnostics. In most cases this will not be a significant problem, but could produce an awkward result in some special cases such as converting the result of ConsolidateWarnings, which will force the resulting warning groups to be flattened early.
type NonFatalError ¶
type NonFatalError struct {
Diagnostics
}
NonFatalError is a special error type, returned by Diagnostics.ErrWithWarnings and Diagnostics.NonFatalErr, that indicates that the wrapped diagnostics should be treated as non-fatal. Callers can conditionally type-assert an error to this type in order to detect the non-fatal scenario and handle it in a different way.
func (NonFatalError) Error ¶
func (woe NonFatalError) Error() string
type Source ¶
type Source struct { Subject *SourceRange Context *SourceRange }
func WarningGroupSourceRanges ¶
func WarningGroupSourceRanges(diag Diagnostic) []Source
WarningGroupSourceRanges can be used in conjunction with Diagnostics.ConsolidateWarnings to recover the full set of original source locations from a consolidated warning.
For convenience, this function accepts any diagnostic and will just return the single Source value from any diagnostic that isn't a warning group.
type SourceRange ¶
func SourceRangeFromHCL ¶
func SourceRangeFromHCL(hclRange hcl.Range) SourceRange
SourceRangeFromHCL constructs a SourceRange from the corresponding range type within the HCL package.
func (SourceRange) StartString ¶
func (r SourceRange) StartString() string
StartString returns a string representation of the start of the range, including the filename and the line and column numbers.
func (SourceRange) ToHCL ¶
func (r SourceRange) ToHCL() hcl.Range
ToHCL constructs a HCL Range from the receiving SourceRange. This is the opposite of SourceRangeFromHCL.