-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
289 lines (253 loc) · 9.06 KB
/
Copy patherrors.go
File metadata and controls
289 lines (253 loc) · 9.06 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
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package jsonc
import (
"bytes"
"errors"
"fmt"
"strings"
)
// Position identifies a location within JSONC source text.
type Position struct {
// Line is the 1-indexed line number; 0 means "no position".
Line int
// Column is the 1-indexed column number on Line.
Column int
// Offset is the 0-indexed byte offset from the start of the input.
Offset int
}
// String returns the position formatted as "line:column".
func (p Position) String() string {
return fmt.Sprintf("%d:%d", p.Line, p.Column)
}
// SyntaxError is returned when the JSONC input is malformed.
// Use [errors.Is](err, [ErrSyntax]) to test for syntax errors generically.
type SyntaxError struct {
// Message is a short human-readable description of the syntactic problem.
Message string
// Pos is the source position where the error was detected; Line == 0
// when the error has no specific location.
Pos Position
// Token is the offending token text, when available.
Token string
}
func (e *SyntaxError) Error() string {
if e.Pos.Line > 0 {
return fmt.Sprintf("jsonc: line %d, column %d: %s", e.Pos.Line, e.Pos.Column, e.Message)
}
return fmt.Sprintf("jsonc: %s", e.Message)
}
func (e *SyntaxError) Is(target error) bool {
_, ok := target.(*SyntaxError)
return ok
}
// TypeError is returned when one or more JSONC values cannot be assigned to
// the target Go types. The decoder accumulates conversion failures rather
// than failing fast and returns them all at once via this type.
type TypeError struct {
// Errors holds one message per failed value-to-target conversion, each
// already prefixed with "line N: " when a position is known.
Errors []string
}
func (e *TypeError) Error() string {
return fmt.Sprintf("jsonc: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
}
func (e *TypeError) Is(target error) bool {
_, ok := target.(*TypeError)
return ok
}
// UnknownFieldError is returned when decoding with [WithStrict] and a JSONC
// key has no corresponding struct field.
type UnknownFieldError struct {
// Field is the JSONC key that did not match any struct field.
Field string
// Pos is the source position of the unrecognized key.
Pos Position
}
func (e *UnknownFieldError) Error() string {
return fmt.Sprintf("jsonc: line %d: unknown field %q", e.Pos.Line, e.Field)
}
func (e *UnknownFieldError) Is(target error) bool {
_, ok := target.(*UnknownFieldError)
return ok
}
// DuplicateKeyError is returned when an object key is defined more than once.
// By default duplicates are rejected; pass [WithAllowDuplicateKeys] to opt
// into stdlib-style last-wins behavior.
type DuplicateKeyError struct {
// Key is the duplicated object member name.
Key string
// Pos is the source position of the second (offending) occurrence.
Pos Position
}
func (e *DuplicateKeyError) Error() string {
return fmt.Sprintf("jsonc: line %d: duplicate key %q", e.Pos.Line, e.Key)
}
func (e *DuplicateKeyError) Is(target error) bool {
_, ok := target.(*DuplicateKeyError)
return ok
}
// ValidationError wraps an error returned by a [StructValidator] with the
// [Position] of the JSONC node that was decoded into the struct. Err is
// available via [errors.Unwrap] for callers that want the underlying value.
type ValidationError struct {
// Err is the error returned by the validator.
Err error
// Pos is the source position of the JSONC object that produced the
// validated struct value.
Pos Position
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("jsonc: line %d: validation: %s", e.Pos.Line, e.Err.Error())
}
func (e *ValidationError) Unwrap() error {
return e.Err
}
func (e *ValidationError) Is(target error) bool {
_, ok := target.(*ValidationError)
return ok
}
// DefaultError is returned when a default value from a struct tag cannot be
// applied (parse failure, overflow, or unsupported field type).
type DefaultError struct {
// Field is the struct field name (or jsonc-tag name, when set).
Field string
// Message describes why the default could not be applied.
Message string
// Pos is the source position of the surrounding object, if known.
Pos Position
}
func (e *DefaultError) Error() string {
return fmt.Sprintf("jsonc: field %q: %s", e.Field, e.Message)
}
func (e *DefaultError) Is(target error) bool {
_, ok := target.(*DefaultError)
return ok
}
// OverflowError is returned when a JSONC number overflows the target Go type.
type OverflowError struct {
// Value is the offending number's source text.
Value string
// Type is the Go target type that could not hold Value.
Type string
// Pos is the source position of the number.
Pos Position
}
func (e *OverflowError) Error() string {
return fmt.Sprintf("jsonc: line %d: value %s overflows %s", e.Pos.Line, e.Value, e.Type)
}
func (e *OverflowError) Is(target error) bool {
_, ok := target.(*OverflowError)
return ok
}
// StrictJSONError is returned when the input contains a JSONC extension
// (a comment or a trailing comma) while [WithStrictJSON] is enabled.
type StrictJSONError struct {
// Feature names the rejected extension, e.g. "// line comment" or
// "trailing comma".
Feature string
// Pos is the source position where the extension appeared.
Pos Position
}
func (e *StrictJSONError) Error() string {
return fmt.Sprintf("jsonc: line %d: strict JSON mode rejects %s", e.Pos.Line, e.Feature)
}
func (e *StrictJSONError) Is(target error) bool {
_, ok := target.(*StrictJSONError)
return ok
}
// Sentinel errors for use with [errors.Is].
var (
// ErrSyntax indicates malformed JSONC input.
ErrSyntax = &SyntaxError{}
// ErrType indicates one or more JSONC values could not be assigned to the target Go types.
ErrType = &TypeError{}
// ErrUnknownField indicates a JSONC key has no corresponding struct field (with [WithStrict]).
ErrUnknownField = &UnknownFieldError{}
// ErrDuplicateKey indicates an object key was defined more than once.
ErrDuplicateKey = &DuplicateKeyError{}
// ErrValidation indicates a [StructValidator] rejected a decoded struct.
ErrValidation = &ValidationError{}
// ErrDefault indicates a default struct tag value could not be applied.
ErrDefault = &DefaultError{}
// ErrOverflow indicates a JSONC number overflows the target Go type.
ErrOverflow = &OverflowError{}
// ErrStrictJSON indicates a JSONC extension (comment or trailing comma)
// was encountered while [WithStrictJSON] was enabled.
ErrStrictJSON = &StrictJSONError{}
// ErrPathSyntax indicates an invalid [Path] expression.
ErrPathSyntax = errors.New("jsonc: invalid path syntax")
// ErrPathNotFound indicates no node matched a [Path] query.
ErrPathNotFound = errors.New("jsonc: path not found")
// ErrPointerSyntax indicates an invalid RFC 6901 JSON Pointer expression.
ErrPointerSyntax = errors.New("jsonc: invalid JSON Pointer syntax")
// ErrPatchSyntax indicates an invalid RFC 6902 JSON Patch document.
ErrPatchSyntax = errors.New("jsonc: invalid JSON Patch syntax")
// ErrNilPointer indicates a nil pointer was passed where a non-nil pointer is required.
ErrNilPointer = errors.New("jsonc: non-nil pointer required")
// ErrDocumentSize indicates the input exceeds the configured [WithMaxDocumentSize] limit.
ErrDocumentSize = errors.New("jsonc: document size exceeds limit")
// ErrUnsupportedValue indicates a value cannot be encoded (Inf/NaN, channels,
// functions, complex numbers, or cyclic data).
ErrUnsupportedValue = errors.New("jsonc: unsupported value")
)
// FormatError returns a human-readable string for errors that carry a
// [Position] (such as [SyntaxError], [DuplicateKeyError], [OverflowError],
// [StrictJSONError], or [ValidationError]). The output includes the offending
// source line and a column pointer. For other error types it returns
// err.Error(). Set color to true to include ANSI color escape sequences.
func FormatError(data []byte, err error, color ...bool) string {
pos := extractPosition(err)
if pos.Line == 0 {
return err.Error()
}
lines := bytes.Split(data, []byte("\n"))
lineIdx := pos.Line - 1
if lineIdx < 0 || lineIdx >= len(lines) {
return err.Error()
}
useColor := len(color) > 0 && color[0]
var buf bytes.Buffer
if useColor {
fmt.Fprintf(&buf, "\x1b[1;31m%s\x1b[0m\n", err.Error())
fmt.Fprintf(&buf, " %s\n", string(lines[lineIdx]))
fmt.Fprintf(&buf, " %s\x1b[1;31m^\x1b[0m\n", repeatByte(' ', pos.Column-1))
} else {
fmt.Fprintf(&buf, "%s\n", err.Error())
fmt.Fprintf(&buf, " %s\n", string(lines[lineIdx]))
fmt.Fprintf(&buf, " %s^\n", repeatByte(' ', pos.Column-1))
}
return buf.String()
}
func extractPosition(err error) Position {
var synErr *SyntaxError
var valErr *ValidationError
var dupErr *DuplicateKeyError
var ovfErr *OverflowError
var unkErr *UnknownFieldError
var strErr *StrictJSONError
switch {
case errors.As(err, &synErr):
return synErr.Pos
case errors.As(err, &valErr):
return valErr.Pos
case errors.As(err, &dupErr):
return dupErr.Pos
case errors.As(err, &ovfErr):
return ovfErr.Pos
case errors.As(err, &unkErr):
return unkErr.Pos
case errors.As(err, &strErr):
return strErr.Pos
default:
return Position{}
}
}
func repeatByte(b byte, n int) string {
if n <= 0 {
return ""
}
buf := make([]byte, n)
for i := range buf {
buf[i] = b
}
return string(buf)
}