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  }