1
0
mirror of https://github.com/ggicci/httpin.git synced 2025-02-21 19:06:46 +02:00

Merge pull request #107 from alecsammon/omitempty

Add support for `omitempty` field tag to exclude empty values from queries and headers
This commit is contained in:
Ggicci 2024-05-20 00:32:17 -04:00 committed by GitHub
commit eed0621e7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 123 additions and 54 deletions

View File

@ -56,6 +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"`
}
func ListUsers(rw http.ResponseWriter, r *http.Request) {

View File

@ -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.

View File

@ -1,12 +1,20 @@
package core
import "github.com/ggicci/httpin/internal"
import (
"github.com/ggicci/httpin/internal"
)
type FormEncoder struct {
Setter func(key string, value []string) // form value setter
}
func (e *FormEncoder) Execute(rtm *DirectiveRuntime) error {
if rtm.Value.IsZero() {
if rtm.Resolver.GetDirective("omitempty") != nil {
return nil
}
}
if rtm.IsFieldSet() {
return nil // skip when already encoded by former directives
}

View File

@ -29,13 +29,15 @@ func TestDirectiveHeader_Decode(t *testing.T) {
func TestDirectiveHeader_NewRequest(t *testing.T) {
type ApiQuery struct {
ApiUid int `in:"header=x-api-uid"`
ApiToken string `in:"header=X-Api-Token"`
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) {
tk := "some-secret-token"
query := &ApiQuery{
ApiUid: 91241844,
ApiToken: "some-secret-token",
ApiToken: &tk,
}
co, err := New(ApiQuery{})
@ -48,4 +50,26 @@ func TestDirectiveHeader_NewRequest(t *testing.T) {
expected.Header.Set("x-api-uid", "91241844")
expected.Header.Set("X-Api-Token", "some-secret-token")
assert.Equal(t, expected, req)
})
t.Run("with empty value", func(t *testing.T) {
query := &ApiQuery{
ApiUid: 0,
ApiToken: nil,
}
co, err := New(ApiQuery{})
assert.NoError(t, err)
req, err := co.NewRequest("POST", "/api", query)
assert.NoError(t, err)
expected, _ := http.NewRequest("POST", "/api", nil)
assert.Equal(t, expected, req)
_, ok := req.Header["X-Api-Uid"]
assert.False(t, ok)
_, ok = req.Header["X-Api-Token"]
assert.False(t, ok)
})
}

17
core/omitempty.go Normal file
View File

@ -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
}

View File

@ -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"`
Age int `in:"query=age;omitempty"`
Enabled bool `in:"query=enabled"`
Price float64 `in:"query=price"`
@ -41,8 +41,10 @@ func TestDirectiveQuery_NewRequest(t *testing.T) {
AgeList []int `in:"query=age_list[]"`
NamePointer *string `in:"query=name_pointer"`
AgePointer *int `in:"query=age_pointer"`
AgePointer *int `in:"query=age_pointer;omitempty"`
}
t.Run("with all values", func(t *testing.T) {
query := &SearchQuery{
Name: "cupcake",
Age: 12,
@ -77,6 +79,22 @@ func TestDirectiveQuery_NewRequest(t *testing.T) {
expectedQuery.Set("age_pointer", "19") // query.PointerAge
expected.URL.RawQuery = expectedQuery.Encode()
assert.Equal(t, expected, req)
})
t.Run("with empty values", func(t *testing.T) {
query := &SearchQuery{}
co, err := New(SearchQuery{})
assert.NoError(t, err)
req, err := co.NewRequest("GET", "/pets", query)
assert.NoError(t, err)
assert.True(t, req.URL.Query().Has("name"))
assert.False(t, req.URL.Query().Has("age"))
assert.True(t, req.URL.Query().Has("name_pointer"))
assert.False(t, req.URL.Query().Has("age_pointer"))
})
}
type Location struct {