github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/reporter/reporter.go (about)

     1  // Package reporter contains the types used for reporting errors from
     2  // protocompile operations. It contains error types as well as interfaces
     3  // for reporting and handling errors.
     4  package reporter
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/jhump/protocompile/ast"
    10  )
    11  
    12  // ErrorReporter is responsible for reporting the given error. If the reporter
    13  // returns a non-nil error, parsing/linking will abort with that error. If the
    14  // reporter returns nil, parsing will continue, allowing the parser to try to
    15  // report as many syntax and/or link errors as it can find.
    16  type ErrorReporter func(err ErrorWithPos) error
    17  
    18  // WarningReporter is responsible for reporting the given warning. This is used
    19  // for indicating non-error messages to the calling program for things that do
    20  // not cause the parse to fail but are considered bad practice. Though they are
    21  // just warnings, the details are supplied to the reporter via an error type.
    22  type WarningReporter func(ErrorWithPos)
    23  
    24  // Reporter is a type that handles reporting both errors and warnings.
    25  type Reporter interface {
    26  	// Error is called when the given error is encountered and needs to be
    27  	// reported to the calling program. This signature matches ErrorReporter
    28  	// because it has the same semantics. If this function returns non-nil
    29  	// then the operation will abort immediately with the given error. But
    30  	// if it returns nil, the operation will continue, reporting more errors
    31  	// as they are encountered. If the reporter never returns non-nil then
    32  	// the operation will eventually fail with ErrInvalidSource.
    33  	Error(ErrorWithPos) error
    34  	// Warning is called when the given warnings is encountered and needs to be
    35  	// reported to the calling program. Despite the argument being an error
    36  	// type, a warning will never cause the operation to abort or fail (unless
    37  	// the reporter's implementation of this method panics).
    38  	Warning(ErrorWithPos)
    39  }
    40  
    41  // NewReporter creates a new reporter that invokes the given functions on error
    42  // or warning.
    43  func NewReporter(errs ErrorReporter, warnings WarningReporter) Reporter {
    44  	return reporterFuncs{errs: errs, warnings: warnings}
    45  }
    46  
    47  type reporterFuncs struct {
    48  	errs     ErrorReporter
    49  	warnings WarningReporter
    50  }
    51  
    52  func (r reporterFuncs) Error(err ErrorWithPos) error {
    53  	if r.errs == nil {
    54  		return err
    55  	}
    56  	return r.errs(err)
    57  }
    58  
    59  func (r reporterFuncs) Warning(err ErrorWithPos) {
    60  	if r.warnings != nil {
    61  		r.warnings(err)
    62  	}
    63  }
    64  
    65  // Handler is used by protocompile operations for handling errors and warnings.
    66  // This type is thread-safe. It uses a mutex to serialize calls to its reporter
    67  // so that reporter instances do not have to be thread-safe (unless re-used
    68  // across multiple handlers).
    69  type Handler struct {
    70  	parent       *Handler
    71  	mu           sync.Mutex
    72  	reporter     Reporter
    73  	errsReported bool
    74  	err          error
    75  }
    76  
    77  // NewHandler creates a new Handler that reports errors and warnings using the
    78  // given reporter.
    79  func NewHandler(rep Reporter) *Handler {
    80  	if rep == nil {
    81  		rep = NewReporter(nil, nil)
    82  	}
    83  	return &Handler{reporter: rep}
    84  }
    85  
    86  // SubHandler returns a "child" of h. Use of a child handler is the same as use
    87  // of the parent, except that the Err() and ReporterError() functions only
    88  // report non-nil for errors that were reported using the child handler. So
    89  // errors reported directly to the parent or to a different child handler won't
    90  // be returned. This is useful for making concurrent access to the handler more
    91  // deterministic: if a child handler is only used from one goroutine, its view
    92  // of reported errors is consistent and unimpacted by concurrent operations.
    93  func (h *Handler) SubHandler() *Handler {
    94  	return &Handler{parent: h}
    95  }
    96  
    97  // HandleErrorf handles an error with the given source position, creating the
    98  // error using the given message format and arguments.
    99  //
   100  // If the handler has already aborted (by returning a non-nil error from a call
   101  // to HandleError or HandleErrorf), that same error is returned and the given
   102  // error is not reported.
   103  func (h *Handler) HandleErrorf(pos ast.SourcePos, format string, args ...interface{}) error {
   104  	return h.HandleError(Errorf(pos, format, args...))
   105  }
   106  
   107  // HandleError handles the given error. If the given err is an ErrorWithPos, it
   108  // is reported, and this function returns the error returned by the reporter. If
   109  // the given err is NOT an ErrorWithPos, the current operation will abort
   110  // immediately.
   111  //
   112  // If the handler has already aborted (by returning a non-nil error from a prior
   113  // call to HandleError or HandleErrorf), that same error is returned and the
   114  // given error is not reported.
   115  func (h *Handler) HandleError(err error) error {
   116  	if h.parent != nil {
   117  		_, isErrWithPos := err.(ErrorWithPos)
   118  		err = h.parent.HandleError(err)
   119  
   120  		// update child state
   121  		h.mu.Lock()
   122  		defer h.mu.Unlock()
   123  		if isErrWithPos {
   124  			h.errsReported = true
   125  		}
   126  		h.err = err
   127  		return err
   128  	}
   129  
   130  	h.mu.Lock()
   131  	defer h.mu.Unlock()
   132  
   133  	if h.err != nil {
   134  		return h.err
   135  	}
   136  	if ewp, ok := err.(ErrorWithPos); ok {
   137  		h.errsReported = true
   138  		err = h.reporter.Error(ewp)
   139  	}
   140  	h.err = err
   141  	return err
   142  }
   143  
   144  // HandleWarning handles a warning with the given source position. This will
   145  // delegate to the handler's configured reporter.
   146  func (h *Handler) HandleWarning(pos ast.SourcePos, err error) {
   147  	if h.parent != nil {
   148  		h.parent.HandleWarning(pos, err)
   149  		return
   150  	}
   151  
   152  	// even though we aren't touching mutable fields, we acquire lock anyway so
   153  	// that underlying reporter does not have to be thread-safe
   154  	h.mu.Lock()
   155  	defer h.mu.Unlock()
   156  
   157  	h.reporter.Warning(errorWithSourcePos{pos: pos, underlying: err})
   158  }
   159  
   160  // Error returns the handler result. If any errors have been reported then this
   161  // returns a non-nil error. If the reporter never returned a non-nil error then
   162  // ErrInvalidSource is returned. Otherwise, this returns the error returned by
   163  // the  handler's reporter (the same value returned by ReporterError).
   164  func (h *Handler) Error() error {
   165  	h.mu.Lock()
   166  	defer h.mu.Unlock()
   167  
   168  	if h.errsReported && h.err == nil {
   169  		return ErrInvalidSource
   170  	}
   171  	return h.err
   172  }
   173  
   174  // ReporterError returns the error returned by the handler's reporter. If
   175  // the reporter has either not been invoked (no errors handled) or has not
   176  // returned any non-nil value, then this returns nil.
   177  func (h *Handler) ReporterError() error {
   178  	h.mu.Lock()
   179  	defer h.mu.Unlock()
   180  
   181  	return h.err
   182  }