1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package errors
21
22 import (
23 "cmp"
24 "errors"
25 "fmt"
26 "io"
27 "path/filepath"
28 "slices"
29 "sort"
30 "strings"
31
32 "cuelang.org/go/cue/token"
33 )
34
35
36
37 func New(msg string) error {
38 return errors.New(msg)
39 }
40
41
42
43 func Unwrap(err error) error {
44 return errors.Unwrap(err)
45 }
46
47
48
49
50
51 func Is(err, target error) bool {
52 return errors.Is(err, target)
53 }
54
55
56
57
58
59
60
61
62
63
64 func As(err error, target interface{}) bool {
65 return errors.As(err, target)
66 }
67
68
69
70
71 type Message struct {
72 format string
73 args []interface{}
74 }
75
76
77
78
79 func NewMessagef(format string, args ...interface{}) Message {
80 if false {
81
82 _ = fmt.Sprintf(format, args...)
83 }
84 return Message{format: format, args: args}
85 }
86
87
88
89 func NewMessage(format string, args []interface{}) Message {
90 return NewMessagef(format, args...)
91 }
92
93
94
95 func (m *Message) Msg() (format string, args []interface{}) {
96 return m.format, m.args
97 }
98
99 func (m *Message) Error() string {
100 return fmt.Sprintf(m.format, m.args...)
101 }
102
103
104 type Error interface {
105
106
107 Position() token.Pos
108
109
110
111
112 InputPositions() []token.Pos
113
114
115 Error() string
116
117
118
119 Path() []string
120
121
122
123 Msg() (format string, args []interface{})
124 }
125
126
127
128 func Positions(err error) []token.Pos {
129 e := Error(nil)
130 if !errors.As(err, &e) {
131 return nil
132 }
133
134 a := make([]token.Pos, 0, 3)
135
136 pos := e.Position()
137 if pos.IsValid() {
138 a = append(a, pos)
139 }
140 sortOffset := len(a)
141
142 for _, p := range e.InputPositions() {
143 if p.IsValid() && p != pos {
144 a = append(a, p)
145 }
146 }
147
148 slices.SortFunc(a[sortOffset:], comparePos)
149 return slices.Compact(a)
150 }
151
152
153 func Path(err error) []string {
154 if e := Error(nil); errors.As(err, &e) {
155 return e.Path()
156 }
157 return nil
158 }
159
160
161 func Newf(p token.Pos, format string, args ...interface{}) Error {
162 return &posError{
163 pos: p,
164 Message: NewMessagef(format, args...),
165 }
166 }
167
168
169
170 func Wrapf(err error, p token.Pos, format string, args ...interface{}) Error {
171 pErr := &posError{
172 pos: p,
173 Message: NewMessagef(format, args...),
174 }
175 return Wrap(pErr, err)
176 }
177
178
179
180
181 func Wrap(parent Error, child error) Error {
182 if child == nil {
183 return parent
184 }
185 a, ok := child.(list)
186 if !ok {
187 return &wrapped{parent, child}
188 }
189 b := make(list, len(a))
190 for i, err := range a {
191 b[i] = &wrapped{parent, err}
192 }
193 return b
194 }
195
196 type wrapped struct {
197 main Error
198 wrap error
199 }
200
201
202 func (e *wrapped) Error() string {
203 switch msg := e.main.Error(); {
204 case e.wrap == nil:
205 return msg
206 case msg == "":
207 return e.wrap.Error()
208 default:
209 return fmt.Sprintf("%s: %s", msg, e.wrap)
210 }
211 }
212
213 func (e *wrapped) Is(target error) bool {
214 return Is(e.main, target)
215 }
216
217 func (e *wrapped) As(target interface{}) bool {
218 return As(e.main, target)
219 }
220
221 func (e *wrapped) Msg() (format string, args []interface{}) {
222 return e.main.Msg()
223 }
224
225 func (e *wrapped) Path() []string {
226 if p := Path(e.main); p != nil {
227 return p
228 }
229 return Path(e.wrap)
230 }
231
232 func (e *wrapped) InputPositions() []token.Pos {
233 return append(e.main.InputPositions(), Positions(e.wrap)...)
234 }
235
236 func (e *wrapped) Position() token.Pos {
237 if p := e.main.Position(); p != token.NoPos {
238 return p
239 }
240 if wrap, ok := e.wrap.(Error); ok {
241 return wrap.Position()
242 }
243 return token.NoPos
244 }
245
246 func (e *wrapped) Unwrap() error { return e.wrap }
247
248 func (e *wrapped) Cause() error { return e.wrap }
249
250
251 func Promote(err error, msg string) Error {
252 switch x := err.(type) {
253 case Error:
254 return x
255 default:
256 return Wrapf(err, token.NoPos, "%s", msg)
257 }
258 }
259
260 var _ Error = &posError{}
261
262
263
264
265
266 type posError struct {
267 pos token.Pos
268 Message
269 }
270
271 func (e *posError) Path() []string { return nil }
272 func (e *posError) InputPositions() []token.Pos { return nil }
273 func (e *posError) Position() token.Pos { return e.pos }
274
275
276 func Append(a, b Error) Error {
277 switch x := a.(type) {
278 case nil:
279 return b
280 case list:
281 return appendToList(x, b)
282 }
283
284 list := appendToList(nil, a)
285 list = appendToList(list, b)
286 return list
287 }
288
289
290
291
292
293 func Errors(err error) []Error {
294 if err == nil {
295 return nil
296 }
297 var listErr list
298 var errorErr Error
299 switch {
300 case As(err, &listErr):
301 return listErr
302 case As(err, &errorErr):
303 return []Error{errorErr}
304 default:
305 return []Error{Promote(err, "")}
306 }
307 }
308
309 func appendToList(a list, err Error) list {
310 switch x := err.(type) {
311 case nil:
312 return a
313 case list:
314 if a == nil {
315 return x
316 }
317 return append(a, x...)
318 default:
319 return append(a, err)
320 }
321 }
322
323
324
325 type list []Error
326
327 func (p list) Is(target error) bool {
328 for _, e := range p {
329 if errors.Is(e, target) {
330 return true
331 }
332 }
333 return false
334 }
335
336 func (p list) As(target interface{}) bool {
337 for _, e := range p {
338 if errors.As(e, target) {
339 return true
340 }
341 }
342 return false
343 }
344
345
346 func (p *list) AddNewf(pos token.Pos, msg string, args ...interface{}) {
347 err := &posError{pos: pos, Message: Message{format: msg, args: args}}
348 *p = append(*p, err)
349 }
350
351
352 func (p *list) Add(err Error) {
353 *p = appendToList(*p, err)
354 }
355
356
357 func (p *list) Reset() { *p = (*p)[:0] }
358
359
360 func (p list) Len() int { return len(p) }
361 func (p list) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
362
363 func (p list) Less(i, j int) bool {
364 if c := comparePos(p[i].Position(), p[j].Position()); c != 0 {
365 return c == -1
366 }
367
368
369
370
371 if !equalPath(p[i].Path(), p[j].Path()) {
372 return lessPath(p[i].Path(), p[j].Path())
373 }
374 return p[i].Error() < p[j].Error()
375 }
376
377 func comparePos(a, b token.Pos) int {
378 if a.Filename() != b.Filename() {
379 return cmp.Compare(a.Filename(), b.Filename())
380 }
381 if a.Line() != b.Line() {
382 return cmp.Compare(a.Line(), b.Line())
383 }
384 if a.Column() != b.Column() {
385 return cmp.Compare(a.Column(), b.Column())
386 }
387 return 0
388 }
389
390 func lessPath(a, b []string) bool {
391 for i, x := range a {
392 if i >= len(b) {
393 return false
394 }
395 if x != b[i] {
396 return x < b[i]
397 }
398 }
399 return len(a) < len(b)
400 }
401
402 func equalPath(a, b []string) bool {
403 if len(a) != len(b) {
404 return false
405 }
406 for i, x := range a {
407 if x != b[i] {
408 return false
409 }
410 }
411 return true
412 }
413
414
415
416 func Sanitize(err Error) Error {
417 if err == nil {
418 return nil
419 }
420 if l, ok := err.(list); ok {
421 a := l.sanitize()
422 if len(a) == 1 {
423 return a[0]
424 }
425 return a
426 }
427 return err
428 }
429
430 func (p list) sanitize() list {
431 if p == nil {
432 return p
433 }
434 a := make(list, len(p))
435 copy(a, p)
436 a.RemoveMultiples()
437 return a
438 }
439
440
441
442
443 func (p list) Sort() {
444 sort.Sort(p)
445 }
446
447
448 func (p *list) RemoveMultiples() {
449 p.Sort()
450 var last Error
451 i := 0
452 for _, e := range *p {
453 if last == nil || !approximateEqual(last, e) {
454 last = e
455 (*p)[i] = e
456 i++
457 }
458 }
459 (*p) = (*p)[0:i]
460 }
461
462 func approximateEqual(a, b Error) bool {
463 aPos := a.Position()
464 bPos := b.Position()
465 if aPos == token.NoPos || bPos == token.NoPos {
466 return a.Error() == b.Error()
467 }
468 return aPos.Filename() == bPos.Filename() &&
469 aPos.Line() == bPos.Line() &&
470 aPos.Column() == bPos.Column() &&
471 equalPath(a.Path(), b.Path())
472 }
473
474
475 func (p list) Error() string {
476 format, args := p.Msg()
477 return fmt.Sprintf(format, args...)
478 }
479
480
481 func (p list) Msg() (format string, args []interface{}) {
482 switch len(p) {
483 case 0:
484 return "no errors", nil
485 case 1:
486 return p[0].Msg()
487 }
488 return "%s (and %d more errors)", []interface{}{p[0], len(p) - 1}
489 }
490
491
492 func (p list) Position() token.Pos {
493 if len(p) == 0 {
494 return token.NoPos
495 }
496 return p[0].Position()
497 }
498
499
500 func (p list) InputPositions() []token.Pos {
501 if len(p) == 0 {
502 return nil
503 }
504 return p[0].InputPositions()
505 }
506
507
508 func (p list) Path() []string {
509 if len(p) == 0 {
510 return nil
511 }
512 return p[0].Path()
513 }
514
515
516
517 func (p list) Err() error {
518 if len(p) == 0 {
519 return nil
520 }
521 return p
522 }
523
524
525 type Config struct {
526
527
528 Format func(w io.Writer, format string, args ...interface{})
529
530
531
532 Cwd string
533
534
535 ToSlash bool
536 }
537
538
539
540
541 func Print(w io.Writer, err error, cfg *Config) {
542 if cfg == nil {
543 cfg = &Config{}
544 }
545 for _, e := range list(Errors(err)).sanitize() {
546 printError(w, e, cfg)
547 }
548 }
549
550
551
552 func Details(err error, cfg *Config) string {
553 var b strings.Builder
554 Print(&b, err, cfg)
555 return b.String()
556 }
557
558
559 func String(err Error) string {
560 var b strings.Builder
561 writeErr(&b, err)
562 return b.String()
563 }
564
565 func writeErr(w io.Writer, err Error) {
566 if path := strings.Join(err.Path(), "."); path != "" {
567 _, _ = io.WriteString(w, path)
568 _, _ = io.WriteString(w, ": ")
569 }
570
571 for {
572 u := errors.Unwrap(err)
573
574 printed := false
575 msg, args := err.Msg()
576 s := fmt.Sprintf(msg, args...)
577 if s != "" || u == nil {
578 _, _ = io.WriteString(w, s)
579 printed = true
580 }
581
582 if u == nil {
583 break
584 }
585
586 if printed {
587 _, _ = io.WriteString(w, ": ")
588 }
589 err, _ = u.(Error)
590 if err == nil {
591 fmt.Fprint(w, u)
592 break
593 }
594 }
595 }
596
597 func defaultFprintf(w io.Writer, format string, args ...interface{}) {
598 fmt.Fprintf(w, format, args...)
599 }
600
601 func printError(w io.Writer, err error, cfg *Config) {
602 if err == nil {
603 return
604 }
605 fprintf := cfg.Format
606 if fprintf == nil {
607 fprintf = defaultFprintf
608 }
609
610 positions := []string{}
611 for _, p := range Positions(err) {
612 pos := p.Position()
613 s := pos.Filename
614 if cfg.Cwd != "" {
615 if p, err := filepath.Rel(cfg.Cwd, s); err == nil {
616 s = p
617
618
619
620 if !strings.HasPrefix(s, ".") {
621 s = fmt.Sprintf(".%s%s", string(filepath.Separator), s)
622 }
623 }
624 }
625 if cfg.ToSlash {
626 s = filepath.ToSlash(s)
627 }
628 if pos.IsValid() {
629 if s != "" {
630 s += ":"
631 }
632 s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
633 }
634 if s == "" {
635 s = "-"
636 }
637 positions = append(positions, s)
638 }
639
640 if e, ok := err.(Error); ok {
641 writeErr(w, e)
642 } else {
643 fprintf(w, "%v", err)
644 }
645
646 if len(positions) == 0 {
647 fprintf(w, "\n")
648 return
649 }
650
651 fprintf(w, ":\n")
652 for _, pos := range positions {
653 fprintf(w, " %s\n", pos)
654 }
655 }
656
View as plain text