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  }