dump

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 18, 2025 License: MIT Imports: 7 Imported by: 0

README

Dump Package

The dump package is a utility that serializes any Go value into its string representation.

The dump package is part of the CTX42 Testing Module, an ongoing effort to build a new, flexible, and developer-friendly testing framework.

The dump package provides a configurable way to render any Go value — whether it’s a simple integer, a nested struct, or a recursive data structure — into a human-readable string. This is particularly useful in testing, where comparing complex values often requires more than Go’s built-in reflect.DeepEqual. By converting values to strings, the dump package lets you leverage string comparison or diffing tools to pinpoint discrepancies quickly and accurately.

Basic Usage

val := types.TA{
    Dur: 3,
    Int: 42,
    Loc: types.WAW,
    Str: "abc",
    Tim: time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC),
    TAp: nil,
}

have := dump.Default().Any(val)

fmt.Println(have)
// Output:
//	{
//		Int: 42,
//		Str: "abc",
//		Tim: "2000-01-02T03:04:05Z",
//		Dur: "3ns",
//		Loc: "Europe/Warsaw",
//		TAp: nil,
//	}

The default dump renders the struct in a nicely formatted, multi-line string. Fields are listed in the order they’re declared in the struct, ensuring consistent output for reliable comparisons.

Configuration Options

One of the dump package’s strengths is its configurability. You can tweak how values are rendered to suit your needs. Here are some key options:

Flat Output

For a compact, single-line representation, use the Flat option:

val := map[string]any{
    "int": 42,
    "loc": types.WAW,
    "nil": nil,
}

have := dump.New(dump.WithFlat).Any(val)

fmt.Println(have)
// Output:
// map[string]any{"int": 42, "loc": "Europe/Warsaw", "nil": nil}

For maps, keys are sorted (when possible) to maintain consistency.

Custom Time Formats

You can customize how time.Time values are displayed using the dump.WithTimeFormat option:

val := map[time.Time]int{time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC): 42}

have := dump.New(dump.WithFlat, dump.WithTimeFormat(time.Kitchen)).Any(val)

fmt.Println(have)
// Output:
// map[time.Time]int{"3:04AM": 42}
Pointer Addresses

By default, pointer addresses are hidden, but you can enable them with dump.WithPtrAddr option:

val := map[string]any{
    "fn0": func() {},
    "fn1": func() {},
}

have := New(dump.WithPtrAddr).Any(val)

fmt.Println(have)
// Output:
// map[string]any{
// 	"fn0": <func>(<0x533760>),
// 	"fn1": <func>(<0x533780>),
// }
Custom Dumpers

For ultimate flexibility, you can define custom dumpers for specific types. Dumpers for types are regular functions matching dump.Dumper signature declared in the package.

type Dumper func(dmp Dump, level int, val reflect.Value) string

For example:

var i int
customIntDumper := func(dmp Dump, lvl int, val reflect.Value) string {
	switch val.Kind() {
	case reflect.Int:
		return fmt.Sprintf("%X", val.Int())
	default:
		panic("unexpected kind")
	}
}

have := dump.New(dump.WithFlat, dump.WithCompact, dump.WithDumper(i, customIntDumper)).Any(42)

fmt.Println(have)
// Output:
// 2A

The above example dumps integers as hexadecimal values, showcasing how you can tailor the output for your use case.

Handling Complex and Recursive Types

The dump package shines when dealing with complicated or recursive data structures. It includes cycle detection to prevent infinite loops. Here’s an example with a recursive struct:

type Node struct {
    Value    int
    Children []*Node
}

val := &Node{
    Value: 1,
    Children: []*Node{
        {Value: 2},
        {Value: 3, Children: []*Node{{Value: 4}}},
    },
}

have := dump.New().Any(val)
fmt.Println(have)
// Output:
// {
//		Value: 1,
//		Children: []*dump_test.Node{{
//			Value: 2,
//			Children: nil,
//		}, {
//			Value: 3,
//			Children: {{
//				Value: 4,
//				Children: nil,
//			}},
//		}},
//	}
type Node struct {
    Value    int
    Children []*Node
}

val := &Node{
    Value: 1,
    Children: []*Node{
        {Value: 2, Children: nil},
        {
            Value: 3,
            Children: []*Node{
                {Value: 4, Children: nil},
            },
        },
    },
}

have := dump.New().Any(val)
fmt.Println(have)
// Output:
// {
// 	Value: 1,
// 	Children: []*dump_test.Node{
// 		{
// 			Value: 2,
// 			Children: nil,
// 		},
// 		{
// 			Value: 3,
// 			Children: []*dump_test.Node{
// 				{
// 					Value: 4,
// 					Children: nil,
// 				},
// 			},
// 		},
// 	},
// }

Extensibility

The Dump package is built with extensibility in mind. Custom dumpers let you define how your own types are serialized, integrating seamlessly with the framework. This adaptability ensures the package can grow with your project’s needs.

Conclusion

Testing equivalence of complex data structures in Go doesn’t have to be a chore. The dump package simplifies the process by converting any value into a string, ready for comparison or diffing. Its configurability and extensibility make it a versatile tool for any testing scenario.

Documentation

Overview

Package dump can render string representation of any value.

Package dump.

nolint: forbidigo

Index

Examples

Constants

View Source
const (
	// DefaultTimeFormat is default format for parsing time strings.
	DefaultTimeFormat = time.RFC3339Nano

	// DefaultDepth is default depth when dumping values recursively.
	DefaultDepth = 6

	// DefaultIndent is default additional indent when dumping values.
	DefaultIndent = 0

	// DefaultTabWith is default tab width in spaces.
	DefaultTabWith = 2
)

Package wide default configuration.

View Source
const (
	DurAsString  = ""          // Same format as [time.Duration.String].
	DurAsSeconds = "<seconds>" // Duration as seconds float.
)

Formats to used by GetDurDumper.

View Source
const (
	TimeAsRFC3339  = ""         // Formats time as [time.RFC3339Nano].
	TimeAsUnix     = "<unix>"   // Formats time as Unix timestamp (seconds).
	TimeAsGoString = "<go-str>" // Formats time the same way as [time.GoString].
)

Formats to used by GetTimeDumper.

Variables

View Source
var (
	// TimeFormat is configurable format for dumping [time.Time] values.
	TimeFormat = DefaultTimeFormat

	// Depth is configurable depth when dumping values recursively.
	Depth = DefaultDepth

	// Indent is configurable additional indent when dumping values.
	Indent = DefaultIndent

	// TabWidth is configurable tab width in spaces.
	TabWidth = DefaultTabWith
)

Package wide configuration.

Functions

func DurDumperSeconds

func DurDumperSeconds(dmp Dump, lvl int, val reflect.Value) string

DurDumperSeconds requires val to be dereferenced representation of reflect.Duration and returns its string representation in format defined by Dump configuration.

func DurDumperString

func DurDumperString(dmp Dump, lvl int, val reflect.Value) string

DurDumperString requires val to be dereferenced representation of reflect.Duration and returns its string representation in format defined by Dump configuration.

func TimeDumperDate

func TimeDumperDate(dmp Dump, lvl int, val reflect.Value) string

TimeDumperDate requires val to be dereferenced representation of reflect.Struct which can be cast to time.Time and returns its representation using time.Time.GoString method.

func TimeDumperUnix

func TimeDumperUnix(dmp Dump, lvl int, val reflect.Value) string

TimeDumperUnix requires val to be dereferenced representation of reflect.Struct which can be cast to time.Time and returns its string representation as Unix timestamp.

func WithCompact

func WithCompact(dmp *Dump)

WithCompact is option for New which makes Dump display values without unnecessary whitespaces.

func WithFlat

func WithFlat(dmp *Dump)

WithFlat is option for New which makes Dump display values in one line.

func WithPtrAddr

func WithPtrAddr(dmp *Dump)

WithPtrAddr is option for New which makes Dump display pointer addresses.

Types

type Dump

type Dump struct {
	// Display values on one line.
	Flat bool

	// Display strings shorter that given value as with Flat.
	FlatStings int

	// Do not use any indents or whitespace separators.
	Compact bool

	// Controls how [time.Time] is formated.
	//
	// Aside from Go time formating layouts, the following custom formats are
	// available:
	//
	//  - [TimeAsUnix] - Unix timestamp,
	//
	// By default (empty value) [time.RFC3339Nano] is used.
	TimeFormat string

	// Controls how [time.Duration] is formated.
	//
	// Supports formats:
	//
	//  - [DurAsString]
	//  - [DurAsSeconds]
	DurationFormat string

	// Show pointer addresses.
	PtrAddr bool

	// Print types.
	PrintType bool

	// Use "any" instead of "interface{}".
	UseAny bool

	// Custom type dumpers.
	//
	// By default, dumpers for types:
	//   - [time.Time]
	//   - [time.Duration]
	//   - [time.Location]
	//
	// are automatically registered.
	Dumpers map[reflect.Type]Dumper

	// Controls maximum nesting when dumping recursive types.
	// The depth is also used to properly indent values being dumped.
	MaxDepth int

	// How much additional indentation to apply to values being dumped.
	Indent int

	// Default tab with in spaces.
	TabWidth int
}

Dump implements logic for dumping values and types.

func New

func New(opts ...Option) Dump

New returns new instance of Dump.

func (Dump) Any

func (dmp Dump) Any(val any) string

Any dumps any value to its string representation.

Example
package main

import (
	"fmt"
	"time"

	"github.com/ctx42/testing/internal/types"
	"github.com/ctx42/testing/pkg/dump"
)

func main() {
	val := types.TA{
		Dur: 3,
		Int: 42,
		Loc: types.WAW,
		Str: "abc",
		Tim: time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC),
		TAp: nil,
	}

	have := dump.New().Any(val)

	fmt.Println(have)
}
Output:

{
  Int: 42,
  Str: "abc",
  Tim: "2000-01-02T03:04:05Z",
  Dur: "3ns",
  Loc: "Europe/Warsaw",
  TAp: nil,
}
Example (CustomDumper)
package main

import (
	"fmt"
	"reflect"

	"github.com/ctx42/testing/pkg/dump"
)

func main() {
	var i int
	dumper := func(dmp dump.Dump, lvl int, val reflect.Value) string {
		switch val.Kind() {
		case reflect.Int:
			return fmt.Sprintf("%X", val.Int())
		default:
			panic("unexpected kind")
		}
	}
	opts := []dump.Option{
		dump.WithFlat,
		dump.WithCompact,
		dump.WithDumper(i, dumper),
	}

	have := dump.New(opts...).Any(42)

	fmt.Println(have)
}
Output:

2A
Example (CustomTimeFormat)
package main

import (
	"fmt"
	"time"

	"github.com/ctx42/testing/pkg/dump"
)

func main() {
	val := map[time.Time]int{time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC): 42}

	have := dump.New(dump.WithFlat, dump.WithTimeFormat(time.Kitchen)).Any(val)

	fmt.Println(have)
}
Output:

map[time.Time]int{"3:04AM": 42}
Example (FlatCompact)
package main

import (
	"fmt"

	"github.com/ctx42/testing/internal/types"
	"github.com/ctx42/testing/pkg/dump"
)

func main() {
	val := map[string]any{
		"int": 42,
		"loc": types.WAW,
		"nil": nil,
	}

	have := dump.New(dump.WithFlat).Any(val)

	fmt.Println(have)
}
Output:

map[string]any{"int": 42, "loc": "Europe/Warsaw", "nil": nil}
Example (Recursive)
package main

import (
	"fmt"

	"github.com/ctx42/testing/pkg/dump"
)

func main() {
	type Node struct {
		Value    int
		Children []*Node
	}

	val := &Node{
		Value: 1,
		Children: []*Node{
			{
				Value:    2,
				Children: nil,
			},
			{
				Value: 3,
				Children: []*Node{
					{
						Value:    4,
						Children: nil,
					},
				},
			},
		},
	}

	have := dump.New().Any(val)
	fmt.Println(have)
}
Output:

{
  Value: 1,
  Children: []*dump_test.Node{
    {
      Value: 2,
      Children: nil,
    },
    {
      Value: 3,
      Children: []*dump_test.Node{
        {
          Value: 4,
          Children: nil,
        },
      },
    },
  },
}

func (Dump) Value

func (dmp Dump) Value(val reflect.Value) string

Value dumps a reflect.Value representation of a value as a string.

type Dumper

type Dumper func(dmp Dump, level int, val reflect.Value) string

Dumper represents function signature for value dumpers.

func GetDurDumper

func GetDurDumper(format string) Dumper

GetDurDumper returns time.Duration dumper based on format.

func GetTimeDumper

func GetTimeDumper(format string) Dumper

GetTimeDumper returns time.Time dumper based on format.

func TimeDumperFmt

func TimeDumperFmt(format string) Dumper

TimeDumperFmt returns Dumper for time.Time using given format. The returned function requires val to be dereferenced representation of reflect.Struct which can be cast to time.Time and returns its string representation in format defined by Dump configuration.

type Option

type Option func(*Dump)

Option represents [NewConfig] option.

func WithDumper

func WithDumper(typ any, dumper Dumper) Option

WithDumper adds custom Dumper to the config.

func WithFlatStrings added in v0.6.0

func WithFlatStrings(n int) Option

WithFlatStrings configures the maximum length of strings to be represented as flat in the output. Strings longer than the specified length may be formatted differently, depending on the configuration. This option is similar to WithFlat but applies specifically to strings based on their length.

func WithIndent

func WithIndent(n int) Option

WithIndent is option for New which sets additional indentation to apply to dumped values.

func WithMaxDepth

func WithMaxDepth(maximum int) Option

WithMaxDepth is option for New which controls maximum nesting when bumping recursive types.

func WithTabWidth

func WithTabWidth(n int) Option

WithTabWidth is option for New setting tab width in spaces.

func WithTimeFormat

func WithTimeFormat(format string) Option

WithTimeFormat is option for New which makes Dump display time.Time using given format. The format might be standard Go time formating layout or one of the custom values - see [Dump.TimeFormat] for more details.

type Printer

type Printer struct {
	// contains filtered or unexported fields
}

Printer represents code printer.

func NewPrinter

func NewPrinter(dmp Dump) Printer

NewPrinter returns new Printer configured by Dump.

func (Printer) Comma

func (prn Printer) Comma(last bool) Printer

Comma prints Comma when not flat and not last entry.

func (Printer) NL

func (prn Printer) NL() Printer

NL prints new line when not flat.

func (Printer) NLI

func (prn Printer) NLI(cnt int) Printer

NLI prints new line when not flat and at least one entry.

func (Printer) Sep

func (prn Printer) Sep(last bool) Printer

Sep writes separator space.

func (Printer) Space

func (prn Printer) Space() Printer

Space writes a space when not compact.

func (Printer) String

func (prn Printer) String() string

String returns built string.

func (Printer) Tab

func (prn Printer) Tab(n int) Printer

Tab prints indentation with n spaces when not flat.

func (Printer) Write

func (prn Printer) Write(s string) Printer

Write writes string to the builder.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL