Skip to content

Commit

Permalink
fix: encoding post form bug
Browse files Browse the repository at this point in the history
  • Loading branch information
ggicci committed Dec 31, 2023
1 parent 3e64019 commit 923ff2f
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 43 deletions.
28 changes: 19 additions & 9 deletions core/body_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,19 @@ type BodyPayload struct {
}

type BodyPayloadInJSON struct {
Page int `in:"form=page"`
PageSize int `in:"form=page_size"`
Body *BodyPayload `in:"body=json"`
Body *BodyPayload `in:"body=json"`
}

type BodyPayloadInXML struct {
Body *BodyPayload `in:"body=xml"`
}

type YouCannotUseFormAndBodyAtTheSameTime struct {
Page int `in:"form=page"`
PageSize int `in:"form=page_size"`
Body *BodyPayload `in:"body=json"`
}

var sampleBodyPayloadInJSONText = `
{
"name": "Elia",
Expand All @@ -50,8 +54,6 @@ var sampleBodyPayloadInJSONText = `
}`

var sampleBodyPayloadInJSONObject = &BodyPayloadInJSON{
Page: 4,
PageSize: 30,
Body: &BodyPayload{
Name: "Elia",
Age: 14,
Expand Down Expand Up @@ -137,6 +139,18 @@ func TestBodyDirective_Decode_ErrUnknownBodyFormat(t *testing.T) {
assert.ErrorContains(t, err, "unknown body format: \"yaml\"")
}

func TestBodyDirective_Decode_ErrConflictWithFormDirective(t *testing.T) {
co, err := New(YouCannotUseFormAndBodyAtTheSameTime{})
assert.NoError(t, err)
req, err := co.NewRequest("POST", "/data", &YouCannotUseFormAndBodyAtTheSameTime{
Page: 1,
PageSize: 20,
Body: sampleBodyPayloadInJSONObject.Body,
})
assert.ErrorContains(t, err, "cannot use both form and body directive at the same time")
assert.Nil(t, req)
}

type yamlBody struct{}

var errYamlNotImplemented = errors.New("yaml not implemented")
Expand Down Expand Up @@ -201,10 +215,6 @@ func TestBodyDirective_NewRequest_JSON(t *testing.T) {
assert.NoError(err)
req, err := co.NewRequest("POST", "/data", sampleBodyPayloadInJSONObject)
expected, _ := http.NewRequest("POST", "/data", nil)
expected.Form = url.Values{
"page": {"4"},
"page_size": {"30"},
}
expected.Header.Set("Content-Type", "application/json")
assert.NoError(err)
var body bytes.Buffer
Expand Down
2 changes: 1 addition & 1 deletion core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (c *Core) NewRequestWithContext(ctx context.Context, method string, url str
return nil, err
}

rb := &RequestBuilder{}
rb := NewRequestBuilder()

// NOTE(ggicci): the error returned a joined error by using errors.Join.
if err = c.scanResolver.Scan(
Expand Down
6 changes: 4 additions & 2 deletions core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,13 @@ func TestCore_NamedCoder(t *testing.T) {
assert.NoError(t, err)

expected, _ := http.NewRequest("PUT", "/users/ggicci", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"name": {"Ggicci"},
"birthday": {"1991-11-10"},
"effective_between": {"2021-04-12", "2025-04-12"},
"created_between": {"2021-01-01T00:00:00Z", "2022-01-01T00:00:00Z"},
}
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
assert.Equal(t, expected, req)
}()
Expand Down Expand Up @@ -499,10 +500,11 @@ func TestRegisterCoder_CustomType_OverrideDefaultTypeCoder(t *testing.T) {
RegisterationPlace: &Place{Country: "US", City: "New_York"},
}
expected, _ := http.NewRequest("GET", "/search", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"is_member": {"yes"},
"registration_place": {"US.New_York"},
}
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")

req, err := co.NewRequest("GET", "/search", payload)
Expand Down
7 changes: 5 additions & 2 deletions core/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/url"
"os"
"path/filepath"
"strings"
"testing"

"github.com/ggicci/httpin/internal"
Expand Down Expand Up @@ -246,10 +247,11 @@ func TestUpload_WithNilFile(t *testing.T) {
Avatar: nil,
}
expected, _ := http.NewRequest("POST", "/post", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"name": {""},
"gender": {""},
}
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
co, err := New(UpdateUserProfileInput{})
assert.NoError(t, err)
Expand All @@ -263,9 +265,10 @@ func TestUpload_WithNilMultiFile(t *testing.T) {
Attachments: nil,
}
expected, _ := http.NewRequest("POST", "/post", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"title": {""},
}
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
co, err := New(UpdateGitHubIssueInput{})
assert.NoError(t, err)
Expand Down
8 changes: 6 additions & 2 deletions core/form_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package core

import (
"encoding/base64"
"io"
"net/http"
"net/url"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -168,7 +170,7 @@ func TestDirectiveForm_NewRequest(t *testing.T) {
assert.NoError(t, err)

expected, _ := http.NewRequest("POST", "/signup", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"bool": {"true"},
"int": {"9"},
"int8": {"14"},
Expand Down Expand Up @@ -211,6 +213,7 @@ func TestDirectiveForm_NewRequest(t *testing.T) {
"strings": {"Life", "is", "a", "Miracle"},
"times": {"2000-01-02T22:04:05Z", "1991-06-28T06:00:00Z"},
}
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
assert.Equal(t, expected, req)
}
Expand All @@ -230,14 +233,15 @@ func TestDirectiveForm_NewRequest_ByteSlice(t *testing.T) {
},
}
expected, _ := http.NewRequest("POST", "/api", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"bytes": {base64.StdEncoding.EncodeToString(payload.Bytes)},
"multi_bytes": {
base64.StdEncoding.EncodeToString(payload.MultiBytes[0]),
base64.StdEncoding.EncodeToString(payload.MultiBytes[1]),
},
}
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
req, err := co.NewRequest("POST", "/api", payload)
assert.NoError(t, err)
assert.Equal(t, expected, req)
Expand Down
5 changes: 4 additions & 1 deletion core/patch_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package core

import (
"io"
"net/http"
"net/url"
"path/filepath"
"strings"
"testing"

"github.com/ggicci/httpin/patch"
Expand Down Expand Up @@ -156,12 +158,13 @@ func TestPatchField_NewRequest_NoFiles(t *testing.T) {
}

expected, _ := http.NewRequest("POST", "/patchAccount", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"email": {"[email protected]"},
"age": {"18"},
"hobbies": {"reading", "swimming"},
}
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))

co, err := New(AccountPatch{})
assert.NoError(err)
Expand Down
43 changes: 22 additions & 21 deletions core/requestbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ type RequestBuilder struct {
Body io.ReadCloser
}

func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
Query: make(url.Values),
Form: make(url.Values),
Attachment: make(map[string][]FileMarshaler),
Header: make(http.Header),
Cookie: make([]*http.Cookie, 0),
Path: make(map[string]string),
}
}

func (rb *RequestBuilder) Populate(req *http.Request) error {
if err := rb.validate(); err != nil {
return err
Expand All @@ -38,15 +49,14 @@ func (rb *RequestBuilder) Populate(req *http.Request) error {
return err
}
} else { // urlencoded form
req.Form = rb.Form
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rb.populateForm(req)
}
}

// Populate body.
if rb.hasBody() {
req.Body = rb.Body
req.Header.Set("Content-Type", rb.bodyContentType())
rb.Header.Set("Content-Type", rb.bodyContentType())
}

// Populate path.
Expand All @@ -73,30 +83,18 @@ func (rb *RequestBuilder) Populate(req *http.Request) error {
}

func (rb *RequestBuilder) SetQuery(key string, value []string) {
if rb.Query == nil {
rb.Query = make(url.Values)
}
rb.Query[key] = value
}

func (rb *RequestBuilder) SetForm(key string, value []string) {
if rb.Form == nil {
rb.Form = make(url.Values)
}
rb.Form[key] = value
}

func (rb *RequestBuilder) SetHeader(key string, value []string) {
if rb.Header == nil {
rb.Header = make(http.Header)
}
rb.Header[http.CanonicalHeaderKey(key)] = value
}

func (rb *RequestBuilder) SetPath(key string, value []string) {
if rb.Path == nil {
rb.Path = make(map[string]string)
}
if len(value) > 0 {
rb.Path[key] = value[0]
}
Expand All @@ -108,9 +106,6 @@ func (rb *RequestBuilder) SetBody(bodyType string, bodyReader io.ReadCloser) {
}

func (rb *RequestBuilder) SetAttachment(key string, files []FileMarshaler) {
if rb.Attachment == nil {
rb.Attachment = make(map[string][]FileMarshaler)
}
rb.Attachment[key] = files
}

Expand All @@ -125,8 +120,8 @@ func (rb *RequestBuilder) bodyContentType() string {
}

func (rb *RequestBuilder) validate() error {
if rb.hasAttachment() && rb.hasBody() {
return errors.New("cannot use body directive and file upload at the same time")
if rb.hasForm() && rb.hasBody() {
return errors.New("cannot use both form and body directive at the same time")
}
return nil
}
Expand All @@ -147,6 +142,12 @@ func (rb *RequestBuilder) hasBody() bool {
return rb.Body != nil && rb.BodyType != ""
}

func (rb *RequestBuilder) populateForm(req *http.Request) {
rb.Header.Set("Content-Type", "application/x-www-form-urlencoded")
formData := rb.Form.Encode()
req.Body = io.NopCloser(strings.NewReader(formData))
}

func (rb *RequestBuilder) populateMultipartForm(req *http.Request) error {
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
Expand Down Expand Up @@ -183,7 +184,7 @@ func (rb *RequestBuilder) populateMultipartForm(req *http.Request) error {

// Set the body and content type.
req.Body = io.NopCloser(body)
req.Header.Set("Content-Type", writer.FormDataContentType())
rb.Header.Set("Content-Type", writer.FormDataContentType())
return nil
}

Expand Down
5 changes: 4 additions & 1 deletion core/required_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package core

import (
"io"
"net/http"
"net/url"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -59,11 +61,12 @@ func TestDirectiveRequired_NewRequest_RequiredFieldPresent(t *testing.T) {
Color: "red",
}
expected, _ := http.NewRequest("GET", "/hello", nil)
expected.Form = url.Values{
expectedForm := url.Values{
"created_at": {"1991-11-10T00:00:00Z"},
"colour": {"red"}, // NOTE: will use the first name in the tag
}
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
expected.Body = io.NopCloser(strings.NewReader(expectedForm.Encode()))
req, err := co.NewRequest("GET", "/hello", payload)
assert.NoError(t, err)
assert.Equal(t, expected, req)
Expand Down
6 changes: 2 additions & 4 deletions httpin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/ggicci/httpin/core"
Expand Down Expand Up @@ -154,10 +155,7 @@ func TestNewRequest(t *testing.T) {
assert.NoError(t, err)

expected, _ := http.NewRequest("GET", "/products", nil)
expected.Form = url.Values{
"page": {"19"},
"per_page": {"50"},
}
expected.Body = io.NopCloser(strings.NewReader("page=19&per_page=50"))
expected.Header.Set("Content-Type", "application/x-www-form-urlencoded")
assert.Equal(t, expected, req)
}
Expand Down

0 comments on commit 923ff2f

Please sign in to comment.