package tfconfig import ( "fmt" legacyhclparser "github.com/hashicorp/hcl/hcl/parser" "github.com/hashicorp/hcl2/hcl" ) // Diagnostic describes a problem (error or warning) encountered during // configuration loading. type Diagnostic struct { Severity DiagSeverity `json:"severity"` Summary string `json:"summary"` Detail string `json:"detail,omitempty"` // Pos is not populated for all diagnostics, but when populated should // indicate a particular line that the described problem relates to. Pos *SourcePos `json:"pos,omitempty"` } // Diagnostics represents a sequence of diagnostics. This is the type that // should be returned from a function that might generate diagnostics. type Diagnostics []Diagnostic // HasErrors returns true if there is at least one Diagnostic of severity // DiagError in the receiever. // // If a function returns a Diagnostics without errors then the result can // be assumed to be complete within the "best effort" constraints of this // library. If errors are present then the caller may wish to employ more // caution in relying on the result. func (diags Diagnostics) HasErrors() bool { for _, diag := range diags { if diag.Severity == DiagError { return true } } return false } func (diags Diagnostics) Error() string { switch len(diags) { case 0: return "no problems" case 1: return fmt.Sprintf("%s: %s", diags[0].Summary, diags[0].Detail) default: return fmt.Sprintf("%s: %s (and %d other messages)", diags[0].Summary, diags[0].Detail, len(diags)-1) } } // Err returns an error representing the receiver if the receiver HasErrors, or // nil otherwise. // // The returned error can be type-asserted back to a Diagnostics if needed. func (diags Diagnostics) Err() error { if diags.HasErrors() { return diags } return nil } // DiagSeverity describes the severity of a Diagnostic. type DiagSeverity rune // DiagError indicates a problem that prevented proper processing of the // configuration. In the precense of DiagError diagnostics the result is // likely to be incomplete. const DiagError DiagSeverity = 'E' // DiagWarning indicates a problem that the user may wish to consider but // that did not prevent proper processing of the configuration. const DiagWarning DiagSeverity = 'W' // MarshalJSON is an implementation of encoding/json.Marshaler func (s DiagSeverity) MarshalJSON() ([]byte, error) { switch s { case DiagError: return []byte(`"error"`), nil case DiagWarning: return []byte(`"warning"`), nil default: return []byte(`"invalid"`), nil } } func diagnosticsHCL(diags hcl.Diagnostics) Diagnostics { if len(diags) == 0 { return nil } ret := make(Diagnostics, len(diags)) for i, diag := range diags { ret[i] = Diagnostic{ Summary: diag.Summary, Detail: diag.Detail, } switch diag.Severity { case hcl.DiagError: ret[i].Severity = DiagError case hcl.DiagWarning: ret[i].Severity = DiagWarning } if diag.Subject != nil { pos := sourcePosHCL(*diag.Subject) ret[i].Pos = &pos } } return ret } func diagnosticsError(err error) Diagnostics { if err == nil { return nil } if posErr, ok := err.(*legacyhclparser.PosError); ok { pos := sourcePosLegacyHCL(posErr.Pos, "") return Diagnostics{ Diagnostic{ Severity: DiagError, Summary: posErr.Err.Error(), Pos: &pos, }, } } return Diagnostics{ Diagnostic{ Severity: DiagError, Summary: err.Error(), }, } } func diagnosticsErrorf(format string, args ...interface{}) Diagnostics { return diagnosticsError(fmt.Errorf(format, args...)) }