-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy patherrorhandler.go
More file actions
130 lines (113 loc) · 4.27 KB
/
Copy patherrorhandler.go
File metadata and controls
130 lines (113 loc) · 4.27 KB
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
package helium
import (
"context"
"errors"
"sync"
"github.com/lestrrat-go/helium/sink"
)
// ErrorLeveler is an optional interface that errors can implement to
// report their severity. Errors that do not implement this interface
// are treated as warnings (ErrorLevelWarning).
type ErrorLeveler interface {
ErrorLevel() ErrorLevel
}
// ErrorHandler receives errors reported during parsing, compilation,
// or validation. Implementations may log, accumulate, or discard errors.
//
// Which errors reach a handler depends on the component it is set on: the root
// [Parser] consults it only for DTD validation; the xinclude Processor delivers
// non-fatal XInclude warnings during Process/ProcessTree; the xsd, relaxng, and
// schematron compilers and validators deliver their compilation and validation
// diagnostics; and the catalog Loader delivers its catalog-loading diagnostics.
// xslt3 has no ErrorHandler of its own — it drives the xsd compiler's handler
// internally. In every case the handler is retained by reference and shared
// across each operation run on the configured value, which is an immutable-value
// builder; setting a nil handler is allowed and is treated as [NilErrorHandler]
// (discard) at use time — never a panic.
//
// Close ownership differs by component: the [Parser] (after each DTD-validating
// parse) and the xsd, relaxng, and schematron compilers and validators (after
// each Compile/Validate) close a handler that implements io.Closer, so such a
// handler must not be shared across those operations; the catalog Loader and the
// xinclude Processor never close the handler — the caller owns its lifecycle.
//
// Handle is called synchronously at the point of error detection unless
// the implementation itself introduces asynchrony (e.g. Sink[error]).
//
// Implementations must not block for extended periods.
//
// The error value may optionally implement ErrorLeveler to indicate
// severity. Users can type-assert to inspect the level.
type ErrorHandler interface {
Handle(context.Context, error)
}
// NilErrorHandler is an ErrorHandler that discards all errors.
// Use as a default when no handler is provided.
type NilErrorHandler struct{}
func (NilErrorHandler) Handle(context.Context, error) {}
type leveledError struct {
msg string
level ErrorLevel
}
func (e *leveledError) Error() string { return e.msg }
func (e *leveledError) ErrorLevel() ErrorLevel { return e.level }
// NewLeveledError creates an error that implements ErrorLeveler.
func NewLeveledError(msg string, level ErrorLevel) error {
return &leveledError{msg: msg, level: level}
}
type errorAccumulator struct {
level ErrorLevel
mu sync.Mutex
errors []error
}
func (a *errorAccumulator) Handle(_ context.Context, err error) {
if a.level != 0 {
level := ErrorLevelWarning
var l ErrorLeveler
if errors.As(err, &l) {
level = l.ErrorLevel()
}
if level != a.level {
return
}
}
a.mu.Lock()
a.errors = append(a.errors, err)
a.mu.Unlock()
}
func (a *errorAccumulator) collectErrors() []error {
a.mu.Lock()
defer a.mu.Unlock()
return append([]error(nil), a.errors...)
}
// ErrorCollector collects errors into a slice via an internal Sink[error].
// When level is zero (ErrorLevelNone), all errors are collected. When set,
// only errors matching that level are collected.
//
// Satisfies ErrorHandler and io.Closer. The parser/compiler closes it
// automatically at the end of the operation.
type ErrorCollector struct {
acc *errorAccumulator
s *sink.Sink[error]
}
// NewErrorCollector creates an ErrorCollector backed by a Sink[error].
// Pass ErrorLevelNone (0) for level to collect all errors regardless of severity.
func NewErrorCollector(ctx context.Context, level ErrorLevel, opts ...sink.Option) *ErrorCollector {
acc := &errorAccumulator{level: level}
return &ErrorCollector{
acc: acc,
s: sink.New[error](ctx, acc, opts...),
}
}
// Handle satisfies ErrorHandler. Sends the error to the internal Sink.
func (ec *ErrorCollector) Handle(ctx context.Context, err error) {
ec.s.Handle(ctx, err)
}
// Close satisfies io.Closer. Drains the internal Sink.
func (ec *ErrorCollector) Close() error {
return ec.s.Close()
}
// Errors returns a copy of the collected errors. Safe to call after Close.
func (ec *ErrorCollector) Errors() []error {
return ec.acc.collectErrors()
}