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