From b8decc7a973a2706a8f9df37f75c86a04c41cb21 Mon Sep 17 00:00:00 2001 From: Alec Sammon Date: Thu, 30 May 2024 12:48:01 +0100 Subject: [PATCH 1/2] Add custom decider --- sheriff.go | 123 +++++++++++++++++++++++++++++++----------------- sheriff_test.go | 23 +++++++++ 2 files changed, 102 insertions(+), 44 deletions(-) diff --git a/sheriff.go b/sheriff.go index 85ca75b..d6f735f 100644 --- a/sheriff.go +++ b/sheriff.go @@ -10,8 +10,18 @@ import ( "github.com/hashicorp/go-version" ) +// A Decider 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 Decider func(field reflect.StructField) (bool, error) + // Options determine which struct fields are being added to the output map. type Options struct { + // The Decider 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 Decider will be used, which uses the Groups and ApiVersion fields. + // Setting this value will result in the other options being ignored. + Decider Decider + // 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. @@ -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.Decider == nil { + options.Decider = createDefaultDecider(options) + } if t.Kind() == reflect.Ptr { // follow pointer @@ -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.Decider(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) @@ -210,6 +185,66 @@ func Marshal(options *Options, data interface{}) (interface{}, error) { return dest, nil } +// createDefaultDecider creates a default Decider function which uses the options.Groups and options.ApiVersion fields +// in order to determine whether a field should be marshalled or not. +func createDefaultDecider(options *Options) Decider { + 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. diff --git a/sheriff_test.go b/sheriff_test.go index fe23197..b2dbca4 100644 --- a/sheriff_test.go +++ b/sheriff_test.go @@ -3,6 +3,7 @@ package sheriff import ( "encoding/json" "net" + "reflect" "testing" "time" @@ -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_CustomDecider(t *testing.T) { + type testStruct struct { + TestValue string `json:"test"` + SecretValue string `json:"secret" hidden:"true"` + } + v := testStruct{ + TestValue: "teststring", + SecretValue: "asecretvalue", + } + + o := &Options{ + Decider: 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) +} From 8dd1cf6c3b77423c660f91fa8e30b9daff3ca21c Mon Sep 17 00:00:00 2001 From: Alec Sammon Date: Fri, 31 May 2024 07:38:09 +0100 Subject: [PATCH 2/2] * rename to FieldFilter --- sheriff.go | 22 +++++++++++----------- sheriff_test.go | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/sheriff.go b/sheriff.go index d6f735f..c2d19a5 100644 --- a/sheriff.go +++ b/sheriff.go @@ -10,17 +10,17 @@ import ( "github.com/hashicorp/go-version" ) -// A Decider is a function that decides whether a field should be marshalled or not. +// 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 Decider func(field reflect.StructField) (bool, error) +type FieldFilter func(field reflect.StructField) (bool, error) // Options determine which struct fields are being added to the output map. type Options struct { - // The Decider makes the decision whether a field should be marshalled or not. + // 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 Decider will be used, which uses the Groups and ApiVersion fields. + // 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. - Decider Decider + 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 @@ -77,8 +77,8 @@ func Marshal(options *Options, data interface{}) (interface{}, error) { options.nestedGroupsMap = make(map[string][]string) } - if options.Decider == nil { - options.Decider = createDefaultDecider(options) + if options.FieldFilter == nil { + options.FieldFilter = createDefaultFieldFilter(options) } if t.Kind() == reflect.Ptr { @@ -150,7 +150,7 @@ func Marshal(options *Options, data interface{}) (interface{}, error) { } if !isEmbeddedField { - include, err := options.Decider(field) + include, err := options.FieldFilter(field) if err != nil { return nil, err } @@ -185,9 +185,9 @@ func Marshal(options *Options, data interface{}) (interface{}, error) { return dest, nil } -// createDefaultDecider creates a default Decider function which uses the options.Groups and options.ApiVersion fields -// in order to determine whether a field should be marshalled or not. -func createDefaultDecider(options *Options) Decider { +// 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) { diff --git a/sheriff_test.go b/sheriff_test.go index b2dbca4..bddbecf 100644 --- a/sheriff_test.go +++ b/sheriff_test.go @@ -855,7 +855,7 @@ func TestMarshal_User(t *testing.T) { assert.Equal(t, `{"test":"12","testb":"true","testf":"12","tests":"\"test\""}`, string(d)) } -func TestMarshal_CustomDecider(t *testing.T) { +func TestMarshal_CustomFieldFilter(t *testing.T) { type testStruct struct { TestValue string `json:"test"` SecretValue string `json:"secret" hidden:"true"` @@ -866,7 +866,7 @@ func TestMarshal_CustomDecider(t *testing.T) { } o := &Options{ - Decider: func(field reflect.StructField) (bool, error) { + FieldFilter: func(field reflect.StructField) (bool, error) { return field.Tag.Get("hidden") == "", nil }, }