Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom decider #51

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 79 additions & 44 deletions sheriff.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,18 @@ import (
"github.com/hashicorp/go-version"
)

// A FieldFilter is a function that decides whether a field should be marshalled or not.
// If it returns true, the field will be marshalled, otherwise it will be skipped.
type FieldFilter func(field reflect.StructField) (bool, error)

// Options determine which struct fields are being added to the output map.
type Options struct {
// The FieldFilter makes the decision whether a field should be marshalled or not.
// It receives the reflect.StructField of the field and should return true if the field should be included.
// If this is not set then the default FieldFilter will be used, which uses the Groups and ApiVersion fields.
// Setting this value will result in the other options being ignored.
FieldFilter FieldFilter

// Groups determine which fields are getting marshalled based on the groups tag.
// A field with multiple groups (comma-separated) will result in marshalling of that
// field if one of their groups is specified.
Expand Down Expand Up @@ -67,7 +77,9 @@ func Marshal(options *Options, data interface{}) (interface{}, error) {
options.nestedGroupsMap = make(map[string][]string)
}

checkGroups := len(options.Groups) > 0
if options.FieldFilter == nil {
options.FieldFilter = createDefaultFieldFilter(options)
}

if t.Kind() == reflect.Ptr {
// follow pointer
Expand Down Expand Up @@ -138,53 +150,16 @@ func Marshal(options *Options, data interface{}) (interface{}, error) {
}

if !isEmbeddedField {
if checkGroups {
var groups []string
if field.Tag.Get("groups") != "" {
groups = strings.Split(field.Tag.Get("groups"), ",")
}

if len(groups) == 0 && options.nestedGroupsMap[field.Name] != nil {
groups = append(groups, options.nestedGroupsMap[field.Name]...)
}

// Marshall the field if
// - it has at least one of the requested groups
// or
// - it has no group and 'IncludeEmptyTag' is set to true
shouldShow := listContains(groups, options.Groups) || (len(groups) == 0 && options.IncludeEmptyTag)

// Prevent marshalling of the field if
// - it should not be shown (above)
// or
// - it has no groups and 'IncludeEmptyTag' is set to false
shouldHide := !shouldShow || (len(groups) == 0 && !options.IncludeEmptyTag)

if shouldHide {
// skip this field
continue
}
include, err := options.FieldFilter(field)
if err != nil {
return nil, err
}

if since := field.Tag.Get("since"); since != "" {
sinceVersion, err := version.NewVersion(since)
if err != nil {
return nil, err
}
if options.ApiVersion.LessThan(sinceVersion) {
continue
}
if !include {
// skip this field
continue
}

if until := field.Tag.Get("until"); until != "" {
untilVersion, err := version.NewVersion(until)
if err != nil {
return nil, err
}
if options.ApiVersion.GreaterThan(untilVersion) {
continue
}
}
}

v, err := marshalValue(options, val)
Expand All @@ -210,6 +185,66 @@ func Marshal(options *Options, data interface{}) (interface{}, error) {
return dest, nil
}

// createDefaultFieldFilter creates a default FieldFilter function which uses the options.Groups and options.ApiVersion
// fields in order to determine whether a field should be marshalled or not.
func createDefaultFieldFilter(options *Options) FieldFilter {
checkGroups := len(options.Groups) > 0

return func(field reflect.StructField) (bool, error) {
if checkGroups {
var groups []string
if field.Tag.Get("groups") != "" {
groups = strings.Split(field.Tag.Get("groups"), ",")
}

if len(groups) == 0 && options.nestedGroupsMap[field.Name] != nil {
groups = append(groups, options.nestedGroupsMap[field.Name]...)
}

// Marshall the field if
// - it has at least one of the requested groups
// or
// - it has no group and 'IncludeEmptyTag' is set to true
shouldShow := listContains(groups, options.Groups) || (len(groups) == 0 && options.IncludeEmptyTag)

// Prevent marshalling of the field if
// - it should not be shown (above)
// or
// - it has no groups and 'IncludeEmptyTag' is set to false
shouldHide := !shouldShow || (len(groups) == 0 && !options.IncludeEmptyTag)

if shouldHide {
// skip this field
return false, nil
}
}

if since := field.Tag.Get("since"); since != "" {
sinceVersion, err := version.NewVersion(since)
if err != nil {
return true, err
}
if options.ApiVersion.LessThan(sinceVersion) {
// skip this field
return false, nil
}
}

if until := field.Tag.Get("until"); until != "" {
untilVersion, err := version.NewVersion(until)
if err != nil {
return true, err
}
if options.ApiVersion.GreaterThan(untilVersion) {
// skip this field
return false, nil
}
}

return true, nil
}
}

// marshalValue is being used for getting the actual value of a field.
//
// There is support for types implementing the Marshaller interface, arbitrary structs, slices, maps and base types.
Expand Down
23 changes: 23 additions & 0 deletions sheriff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sheriff
import (
"encoding/json"
"net"
"reflect"
"testing"
"time"

Expand Down Expand Up @@ -853,3 +854,25 @@ func TestMarshal_User(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, `{"test":"12","testb":"true","testf":"12","tests":"\"test\""}`, string(d))
}

func TestMarshal_CustomFieldFilter(t *testing.T) {
type testStruct struct {
TestValue string `json:"test"`
SecretValue string `json:"secret" hidden:"true"`
}
v := testStruct{
TestValue: "teststring",
SecretValue: "asecretvalue",
}

o := &Options{
FieldFilter: func(field reflect.StructField) (bool, error) {
return field.Tag.Get("hidden") == "", nil
},
}
m, err := Marshal(o, v)
assert.NoError(t, err)

// ensure the "secret" value is not present in the marshalled map
assert.Equal(t, map[string]interface{}{"test": "teststring"}, m)
}
Loading