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 }