Skip to content

Commit

Permalink
feat: add an option to hide empty sections (#3029)
Browse files Browse the repository at this point in the history
  • Loading branch information
quantumsheep committed Apr 17, 2023
1 parent 598e02e commit 9dc1772
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 11 deletions.
10 changes: 6 additions & 4 deletions internal/core/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ type ViewField struct {
}

type ViewSection struct {
Title string
FieldName string
Title string
FieldName string
HideIfEmpty bool
}

func (v *View) getHumanMarshalerOpt() *human.MarshalOpt {
Expand All @@ -38,8 +39,9 @@ func (v *View) getHumanMarshalerOpt() *human.MarshalOpt {
}
for _, section := range v.Sections {
opt.Sections = append(opt.Sections, &human.MarshalSection{
Title: section.Title,
FieldName: section.FieldName,
Title: section.Title,
FieldName: section.FieldName,
HideIfEmpty: section.HideIfEmpty,
})
}
opt.Title = v.Title
Expand Down
20 changes: 16 additions & 4 deletions internal/gofields/gofields.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import (
"strings"
)

type NilValueError struct {
Path string
}

func NewNilValueError(path string) *NilValueError {
return &NilValueError{Path: path}
}

func (e *NilValueError) Error() string {
return fmt.Sprintf("field %s is nil", e.Path)
}

// GetValue will extract the value at the given path from the data Go struct
// E.g data = { Friends: []Friend{ { Name: "John" } }, path = "Friends.0.Name" will return "John"
func GetValue(data interface{}, path string) (interface{}, error) {
Expand All @@ -23,8 +35,8 @@ func getValue(value reflect.Value, parents []string, path []string) (reflect.Val
return value, nil
}

if !value.IsValid() || isNil(value) {
return reflect.Value{}, fmt.Errorf("field %s is nil", strings.Join(parents, "."))
if !value.IsValid() || IsNil(value) {
return reflect.Value{}, NewNilValueError(strings.Join(parents, "."))
}

if value.Type().Kind() == reflect.Ptr {
Expand Down Expand Up @@ -148,8 +160,8 @@ func listFields(t reflect.Type, parents []string, filter ListFieldFilter) []stri
}
}

// isNil test if a given value is nil. It is saf to call the mthod with non nillable value like scalar types
func isNil(value reflect.Value) bool {
// IsNil test if a given value is nil. It is saf to call the mthod with non nillable value like scalar types
func IsNil(value reflect.Value) bool {
return (value.Kind() == reflect.Ptr || value.Kind() == reflect.Slice || value.Kind() == reflect.Map) && value.IsNil()
}

Expand Down
17 changes: 16 additions & 1 deletion internal/human/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,12 @@ func marshalStruct(value reflect.Value, opt *MarshalOpt) (string, error) {
if err != nil {
return "", err
}
sectionsStrs = append(sectionsStrs, sectionStr)

sectionFieldNames[section.FieldName] = true

if sectionStr != "" {
sectionsStrs = append(sectionsStrs, sectionStr)
}
}

var marshal func(reflect.Value, []string) ([][]string, error)
Expand Down Expand Up @@ -379,8 +383,19 @@ func marshalSection(section *MarshalSection, value reflect.Value, opt *MarshalOp

field, err := gofields.GetValue(value.Interface(), section.FieldName)
if err != nil {
if section.HideIfEmpty {
if _, ok := err.(*gofields.NilValueError); ok {
return "", nil
}
}

return "", err
}

if section.HideIfEmpty && reflect.ValueOf(field).IsZero() {
return "", nil
}

return Marshal(field, &subOpt)
}

Expand Down
91 changes: 91 additions & 0 deletions internal/human/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,97 @@ func TestMarshal(t *testing.T) {
`,
}))

t.Run("hide if empty pointer 1", run(&testCase{
data: &Human{
Name: "Sherlock Holmes",
Age: 42,
Address: nil,
Acquaintances: []*Acquaintance{
{Name: "Dr watson", Link: "Assistant"},
{Name: "Mrs. Hudson", Link: "Landlady"},
},
},
opt: &MarshalOpt{
Title: "Personal Information",
Sections: []*MarshalSection{
{FieldName: "Address", HideIfEmpty: true},
{Title: "Relationship", FieldName: "Acquaintances"},
},
},
result: `
Personal Information:
Name Sherlock Holmes
Age 42
Relationship:
NAME LINK
Dr watson Assistant
Mrs. Hudson Landlady
`,
}))

t.Run("hide if empty pointer 2", run(&testCase{
data: &Human{
Name: "Sherlock Holmes",
Age: 42,
Address: nil,
Acquaintances: []*Acquaintance{
{Name: "Dr watson", Link: "Assistant"},
{Name: "Mrs. Hudson", Link: "Landlady"},
},
},
opt: &MarshalOpt{
Title: "Personal Information",
Sections: []*MarshalSection{
{FieldName: "Address.Street", HideIfEmpty: true},
{Title: "Relationship", FieldName: "Acquaintances"},
},
},
result: `
Personal Information:
Name Sherlock Holmes
Age 42
Relationship:
NAME LINK
Dr watson Assistant
Mrs. Hudson Landlady
`,
}))

t.Run("hide if empty string", run(&testCase{
data: &Human{
Name: "",
Age: 42,
Address: &Address{Street: "221b Baker St", City: "London"},
Acquaintances: []*Acquaintance{
{Name: "Dr watson", Link: "Assistant"},
{Name: "Mrs. Hudson", Link: "Landlady"},
},
},
opt: &MarshalOpt{
Title: "Personal Information",
Sections: []*MarshalSection{
{FieldName: "Name", HideIfEmpty: true},
{FieldName: "Address"},
{Title: "Relationship", FieldName: "Acquaintances"},
},
},
result: `
Personal Information:
Age 42
Address:
Street 221b Baker St
City London
Relationship:
NAME LINK
Dr watson Assistant
Mrs. Hudson Landlady
`,
}))

t.Run("empty string", run(&testCase{
data: "",
result: `-`,
Expand Down
5 changes: 3 additions & 2 deletions internal/human/specs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type MarshalFieldOpt struct {
// MarshalSection describes a section to build from a given struct.
// When marshalling, this section is shown under the main struct section.
type MarshalSection struct {
FieldName string
Title string
FieldName string
Title string
HideIfEmpty bool
}

func (s *MarshalFieldOpt) getLabel() string {
Expand Down

0 comments on commit 9dc1772

Please sign in to comment.