Skip to content

Commit

Permalink
feat: support embedded structs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesse0Michael committed Jan 3, 2022
1 parent 2b4bf9d commit f09a0b1
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 34 deletions.
6 changes: 3 additions & 3 deletions benchmark_results.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
goos: darwin
goarch: arm64
pkg: github.com/jesse0michael/go-request
BenchmarkDecode-10 1000000 2835 ns/op 1896 B/op 42 allocs/op
BenchmarkBaseline-10 1000000 1547 ns/op 1616 B/op 23 allocs/op
BenchmarkDecode-10 1000000 2749 ns/op 1880 B/op 41 allocs/op
BenchmarkBaseline-10 1000000 1500 ns/op 1600 B/op 22 allocs/op
PASS
ok github.com/jesse0michael/go-request 4.480s
ok github.com/jesse0michael/go-request 4.367s
27 changes: 10 additions & 17 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,15 @@ import (
)

type BenchReq struct {
Request struct {
State string `json:"state"`
} `body:"application/json"`
State string `json:"state"`
User string `path:"user"`
Active bool `query:"active"`
Friends []string `query:"friend,explode"`
Delay int `header:"X-DELAY"`
}

var expected = BenchReq{
Request: struct {
State string `json:"state"`
}{State: "active"},
State: "active",
User: "adam",
Active: true,
Friends: []string{"bob", "steve"},
Expand Down Expand Up @@ -93,26 +89,23 @@ func baselineDecode(r *http.Request) (*BenchReq, error) {
return nil, err
}

friends, _ := query["friend"]
friends := query["friend"]

b, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
defer r.Body.Close()
var data struct {
State string `json:"state"`
req := BenchReq{
User: vars["user"],
Active: active,
Delay: delay,
Friends: friends,
}
err = json.Unmarshal(b, &data)
err = json.Unmarshal(b, &req)
if err != nil {
return nil, err
}

return &BenchReq{
Request: data,
User: vars["user"],
Active: active,
Delay: delay,
Friends: friends,
}, nil
return &req, nil
}
32 changes: 32 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,35 @@ func ExampleDecode_multiple() {
// {Value:query}
// {Value:}
}

func ExampleDecode_embedded() {
r := mux.NewRouter()
r.Handle("/users", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req struct {
Request struct {
Active bool `query:"active"`
State string `json:"state"`
Delay int `header:"X-DELAY"`
} `body:"application/json"`
}
err := Decode(r, &req)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Println(err.Error())
}

fmt.Printf("%+v\n", req)
}))

body := `{"state":"idle"}`
req, _ := http.NewRequest(http.MethodPost, "http://www.example.com/users?active=true", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Delay", "60")
rec := httptest.NewRecorder()
r.ServeHTTP(rec, req)
if rec.Code == http.StatusBadRequest {
fmt.Println("decode failed")
}
// Output:
// {Request:{Active:true State:idle Delay:60}}
}
41 changes: 27 additions & 14 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,48 +29,61 @@ func Decode(r *http.Request, data interface{}) error {
}

func decodeRequest(r *http.Request, t reflect.Type, data interface{}) error {
body, err := decodeStruct(r, t, data)
if err != nil {
return err
}
if !body {
err := decodeBody(r, data)
if err != nil {
return err
}
}
return nil
}

func decodeStruct(r *http.Request, t reflect.Type, data interface{}) (bool, error) {
query := r.URL.Query()
vars := mux.Vars(r)
body := false
for i := 0; i < t.NumField(); i++ {
typ := t.Field(i)
field := reflect.ValueOf(data).Elem().Field(i)

if typ.Type.Kind() == reflect.Struct {
var err error
if body, err = decodeStruct(r, typ.Type, field.Addr().Interface()); err != nil {
return body, err
}
}

if queryTag := typ.Tag.Get("query"); queryTag != "" {
if err := decodeQuery(field, typ.Type, query, queryTag); err != nil {
return err
return body, err
}
}

if pathTag := typ.Tag.Get("path"); pathTag != "" {
if err := decodePath(field, typ.Type, vars, pathTag); err != nil {
return err
return body, err
}
}

if headerTag := typ.Tag.Get("header"); headerTag != "" {
if err := decodeHeader(field, typ.Type, r.Header, headerTag); err != nil {
return err
return body, err
}
}

bodyTag := typ.Tag.Get("body")
if bodyTag != "" {
body = true
v := reflect.New(typ.Type).Interface()
if err := decodeBody(r, v); err != nil {
return err
if err := decodeBody(r, field.Addr().Interface()); err != nil {
return body, err
}
field.Set(reflect.ValueOf(v).Elem())
}
}
if !body {
err := decodeBody(r, data)
if err != nil {
return err
}
}
return nil
return body, nil
}

func decodeQuery(field reflect.Value, typ reflect.Type, query url.Values, tag string) error {
Expand Down

0 comments on commit f09a0b1

Please sign in to comment.