Skip to content

Commit

Permalink
fix(proto): field and value fmt formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
muktihari committed Feb 9, 2025
1 parent 5515a76 commit 8746aa9
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 0 deletions.
20 changes: 20 additions & 0 deletions proto/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,26 @@ type Field struct {
IsExpandedField bool
}

var _ fmt.Formatter = (*Field)(nil)

// Format controls how Field is formatted when using fmt. Instead of only printing
// FieldBase's pointer, it also include the value, making it easier to debug.
func (f Field) Format(p fmt.State, verb rune) {
switch {
case verb != 'v':
fmt.Fprintf(p, "%%!%c(%T=%v)", verb, f, f)
case p.Flag('+'):
fmt.Fprintf(p, "{FieldBase:(%p)(%+v) Value:%+v IsExpandedField:%t}",
f.FieldBase, f.FieldBase, f.Value, f.IsExpandedField)
case p.Flag('#'):
fmt.Fprintf(p, "{FieldBase:(%p)(%#v), Value:%#v, IsExpandedField:%t}",
f.FieldBase, f.FieldBase, f.Value, f.IsExpandedField)
default: // %v
fmt.Fprintf(p, "{(%p)(%v) %v %t}",
f.FieldBase, f.FieldBase, f.Value, f.IsExpandedField)
}
}

// WithValue returns a Field containing v value.
func (f Field) WithValue(v any) Field {
f.Value = Any(v)
Expand Down
46 changes: 46 additions & 0 deletions proto/proto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package proto_test

import (
"fmt"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -195,6 +196,51 @@ func TestMessageRemoveFieldByNum(t *testing.T) {
}
}

func TestFieldFormat(t *testing.T) {
field := factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(7))

tt := []struct {
format string
expected string
}{
{
format: "%f",
expected: fmt.Sprintf("%%!f(proto.Field={(%p)(%v) %v %t})",
field.FieldBase, field.FieldBase, field.Value, field.IsExpandedField),
},
{
format: "%s",
expected: fmt.Sprintf("%%!s(proto.Field={(%p)(%v) %v %t})",
field.FieldBase, field.FieldBase, field.Value, field.IsExpandedField),
},
{
format: "%v",
expected: fmt.Sprintf("{(%p)(%v) %v %t}",
field.FieldBase, field.FieldBase, field.Value, field.IsExpandedField),
},
{
format: "%+v",
expected: fmt.Sprintf("{FieldBase:(%p)(%+v) Value:%+v IsExpandedField:%t}",
field.FieldBase, field.FieldBase, field.Value, field.IsExpandedField),
},
{
format: "%#v",
expected: fmt.Sprintf("{FieldBase:(%p)(%#v), Value:%#v, IsExpandedField:%t}",
field.FieldBase, field.FieldBase, field.Value, field.IsExpandedField),
},
}

for i, tc := range tt {
t.Run(fmt.Sprintf("[%d] %s", i, tc.format), func(t *testing.T) {
var buf strings.Builder
fmt.Fprintf(&buf, tc.format, field)
if str := buf.String(); str != tc.expected {
t.Fatalf("expected:\n%q,\ngot:\n%q", tc.expected, str)
}
})
}
}

func TestFieldSubFieldSubtitution(t *testing.T) {
tt := []struct {
name string
Expand Down
18 changes: 18 additions & 0 deletions proto/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package proto

import (
"fmt"
"math"
"reflect"
"strconv"
Expand Down Expand Up @@ -224,6 +225,23 @@ func (v Value) String() string {
return unsafe.String((*byte)(v.ptr), v.num)
}

var _ fmt.Formatter = (*Value)(nil)

// Format controls how Value is formatted when using fmt. It overrides the String method, as the String method
// is used to return string value, rather than the Value formatted as a string.
func (v Value) Format(p fmt.State, verb rune) {
switch {
case v.typ == TypeInvalid:
fmt.Fprintf(p, "<invalid proto.Value>")
case verb != 'v':
fmt.Fprintf(p, fmt.FormatString(p, verb), v.Any())
case p.Flag('#'):
fmt.Fprintf(p, "%#v", v.Any())
default:
fmt.Fprintf(p, "%v", v.Any())
}
}

// SliceBool returns Value as []typedef.Bool, if it's not a valid []typedef.Bool value, it returns nil.
// The caller takes ownership of the returned value, so Value should no longer be used after this call,
// except the returned value is copied and the copied value is used instead.
Expand Down
44 changes: 44 additions & 0 deletions proto/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"math"
"runtime"
"strings"
"testing"
"time"
"unsafe"
Expand Down Expand Up @@ -303,6 +304,49 @@ func TestString(t *testing.T) {
})
}

func TestValueFormat(t *testing.T) {
tt := []struct {
value Value
format string
expected string
}{
{value: Value{}, format: "%v", expected: "<invalid proto.Value>"},
{value: String("FIT"), format: "%d", expected: "%!d(string=FIT)"},
{value: String("FIT"), format: "%s", expected: "FIT"},
{value: String("FIT"), format: "%5s", expected: " FIT"},
{value: String("FIT"), format: "%-5s", expected: "FIT "},
{value: String("FIT"), format: "%v", expected: "FIT"},
{value: String("FIT"), format: "%5v", expected: "FIT"},
{value: Uint64(11), format: "%d", expected: "11"},
{value: Uint64(11), format: "%f", expected: "%!f(uint64=11)"},
{value: Uint64(1), format: "%v", expected: "1"},
{value: Uint64(1), format: "%+v", expected: "1"},
{value: Uint64(1), format: "%#v", expected: "0x1"},
{value: Int64(1), format: "%v", expected: "1"},
{value: Int64(1), format: "%+v", expected: "1"},
{value: Int64(1), format: "%#v", expected: "1"},
{value: Float32(10.500001), format: "%.2f", expected: "10.50"},
{value: SliceUint64([]uint64{1, 2}), format: "%v", expected: "[1 2]"},
{value: SliceUint64([]uint64{1, 2}), format: "%+v", expected: "[1 2]"},
{value: SliceUint64([]uint64{1, 2}), format: "%#v", expected: "[]uint64{0x1, 0x2}"},
{value: SliceBool([]typedef.Bool{typedef.BoolTrue, typedef.BoolFalse}), format: "%v", expected: "[true false]"},
{value: SliceBool([]typedef.Bool{typedef.BoolTrue, typedef.BoolFalse}), format: "%+v", expected: "[true false]"},
{value: SliceBool([]typedef.Bool{typedef.BoolTrue, typedef.BoolFalse}), format: "%#v", expected: "[]typedef.Bool{0x1, 0x0}"},
{value: SliceBool(nil), format: "%v", expected: "[]"},
{value: SliceBool(nil), format: "%#v", expected: "[]typedef.Bool(nil)"},
}

for i, tc := range tt {
t.Run(fmt.Sprintf("[%d] %s(%v)", i, tc.format, tc.value.Any()), func(t *testing.T) {
var buf strings.Builder
fmt.Fprintf(&buf, tc.format, tc.value)
if str := buf.String(); str != tc.expected {
t.Fatalf("expected: %q, got: %q", tc.expected, str)
}
})
}
}

func TestSliceBool(t *testing.T) {
t.Run("correct", func(t *testing.T) {
slice := []typedef.Bool{typedef.BoolTrue, typedef.BoolFalse}
Expand Down

0 comments on commit 8746aa9

Please sign in to comment.