-
Notifications
You must be signed in to change notification settings - Fork 0
/
trait_autodescriber.go
132 lines (123 loc) · 3.58 KB
/
trait_autodescriber.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
package meep
import (
"bytes"
"fmt"
"io"
"reflect"
)
// Errors that generate their messages automatically from their fields!
type TraitAutodescribing struct {
self interface{}
}
type meepAutodescriber interface {
isMeepAutodescriber() *TraitAutodescribing
}
// note that this function applies if you have a type which embeds this one sans-*, but you have a ref to that type.
func (m *TraitAutodescribing) isMeepAutodescriber() *TraitAutodescribing { return m }
func (m *TraitAutodescribing) ErrorMessage() string {
// Check for initialization.
// Can't do much of use if we didn't get initialized with a selfie reference.
if m.self == nil {
panic("meep:uninitialized")
}
// Unwind any pointer indirections.
rv_self := reflect.ValueOf(m.self)
for rv_self.Kind() == reflect.Ptr {
rv_self = rv_self.Elem()
}
// Start buffering.
buf := &bytes.Buffer{}
// Annouce the type info.
buf.WriteString("Error[")
buf.WriteString(rv_self.Type().String())
buf.WriteString("]:")
// Iterate over fields.
describeFields(rv_self, buf)
// That's it. Return the buffer results.
return buf.String()
}
func describeFields(subject reflect.Value, buf *bytes.Buffer) {
// Iterate over fields.
// If we hit any customs, save em; they serialize after other fields.
nField := subject.NumField()
havePrintedFields := false
var custom []func()
for i := 0; i < nField; i++ {
f := subject.Field(i)
// if it's one of the special/multiliners, stack it up for later
if consumed, fn := customDescribe(f.Type()); consumed {
if fn != nil {
custom = append(custom, func() { fn(f, buf) })
}
continue
}
// if it's a regular field, print the field=value pair
if havePrintedFields == false {
buf.WriteByte(' ')
}
havePrintedFields = true
buf.WriteString(subject.Type().Field(i).Name)
buf.WriteByte('=')
buf.WriteString(fmt.Sprintf("%#v", f.Interface()))
buf.WriteByte(';')
}
// Now go back and let the customs have their say.
// (If there are any: Start with a clean line; we're now an ML result.)
if len(custom) > 0 {
inspection := buf.Bytes() // fortunately this is copy free with go slices
hasTrailingBreak := inspection[len(inspection)-1] == '\n'
if !hasTrailingBreak {
buf.WriteByte('\n')
}
}
for _, fn := range custom {
fn()
// customs are expected to finish with one trailing \n apiece
}
}
func customDescribe(typ reflect.Type) (consumed bool, desc func(reflect.Value, io.Writer)) {
switch typ {
case reflect.TypeOf(AllTraits{}):
return true, func(f reflect.Value, buf io.Writer) {
describeFields(f, buf.(*bytes.Buffer))
}
case reflect.TypeOf(TraitTraceable{}):
return true, func(f reflect.Value, buf io.Writer) {
m := reflect.Indirect(f).Interface().(TraitTraceable)
if !m.IsStackSet() {
return
}
buf = indenter(buf)
buf.Write([]byte("Stack trace:\n"))
buf = indenter(buf)
m.WriteStack(buf)
}
case reflect.TypeOf(TraitCausable{}):
return true, func(f reflect.Value, buf io.Writer) {
m := reflect.Indirect(f).Interface().(TraitCausable)
if m.Cause == nil {
return
}
buf = indenter(buf)
buf.Write([]byte("Caused by: "))
// since we're now in multiline mode, we want to wrap up with a br.
msg := []byte(m.Cause.Error())
buf.Write(msg)
if len(msg) == 0 || msg[len(msg)-1] != '\n' {
buf.Write(br)
}
}
case reflect.TypeOf(TraitAutodescribing{}):
return true, nil
default:
return false, nil
}
}
/*
Implements `error`.
If you're using other mixins, you may want to override this again;
if you're just using `Autodescriber`, it'll do fine.
*/
func (m *TraitAutodescribing) Error() string {
return m.ErrorMessage()
}