Skip to content

Commit b335147

Browse files
committed
feat: Add support to disable methods
Currently `godump` always uses the stringer interface if defined. This is not always desirable if you want to check the internals of a type. This adds a feature with the same name used for [`spew`][spew] (`DisableMethods`) which can be set to true to disable this feature. [spew]: https://github.com/davecgh/go-spew/blob/8991bc29aa16c548c550c7ff78260e27b9ab7c73/spew/config.go#L52-L54
1 parent c7f103e commit b335147

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

godump.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ const (
2929

3030
// Default configuration values for the Dumper.
3131
const (
32-
defaultMaxDepth = 15
33-
defaultMaxItems = 100
34-
defaultMaxStringLen = 100000
35-
defaultMaxStackDepth = 10
36-
initialCallerSkip = 2
32+
defaultDisableMethods = false
33+
defaultMaxDepth = 15
34+
defaultMaxItems = 100
35+
defaultMaxStringLen = 100000
36+
defaultMaxStackDepth = 10
37+
initialCallerSkip = 2
3738
)
3839

3940
// defaultDumper is the default Dumper instance used by Dump and DumpStr functions.
@@ -86,6 +87,7 @@ type Dumper struct {
8687
maxStringLen int
8788
writer io.Writer
8889
skippedStackFrames int
90+
disableMethods bool
8991

9092
// callerFn is used to get the caller information.
9193
// It defaults to [runtime.Caller], it is here to be overridden for testing purposes.
@@ -149,15 +151,26 @@ func WithSkipStackFrames(n int) Option {
149151
}
150152
}
151153

154+
// WithDisableMethods will determine if interface methods such as the stringer
155+
// interface should be called for types implementing it. This can be useful if
156+
// you want to see the internals of types implementing a method.
157+
func WithDisableMethods(b bool) Option {
158+
return func(d *Dumper) *Dumper {
159+
d.disableMethods = b
160+
return d
161+
}
162+
}
163+
152164
// NewDumper creates a new Dumper with the given options applied.
153165
// Defaults are used for any setting not overridden.
154166
func NewDumper(opts ...Option) *Dumper {
155167
d := &Dumper{
156-
maxDepth: defaultMaxDepth,
157-
maxItems: defaultMaxItems,
158-
maxStringLen: defaultMaxStringLen,
159-
writer: os.Stdout,
160-
callerFn: runtime.Caller,
168+
maxDepth: defaultMaxDepth,
169+
maxItems: defaultMaxItems,
170+
maxStringLen: defaultMaxStringLen,
171+
disableMethods: defaultDisableMethods,
172+
writer: os.Stdout,
173+
callerFn: runtime.Caller,
161174
}
162175
for _, opt := range opts {
163176
d = opt(d)
@@ -404,7 +417,7 @@ func (d *Dumper) printValue(tw *tabwriter.Writer, v reflect.Value, indent int, v
404417
return
405418
}
406419

407-
if s := asStringer(v); s != "" {
420+
if s := d.asStringer(v); s != "" {
408421
fmt.Fprint(tw, s)
409422
return
410423
}
@@ -455,7 +468,7 @@ func (d *Dumper) printValue(tw *tabwriter.Writer, v reflect.Value, indent int, v
455468
}
456469
indentPrint(tw, indent+1, colorize(colorYellow, symbol)+field.Name)
457470
fmt.Fprint(tw, " => ")
458-
if s := asStringer(fieldVal); s != "" {
471+
if s := d.asStringer(fieldVal); s != "" {
459472
fmt.Fprint(tw, s)
460473
} else {
461474
d.printValue(tw, fieldVal, indent+1, visited)
@@ -535,7 +548,11 @@ func (d *Dumper) printValue(tw *tabwriter.Writer, v reflect.Value, indent int, v
535548
}
536549

537550
// asStringer checks if the value implements fmt.Stringer and returns its string representation.
538-
func asStringer(v reflect.Value) string {
551+
func (d *Dumper) asStringer(v reflect.Value) string {
552+
if d.disableMethods {
553+
return ""
554+
}
555+
539556
val := v
540557
if !val.CanInterface() {
541558
val = forceExported(val)

godump_test.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,6 @@ func TestTheKitchenSink(t *testing.T) {
653653
// Ensure no panic occurred and a sane dump was produced
654654
assert.Contains(t, out, "#") // loosest
655655
assert.Contains(t, out, "Everything") // middle-ground
656-
657656
}
658657

659658
func TestAnsiColorize_Disabled(t *testing.T) {
@@ -738,7 +737,7 @@ func TestAsStringer_ForceExported(t *testing.T) {
738737
v := reflect.ValueOf(h).Elem().FieldByName("secret") // now v.CanAddr() is true, but v.CanInterface() is false
739738

740739
assert.False(t, v.CanInterface(), "field must not be interfaceable")
741-
str := asStringer(v)
740+
str := defaultDumper.asStringer(v)
742741

743742
assert.Contains(t, str, "👻 hidden stringer")
744743
}
@@ -1052,5 +1051,26 @@ func TestDumpJSON(t *testing.T) {
10521051

10531052
assert.Equal(t, []any{"foo", float64(123), true}, got)
10541053
})
1054+
}
1055+
1056+
type hasStringer struct {
1057+
internal string
1058+
}
1059+
1060+
func (hasStringer) String() string {
1061+
return "stringer value"
1062+
}
1063+
1064+
func TestDisableMethods(t *testing.T) {
1065+
data := hasStringer{
1066+
internal: "internal value",
1067+
}
1068+
1069+
d := NewDumper(WithDisableMethods(true))
1070+
v := d.DumpStr(data)
1071+
require.Contains(t, v, "internal value")
10551072

1073+
d = NewDumper()
1074+
v = d.DumpStr(data)
1075+
require.Contains(t, v, "stringer value")
10561076
}

0 commit comments

Comments
 (0)