-
Notifications
You must be signed in to change notification settings - Fork 8
/
verror.go
832 lines (775 loc) · 27.3 KB
/
verror.go
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package verror implements an error reporting mechanism that works across
// programming environments, and a set of common errors.
//
// Each error has an identifier string, which is used for equality checks.
// E.g. a Javascript client can check if a Go server returned a NoExist error by
// checking the string identifier. Error identifier strings start with the VDL
// package path to ensure uniqueness, e.g. "v.io/v23/verror.NoExist".
//
// Each error contains an action, which is the suggested action for a typical
// client to perform upon receiving the error. E.g. some action codes represent
// whether to retry the operation after receiving the error.
//
// Each error also contains a list of typed parameters, and an error message.
// The error message is created by looking up a format string keyed on the error
// identifier, and applying the parameters to the format string. This enables
// error messages to be generated in different languages.
//
// Examples
//
// To define a new error identifier, for example "someNewError", client code is
// expected to declare a variable like this:
// var someNewError = verror.Register("my/package/name.someNewError", NoRetry,
// "{1} {2} English text for new error")
// Text for other languages can be added to the default i18n Catalogue.
//
// If the error should cause a client to retry, consider replacing "NoRetry" with
// one of the other Action codes below.
//
// Errors are given parameters when used. Conventionally, the first parameter
// is the name of the component (typically server or binary name), and the
// second is the name of the operation (such as an RPC or subcommand) that
// encountered the error. Other parameters typically identify the object(s) on
// which the error occurred. This convention is normally applied by New(),
// which fetches the language, component name and operation name from the
// context.T:
// err = verror.New(someNewError, ctx, "object_on_which_error_occurred")
//
// The ExplicitNew() call can be used to specify these things explicitly:
// err = verror.ExplicitNew(someNewError, i18n.GetLangID(ctx),
// "my_component", "op_name", "procedure_name", "object_name")
// If the language, component and/or operation name are unknown, use i18n.NoLangID
// or the empty string, respectively.
//
// Because of the convention for the first two parameters, messages in the
// catalogue typically look like this (at least for left-to-right languages):
// {1} {2} The new error {_}
// The tokens {1}, {2}, etc. refer to the first and second positional parameters
// respectively, while {_} is replaced by the positional parameters not
// explicitly referred to elsewhere in the message. Thus, given the parameters
// above, this would lead to the output:
// my_component op_name The new error object_name
//
// If a substring is of the form {:<number>}, {<number>:}, {:<number>:}, {:_},
// {_:}, or {:_:}, and the corresponding parameters are not the empty string,
// the parameter is preceded by ": " or followed by ":" or both,
// respectively. For example, if the format:
// {3:} foo {2} bar{:_} ({3})
// is used with the cat.Format example above, it yields:
// 3rd: foo 2nd bar: 1st 4th (3rd)
//
// The Convert() and ExplicitConvert() calls are like New() and ExplicitNew(),
// but convert existing errors (with their parameters, if applicable)
// to verror errors with given language, component name, and operation name,
// if non-empty values for these are provided. They also add a PC to a
// list of PC values to assist developers hunting for the error.
//
// If the context.T specified with New() or Convert() is nil, a default
// context is used, set by SetDefaultContext(). This can be used in standalone
// programmes, or in anciliary threads not associated with an RPC. The user
// might do the following to get the language from the environment, and the
// programme name from Args[0]:
// ctx := runtime.NewContext()
// ctx = i18n.WithLangID(ctx, i18n.LangIDFromEnv())
// ctx = verror.WithComponentName(ctx, os.Args[0])
// verror.SetDefaultContext(ctx)
// A standalone tool might set the operation name to be a subcommand name, if
// any. If the default context has not been set, the error generated has no
// language, component and operation values; they will be filled in by the
// first Convert() call that does have these values.
package verror
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"sync"
"v.io/v23/context"
"v.io/v23/i18n"
"v.io/v23/vdl"
"v.io/v23/vtrace"
)
// ID is a unique identifier for errors.
type ID string
// An ActionCode represents the action expected to be performed by a typical client
// receiving an error that perhaps it does not understand.
type ActionCode uint32
// Codes for ActionCode.
const (
// Retry actions are encoded in the bottom few bits.
RetryActionMask ActionCode = 3
NoRetry ActionCode = 0 // Do not retry.
RetryConnection ActionCode = 1 // Renew high-level connection/context.
RetryRefetch ActionCode = 2 // Refetch and retry (e.g., out of date HTTP ETag)
RetryBackoff ActionCode = 3 // Backoff and retry a finite number of times.
)
// RetryAction returns the part of the ActionCode that indicates retry behaviour.
func (ac ActionCode) RetryAction() ActionCode {
return ac & RetryActionMask
}
// An IDAction combines a unique identifier ID for errors with an ActionCode.
// The ID allows stable error checking across different error messages and
// different address spaces. By convention the format for the identifier is
// "PKGPATH.NAME" - e.g. ErrIDFoo defined in the "v23/verror" package has id
// "v23/verror.ErrIDFoo". It is unwise ever to create two IDActions that
// associate different ActionCodes with the same ID.
type IDAction struct {
ID ID
Action ActionCode
}
// Register returns a IDAction with the given ID and Action fields, and
// inserts a message into the default i18n Catalogue in US English.
// Other languages can be added by adding to the Catalogue.
func Register(id ID, action ActionCode, englishText string) IDAction {
i18n.Cat().SetWithBase(defaultLangID(i18n.NoLangID), i18n.MsgID(id), englishText)
return IDAction{id, action}
}
// E is the in-memory representation of a verror error.
//
// The wire representation is defined as vdl.WireError; values of E
// type are automatically converted to/from vdl.WireError by VDL and VOM.
type E struct {
ID ID // The identity of the error.
Action ActionCode // Default action to take on error.
Msg string // Error message; empty if no language known.
ParamList []interface{} // The variadic parameters given to ExplicitNew().
stackPCs []uintptr // PCs of creators of E
chainedPCs []uintptr // PCs of a chained E
}
// TypeOf(verror.E{}) should give vdl.WireError.
func (E) VDLReflect(struct {
Name string `vdl:"v.io/v23/vdl.WireError"`
}) {
}
func (x E) VDLEqual(yiface interface{}) bool {
y := yiface.(E)
switch {
case x.ID != y.ID:
return false
case x.Action != y.Action:
return false
case x.Error() != y.Error():
// NOTE: We compare the result of Error() rather than comparing the Msg
// fields, since the Msg field isn't always set; Msg=="" is equivalent to
// Msg==v.io/v23/verror.Unknown.
//
// TODO(toddw): Investigate the root cause of this.
return false
case len(x.ParamList) != len(y.ParamList):
return false
}
for i := range x.ParamList {
if !vdl.DeepEqual(x.ParamList[i], y.ParamList[i]) {
return false
}
}
return true
}
var (
ttErrorElem = vdl.ErrorType.Elem()
ttWireRetryCode = ttErrorElem.Field(1).Type
ttListAny = vdl.ListType(vdl.AnyType)
)
func (x E) VDLWrite(enc vdl.Encoder) error {
if err := enc.StartValue(ttErrorElem); err != nil {
return err
}
if x.ID != "" {
if err := enc.NextFieldValueString(0, vdl.StringType, string(x.ID)); err != nil {
return err
}
}
if x.Action != NoRetry {
var actionStr string
switch x.Action {
case RetryConnection:
actionStr = "RetryConnection"
case RetryRefetch:
actionStr = "RetryRefetch"
case RetryBackoff:
actionStr = "RetryBackoff"
default:
return fmt.Errorf("action %d not in enum WireRetryCode", x.Action)
}
if err := enc.NextFieldValueString(1, ttWireRetryCode, actionStr); err != nil {
return err
}
}
if x.Msg != "" {
if err := enc.NextFieldValueString(2, vdl.StringType, x.Msg); err != nil {
return err
}
}
if len(x.ParamList) != 0 {
if err := enc.NextField(3); err != nil {
return err
}
if err := enc.StartValue(ttListAny); err != nil {
return err
}
if err := enc.SetLenHint(len(x.ParamList)); err != nil {
return err
}
for i := 0; i < len(x.ParamList); i++ {
if err := enc.NextEntry(false); err != nil {
return err
}
if x.ParamList[i] == nil {
if err := enc.NilValue(vdl.AnyType); err != nil {
return err
}
} else {
if err := vdl.Write(enc, x.ParamList[i]); err != nil {
return err
}
}
}
if err := enc.NextEntry(true); err != nil {
return err
}
if err := enc.FinishValue(); err != nil {
return err
}
}
if err := enc.NextField(-1); err != nil {
return err
}
return enc.FinishValue()
}
func (x *E) VDLRead(dec vdl.Decoder) error {
*x = E{}
if err := dec.StartValue(ttErrorElem); err != nil {
return err
}
decType := dec.Type()
for {
index, err := dec.NextField()
switch {
case err != nil:
return err
case index == -1:
return dec.FinishValue()
}
if decType != ttErrorElem {
index = ttErrorElem.FieldIndexByName(decType.Field(index).Name)
if index == -1 {
if err := dec.SkipValue(); err != nil {
return err
}
continue
}
}
errorFieldSwitch:
switch index {
case 0:
id, err := dec.ReadValueString()
if err != nil {
return err
}
x.ID = ID(id)
case 1:
code, err := dec.ReadValueString()
if err != nil {
return err
}
switch code {
case "NoRetry":
x.Action = NoRetry
case "RetryConnection":
x.Action = RetryConnection
case "RetryRefetch":
x.Action = RetryRefetch
case "RetryBackoff":
x.Action = RetryBackoff
default:
return fmt.Errorf("label %s not in enum WireRetryCode", code)
}
case 2:
msg, err := dec.ReadValueString()
if err != nil {
return err
}
x.Msg = msg
case 3:
if err := dec.StartValue(ttListAny); err != nil {
return err
}
switch len := dec.LenHint(); {
case len > 0:
x.ParamList = make([]interface{}, 0, len)
default:
x.ParamList = nil
}
for {
switch done, err := dec.NextEntry(); {
case err != nil:
return err
case done:
if err := dec.FinishValue(); err != nil {
return err
}
break errorFieldSwitch
}
var elem interface{}
if err := vdl.Read(dec, &elem); err != nil {
return err
}
x.ParamList = append(x.ParamList, elem)
}
}
}
}
const maxPCs = 40 // Maximum number of PC values we'll include in a stack trace.
// A SubErrs is a special type that allows clients to include a list of
// subordinate errors to an error's parameter list. Clients can add a SubErrs
// to the parameter list directly, via New() of include one in an existing
// error using AddSubErrs(). Each element of the slice has a name, an error,
// and an integer that encodes options such as verror.Print as bits set within
// it. By convention, clients are expected to use name of the form "X=Y" to
// distinguish their subordinate errors from those of other abstraction layers.
// For example, a layer reporting on errors in individual blessings in an RPC
// might use strings like "blessing=<blessing_name>".
type SubErrs []SubErr
type SubErrOpts uint32
// A SubErr represents a (string, error, int32) triple, It is the element type for SubErrs.
type SubErr struct {
Name string
Err error
Options SubErrOpts
}
const (
// Print, when set in SubErr.Options, tells Error() to print this SubErr.
Print SubErrOpts = 0x1
)
func assertIsE(err error) (E, bool) {
if e, ok := err.(E); ok {
return e, true
}
if e, ok := err.(*E); ok && e != nil {
return *e, true
}
return E{}, false
}
// ErrorID returns the ID of the given err, or Unknown if the err has no ID.
// If err is nil then ErrorID returns "".
func ErrorID(err error) ID {
if err == nil {
return ""
}
if e, ok := assertIsE(err); ok {
return e.ID
}
return ErrUnknown.ID
}
// Action returns the action of the given err, or NoRetry if the err has no Action.
func Action(err error) ActionCode {
if err == nil {
return NoRetry
}
if e, ok := assertIsE(err); ok {
return e.Action
}
return NoRetry
}
// PCs represents a list of PC locations
type PCs []uintptr
// Stack returns the list of PC locations on the stack when this error was
// first generated within this address space, or an empty list if err is not an
// E.
func Stack(err error) PCs {
if err != nil {
if e, ok := assertIsE(err); ok {
stackIntPtr := make([]uintptr, len(e.stackPCs))
copy(stackIntPtr, e.stackPCs)
if e.chainedPCs != nil {
stackIntPtr = append(stackIntPtr, uintptr(0))
stackIntPtr = append(stackIntPtr, e.chainedPCs...)
}
return stackIntPtr
}
}
return nil
}
func (st PCs) String() string {
buf := bytes.NewBufferString("")
StackToText(buf, st)
return buf.String()
}
// stackToTextIndent emits on w a text representation of stack, which is typically
// obtained from Stack() and represents the source location(s) where an
// error was generated or passed through in the local address space.
// indent is added to a prefix of each line printed.
func stackToTextIndent(w io.Writer, stack []uintptr, indent string) (err error) {
for i := 0; i != len(stack) && err == nil; i++ {
if stack[i] == 0 {
_, err = fmt.Fprintf(w, "%s----- chained verror -----\n", indent)
} else {
fnc := runtime.FuncForPC(stack[i])
file, line := fnc.FileLine(stack[i])
_, err = fmt.Fprintf(w, "%s%s:%d: %s\n", indent, file, line, fnc.Name())
}
}
return err
}
// StackToText emits on w a text representation of stack, which is typically
// obtained from Stack() and represents the source location(s) where an
// error was generated or passed through in the local address space.
func StackToText(w io.Writer, stack []uintptr) error {
return stackToTextIndent(w, stack, "")
}
// defaultLangID returns langID is it is not i18n.NoLangID, and the default
// value of US English otherwise.
func defaultLangID(langID i18n.LangID) i18n.LangID {
if langID == i18n.NoLangID {
langID = "en-US"
}
return langID
}
func isDefaultIDAction(id ID, action ActionCode) bool {
return id == "" && action == 0
}
// makeInternal is like ExplicitNew(), but takes a slice of PC values as an argument,
// rather than constructing one from the caller's PC.
func makeInternal(idAction IDAction, langID i18n.LangID, componentName string, opName string, stack []uintptr, v ...interface{}) E {
msg := ""
var chainedPCs []uintptr
for _, par := range v {
if err, ok := par.(error); ok {
if _, ok := assertIsE(err); ok {
chainedPCs = Stack(err)
break
}
}
}
params := append([]interface{}{componentName, opName}, v...)
if langID != i18n.NoLangID {
id := idAction.ID
if id == "" {
id = ErrUnknown.ID
}
msg = i18n.Cat().Format(langID, i18n.MsgID(id), params...)
}
return E{idAction.ID, idAction.Action, msg, params, stack, chainedPCs}
}
// ExplicitNew returns an error with the given ID, with an error string in the chosen
// language. The component and operation name are included the first and second
// parameters of the error. Other parameters are taken from v[]. The
// parameters are formatted into the message according to i18n.Cat().Format.
// The caller's PC is added to the error's stack.
// If the parameter list contains an instance of verror.E, then the stack of
// the first, and only the first, occurrence of such an instance, will be chained
// to the stack of this newly created error.
func ExplicitNew(idAction IDAction, langID i18n.LangID, componentName string, opName string, v ...interface{}) error {
stack := make([]uintptr, maxPCs)
stack = stack[:runtime.Callers(2, stack)]
return makeInternal(idAction, langID, componentName, opName, stack, v...)
}
// A componentKey is used as a key for context.T's Value() map.
type componentKey struct{}
// WithComponentName returns a context based on ctx that has the
// componentName that New() and Convert() can use.
func WithComponentName(ctx *context.T, componentName string) *context.T {
return context.WithValue(ctx, componentKey{}, componentName)
}
// New is like ExplicitNew(), but obtains the language, component name, and operation
// name from the specified context.T. ctx may be nil.
func New(idAction IDAction, ctx *context.T, v ...interface{}) error {
langID, componentName, opName := dataFromContext(ctx)
stack := make([]uintptr, maxPCs)
stack = stack[:runtime.Callers(2, stack)]
return makeInternal(idAction, langID, componentName, opName, stack, v...)
}
// isEmptyString() returns whether v is an empty string.
func isEmptyString(v interface{}) bool {
str, isAString := v.(string)
return isAString && str == ""
}
// convertInternal is like ExplicitConvert(), but takes a slice of PC values as an argument,
// rather than constructing one from the caller's PC.
func convertInternal(idAction IDAction, langID i18n.LangID, componentName string, opName string, stack []uintptr, err error) E {
// If err is already a verror.E, we wish to:
// - retain all set parameters.
// - if not yet set, set parameters 0 and 1 from componentName and
// opName.
// - if langID is set, and we have the appropriate language's format
// in our catalogue, use it. Otherwise, retain a message (assuming
// additional parameters were not set) even if the language is not
// correct.
if e, ok := assertIsE(err); !ok {
return makeInternal(idAction, langID, componentName, opName, stack, err.Error())
} else {
oldParams := e.ParamList
// Convert all embedded E errors, recursively.
for i := range oldParams {
if subErr, isE := oldParams[i].(E); isE {
oldParams[i] = convertInternal(idAction, langID, componentName, opName, stack, subErr)
} else if subErrs, isSubErrs := oldParams[i].(SubErrs); isSubErrs {
for j := range subErrs {
if subErr, isE := subErrs[j].Err.(E); isE {
subErrs[j].Err = convertInternal(idAction, langID, componentName, opName, stack, subErr)
}
}
oldParams[i] = subErrs
}
}
// Create a non-empty format string if we have the language in the catalogue.
var formatStr string
if langID != i18n.NoLangID {
id := e.ID
if id == "" {
id = ErrUnknown.ID
}
formatStr = i18n.Cat().Lookup(langID, i18n.MsgID(id))
}
// Ignore the caller-supplied component and operation if we already have them.
if componentName != "" && len(oldParams) >= 1 && !isEmptyString(oldParams[0]) {
componentName = ""
}
if opName != "" && len(oldParams) >= 2 && !isEmptyString(oldParams[1]) {
opName = ""
}
var msg string
var newParams []interface{}
if componentName == "" && opName == "" {
if formatStr == "" {
return e // Nothing to change.
} else { // Parameter list does not change.
newParams = e.ParamList
msg = i18n.FormatParams(formatStr, newParams...)
}
} else { // Replace at least one of the first two parameters.
newLen := len(oldParams)
if newLen < 2 {
newLen = 2
}
newParams = make([]interface{}, newLen)
copy(newParams, oldParams)
if componentName != "" {
newParams[0] = componentName
}
if opName != "" {
newParams[1] = opName
}
if formatStr != "" {
msg = i18n.FormatParams(formatStr, newParams...)
}
}
return E{e.ID, e.Action, msg, newParams, e.stackPCs, nil}
}
}
// ExplicitConvert converts a regular err into an E error, setting its IDAction to idAction. If
// err is already an E, it returns err or an equivalent value without changing its type, but
// potentially changing the language, component or operation if langID!=i18n.NoLangID,
// componentName!="" or opName!="" respectively. The caller's PC is added to the
// error's stack.
func ExplicitConvert(idAction IDAction, langID i18n.LangID, componentName string, opName string, err error) error {
if err == nil {
return nil
} else {
var stack []uintptr
if _, isE := assertIsE(err); !isE { // Walk the stack only if convertInternal will allocate an E.
stack = make([]uintptr, maxPCs)
stack = stack[:runtime.Callers(2, stack)]
}
return convertInternal(idAction, langID, componentName, opName, stack, err)
}
}
// defaultCtx is the context used when a nil context.T is passed to New() or Convert().
var (
defaultCtx *context.T
defaultCtxLock sync.RWMutex // Protects defaultCtx.
)
// SetDefaultContext sets the default context used when a nil context.T is
// passed to New() or Convert(). It is typically used in standalone
// programmes that have no RPC context, or in servers for the context of
// ancillary threads not associated with any particular RPC.
func SetDefaultContext(ctx *context.T) {
defaultCtxLock.Lock()
defaultCtx = ctx
defaultCtxLock.Unlock()
}
// Convert is like ExplicitConvert(), but obtains the language, component and operation names
// from the specified context.T. ctx may be nil.
func Convert(idAction IDAction, ctx *context.T, err error) error {
if err == nil {
return nil
}
langID, componentName, opName := dataFromContext(ctx)
var stack []uintptr
if _, isE := assertIsE(err); !isE { // Walk the stack only if convertInternal will allocate an E.
stack = make([]uintptr, maxPCs)
stack = stack[:runtime.Callers(2, stack)]
}
return convertInternal(idAction, langID, componentName, opName, stack, err)
}
// dataFromContext reads the languageID, component name, and operation name
// from the context, using defaults as appropriate.
func dataFromContext(ctx *context.T) (langID i18n.LangID, componentName string, opName string) {
// Use a default context if ctx is nil. defaultCtx may also be nil, so
// further nil checks are required below.
if ctx == nil {
defaultCtxLock.RLock()
ctx = defaultCtx
defaultCtxLock.RUnlock()
}
if ctx != nil {
langID = i18n.GetLangID(ctx)
value := ctx.Value(componentKey{})
componentName, _ = value.(string)
opName = vtrace.GetSpan(ctx).Name()
}
if componentName == "" {
componentName = filepath.Base(os.Args[0])
}
return defaultLangID(langID), componentName, opName
}
// Error returns the error message; if it has not been formatted for a specific
// language, a default message containing the error ID and parameters is
// generated. This method is required to fulfil the error interface.
func (e E) Error() string {
msg := e.Msg
if isDefaultIDAction(e.ID, e.Action) && msg == "" {
msg = i18n.Cat().Format(i18n.NoLangID, i18n.MsgID(ErrUnknown.ID), e.ParamList...)
} else if msg == "" {
msg = i18n.Cat().Format(i18n.NoLangID, i18n.MsgID(e.ID), e.ParamList...)
}
return msg
}
// String is the default printing function for SubErrs.
func (subErrs SubErrs) String() (result string) {
if len(subErrs) > 0 {
sep := ""
for _, s := range subErrs {
if (s.Options & Print) != 0 {
result += fmt.Sprintf("%s[%s: %s]", sep, s.Name, s.Err.Error())
sep = ", "
}
}
}
return result
}
// params returns the variadic arguments to ExplicitNew(). We do not export it
// to discourage its abuse as a general-purpose way to pass alternate return
// values.
func params(err error) []interface{} {
if e, ok := assertIsE(err); ok {
return e.ParamList
}
return nil
}
// subErrorIndex returns index of the first SubErrs in e.ParamList
// or len(e.ParamList) if there is no such parameter.
func (e E) subErrorIndex() (i int) {
for i = range e.ParamList {
if _, isSubErrs := e.ParamList[i].(SubErrs); isSubErrs {
return i
}
}
return len(e.ParamList)
}
// subErrors returns a copy of the subordinate errors accumulated into err, or
// nil if there are no such errors. We do not export it to discourage its
// abuse as a general-purpose way to pass alternate return values.
func subErrors(err error) (r SubErrs) {
if e, ok := assertIsE(err); ok {
size := 0
for i := range e.ParamList {
if subErrsParam, isSubErrs := e.ParamList[i].(SubErrs); isSubErrs {
size += len(subErrsParam)
}
}
if size != 0 {
r = make(SubErrs, 0, size)
for i := range e.ParamList {
if subErrsParam, isSubErrs := e.ParamList[i].(SubErrs); isSubErrs {
r = append(r, subErrsParam...)
}
}
}
}
return r
}
// addSubErrsInternal returns a copy of err with supplied errors appended as
// subordinate errors. Requires that errors[i].Err!=nil for 0<=i<len(errors).
func addSubErrsInternal(err error, langID i18n.LangID, componentName string, opName string, stack []uintptr, errors SubErrs) error {
var pe *E
var e E
var ok bool
if pe, ok = err.(*E); ok {
e = *pe
} else if e, ok = err.(E); !ok {
panic("non-verror.E passed to verror.AddSubErrs")
}
var subErrs SubErrs
index := e.subErrorIndex()
copy(e.ParamList, e.ParamList)
if index == len(e.ParamList) {
e.ParamList = append(e.ParamList, subErrs)
} else {
copy(subErrs, e.ParamList[index].(SubErrs))
}
for _, subErr := range errors {
if _, ok := assertIsE(subErr.Err); ok {
subErrs = append(subErrs, subErr)
} else {
subErr.Err = convertInternal(IDAction{}, langID, componentName, opName, stack, subErr.Err)
subErrs = append(subErrs, subErr)
}
}
e.ParamList[index] = subErrs
if langID != i18n.NoLangID {
e.Msg = i18n.Cat().Format(langID, i18n.MsgID(e.ID), e.ParamList...)
}
return e
}
// ExplicitAddSubErrs returns a copy of err with supplied errors appended as
// subordinate errors. Requires that errors[i].Err!=nil for 0<=i<len(errors).
func ExplicitAddSubErrs(err error, langID i18n.LangID, componentName string, opName string, errors ...SubErr) error {
stack := make([]uintptr, maxPCs)
stack = stack[:runtime.Callers(2, stack)]
return addSubErrsInternal(err, langID, componentName, opName, stack, errors)
}
// AddSubErrs is like ExplicitAddSubErrs, but uses the provided context
// to obtain the langID, componentName, and opName values.
func AddSubErrs(err error, ctx *context.T, errors ...SubErr) error {
stack := make([]uintptr, maxPCs)
stack = stack[:runtime.Callers(2, stack)]
langID, componentName, opName := dataFromContext(ctx)
return addSubErrsInternal(err, langID, componentName, opName, stack, errors)
}
// debugStringInternal returns a more verbose string representation of an
// error, perhaps more thorough than one might present to an end user, but
// useful for debugging by a developer. It prefixes all lines output with
// "prefix" and "name" (if non-empty) and adds intent to prefix wen recursing.
func debugStringInternal(err error, prefix string, name string) string {
str := prefix
if len(name) > 0 {
str += name + " "
}
str += err.Error()
// Append err's stack, indented a little.
prefix += " "
buf := bytes.NewBufferString("")
stackToTextIndent(buf, Stack(err), prefix)
str += "\n" + buf.String()
// Print all the subordinate errors, even the ones that were not
// printed by Error(), indented a bit further.
prefix += " "
if subErrs := subErrors(err); len(subErrs) > 0 {
for _, s := range subErrs {
str += debugStringInternal(s.Err, prefix, s.Name)
}
}
return str
}
// DebugString returns a more verbose string representation of an error,
// perhaps more thorough than one might present to an end user, but useful for
// debugging by a developer.
func DebugString(err error) string {
return debugStringInternal(err, "", "")
}