diff --git a/README.md b/README.md index e27822b..382ef41 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ type ListUsersInput struct { Page int `in:"query=page;default=1"` PerPage int `in:"query=per_page;default=20"` IsMember bool `in:"query=is_member"` - Search *string `in:"query=search,omitempty"` + Search *string `in:"query=search;omitempty"` } func ListUsers(rw http.ResponseWriter, r *http.Request) { diff --git a/core/directive.go b/core/directive.go index fa82fc3..9c7e808 100644 --- a/core/directive.go +++ b/core/directive.go @@ -17,6 +17,7 @@ func init() { RegisterDirective("default", &DirectiveDefault{}) RegisterDirective("nonzero", &DirectiveNonzero{}) registerDirective("path", defaultPathDirective) + registerDirective("omitempty", &DirectiveOmitEmpty{}) // decoder is a special executor which does nothing, but is an indicator of // overriding the decoder for a specific field. diff --git a/core/formencoder.go b/core/formencoder.go index 76f77be..7757ba0 100644 --- a/core/formencoder.go +++ b/core/formencoder.go @@ -2,7 +2,6 @@ package core import ( "github.com/ggicci/httpin/internal" - "slices" ) type FormEncoder struct { @@ -10,8 +9,12 @@ type FormEncoder struct { } func (e *FormEncoder) Execute(rtm *DirectiveRuntime) error { - if rtm.Value.IsZero() && slices.Contains(rtm.Directive.Argv, "omitempty") { - return nil + if rtm.Value.IsZero() { + for _, d := range rtm.Resolver.Directives { + if d.Name == "omitempty" { + return nil + } + } } if rtm.IsFieldSet() { diff --git a/core/header_test.go b/core/header_test.go index 639db7f..9def1cb 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -29,8 +29,8 @@ func TestDirectiveHeader_Decode(t *testing.T) { func TestDirectiveHeader_NewRequest(t *testing.T) { type ApiQuery struct { - ApiUid int `in:"header=x-api-uid,omitempty"` - ApiToken *string `in:"header=X-Api-Token,omitempty"` + ApiUid int `in:"header=x-api-uid;omitempty"` + ApiToken *string `in:"header=X-Api-Token;omitempty"` } t.Run("with all values", func(t *testing.T) { diff --git a/core/omitempty.go b/core/omitempty.go new file mode 100644 index 0000000..f7e002a --- /dev/null +++ b/core/omitempty.go @@ -0,0 +1,17 @@ +// directive: "omitempty" +// https://ggicci.github.io/httpin/directives/omitempty + +package core + +// DirectiveOmitEmpty is used with the DirectiveQuery, DirectiveForm, and DirectiveHeader to indicate that the field +// should be omitted when the value is empty. +// It does not have any affect when used by itself +type DirectiveOmitEmpty struct{} + +func (*DirectiveOmitEmpty) Decode(_ *DirectiveRuntime) error { + return nil +} + +func (*DirectiveOmitEmpty) Encode(_ *DirectiveRuntime) error { + return nil +} diff --git a/core/query_test.go b/core/query_test.go index 5691883..f2598b0 100644 --- a/core/query_test.go +++ b/core/query_test.go @@ -33,7 +33,7 @@ func TestDirectiveQuery_Decode(t *testing.T) { func TestDirectiveQuery_NewRequest(t *testing.T) { type SearchQuery struct { Name string `in:"query=name"` - Age int `in:"query=age,omitempty"` + Age int `in:"query=age;omitempty"` Enabled bool `in:"query=enabled"` Price float64 `in:"query=price"` @@ -41,7 +41,7 @@ func TestDirectiveQuery_NewRequest(t *testing.T) { AgeList []int `in:"query=age_list[]"` NamePointer *string `in:"query=name_pointer"` - AgePointer *int `in:"query=age_pointer,omitempty"` + AgePointer *int `in:"query=age_pointer;omitempty"` } t.Run("with all values", func(t *testing.T) {