Add structtags problem
This commit is contained in:
parent
5c7c62f30a
commit
6fa8a44943
3 changed files with 259 additions and 0 deletions
13
structtags/README.md
Normal file
13
structtags/README.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# structtags
|
||||||
|
|
||||||
|
Ускорьте функцию `Unpack()`, про которую рассказывали на лекции (https://p.go.manytask.org/08-reflect/lecture.slide#19).
|
||||||
|
|
||||||
|
Ваша функция должна работать быстрее, чем бейзлайн + 20%.
|
||||||
|
```
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
pkg: gitlab.com/slon/shad-go/structtags
|
||||||
|
BenchmarkUnpacker/user-4 3273 329346 ns/op
|
||||||
|
BenchmarkUnpacker/user+good+order-4 648 1721068 ns/op
|
||||||
|
PASS
|
||||||
|
```
|
80
structtags/structtags.go
Normal file
80
structtags/structtags.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// +build !solution
|
||||||
|
|
||||||
|
package structtags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Unpacker struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnpacker() *Unpacker {
|
||||||
|
return &Unpacker{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unpacker) Unpack(req *http.Request, ptr interface{}) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fields := make(map[string]reflect.Value)
|
||||||
|
v := reflect.ValueOf(ptr).Elem()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
fieldInfo := v.Type().Field(i)
|
||||||
|
tag := fieldInfo.Tag
|
||||||
|
name := tag.Get("http")
|
||||||
|
if name == "" {
|
||||||
|
name = strings.ToLower(fieldInfo.Name)
|
||||||
|
}
|
||||||
|
fields[name] = v.Field(i)
|
||||||
|
}
|
||||||
|
for name, values := range req.Form {
|
||||||
|
f := fields[name]
|
||||||
|
if !f.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, value := range values {
|
||||||
|
if f.Kind() == reflect.Slice {
|
||||||
|
elem := reflect.New(f.Type().Elem()).Elem()
|
||||||
|
if err := u.populate(elem, value); err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", name, err)
|
||||||
|
}
|
||||||
|
f.Set(reflect.Append(f, elem))
|
||||||
|
} else {
|
||||||
|
if err := u.populate(f, value); err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unpacker) populate(v reflect.Value, value string) error {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
v.SetString(value)
|
||||||
|
|
||||||
|
case reflect.Int:
|
||||||
|
i, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.SetInt(i)
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
b, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.SetBool(b)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported kind %s", v.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
166
structtags/structtags_test.go
Normal file
166
structtags/structtags_test.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package structtags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
expectedUser = User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "John",
|
||||||
|
Surname: "Doe",
|
||||||
|
Phone: "88005551234",
|
||||||
|
HasSubscription: true,
|
||||||
|
}
|
||||||
|
userURL = fmt.Sprintf(
|
||||||
|
"localhost/user?id=%d&name=%s&surname=%s&phone=%s&has_subscription=%t",
|
||||||
|
expectedUser.ID,
|
||||||
|
expectedUser.Name,
|
||||||
|
expectedUser.Surname,
|
||||||
|
expectedUser.Phone,
|
||||||
|
expectedUser.HasSubscription,
|
||||||
|
)
|
||||||
|
|
||||||
|
expectedGood = Good{
|
||||||
|
ID: 45,
|
||||||
|
Name: "pizza",
|
||||||
|
}
|
||||||
|
goodURL = fmt.Sprintf(
|
||||||
|
"localhost/good?id=%d&name=%s",
|
||||||
|
expectedGood.ID,
|
||||||
|
expectedGood.Name,
|
||||||
|
)
|
||||||
|
|
||||||
|
expectedOrder = Order{
|
||||||
|
ID: 37,
|
||||||
|
UserID: 73,
|
||||||
|
GoodIds: []int{1, 2, 3},
|
||||||
|
Date: "01.01.1970",
|
||||||
|
}
|
||||||
|
orderURL = fmt.Sprintf(
|
||||||
|
"localhost/order?id=%d&user_id=%d&good_ids=%d&good_ids=%d&good_ids=%d&date=%s",
|
||||||
|
expectedOrder.ID,
|
||||||
|
expectedOrder.UserID,
|
||||||
|
expectedOrder.GoodIds[0],
|
||||||
|
expectedOrder.GoodIds[1],
|
||||||
|
expectedOrder.GoodIds[2],
|
||||||
|
expectedOrder.Date,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Surname string
|
||||||
|
Phone string
|
||||||
|
HasSubscription bool `http:"has_subscription"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Good struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
ID int
|
||||||
|
UserID int `http:"user_id"`
|
||||||
|
GoodIds []int `http:"good_ids"`
|
||||||
|
Date string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpack_User(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", userURL, nil)
|
||||||
|
user := &User{}
|
||||||
|
u := NewUnpacker()
|
||||||
|
err := u.Unpack(r, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedUser.ID, user.ID)
|
||||||
|
require.Equal(t, expectedUser.Name, user.Name)
|
||||||
|
require.Equal(t, expectedUser.Surname, user.Surname)
|
||||||
|
require.Equal(t, expectedUser.Phone, user.Phone)
|
||||||
|
require.Equal(t, expectedUser.HasSubscription, user.HasSubscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpack_Good(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", goodURL, nil)
|
||||||
|
good := &Good{}
|
||||||
|
u := NewUnpacker()
|
||||||
|
err := u.Unpack(r, good)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedGood.ID, good.ID)
|
||||||
|
require.Equal(t, expectedGood.Name, good.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpack_Order(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", orderURL, nil)
|
||||||
|
order := &Order{}
|
||||||
|
u := NewUnpacker()
|
||||||
|
err := u.Unpack(r, order)
|
||||||
|
fmt.Println(orderURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedOrder.ID, order.ID)
|
||||||
|
require.Equal(t, expectedOrder.UserID, order.UserID)
|
||||||
|
require.Equal(t, expectedOrder.GoodIds, order.GoodIds)
|
||||||
|
require.Equal(t, expectedOrder.Date, order.Date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpack_ParseFormError(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("POST", "localhost", nil)
|
||||||
|
user := &User{}
|
||||||
|
u := NewUnpacker()
|
||||||
|
err := u.Unpack(r, user)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpack_IncorrectBoolData(t *testing.T) {
|
||||||
|
url := "localhost/user?id=1&has_subscription=7"
|
||||||
|
r, _ := http.NewRequest("GET", url, nil)
|
||||||
|
user := &User{}
|
||||||
|
u := NewUnpacker()
|
||||||
|
err := u.Unpack(r, user)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpack_IncorrectIntData(t *testing.T) {
|
||||||
|
url := "localhost/user?id=abc"
|
||||||
|
r, _ := http.NewRequest("GET", url, nil)
|
||||||
|
user := &User{}
|
||||||
|
u := NewUnpacker()
|
||||||
|
err := u.Unpack(r, user)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnpacker(b *testing.B) {
|
||||||
|
userRequest, _ := http.NewRequest("GET", userURL, nil)
|
||||||
|
user := &User{}
|
||||||
|
|
||||||
|
goodRequest, _ := http.NewRequest("GET", goodURL, nil)
|
||||||
|
good := &Good{}
|
||||||
|
|
||||||
|
orderRequest, _ := http.NewRequest("GET", orderURL, nil)
|
||||||
|
order := &Order{}
|
||||||
|
|
||||||
|
b.Run("user", func(b *testing.B) {
|
||||||
|
u := NewUnpacker()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for j := 0; j < 1000; j++ {
|
||||||
|
_ = u.Unpack(userRequest, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("user+good+order", func(b *testing.B) {
|
||||||
|
u := NewUnpacker()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for j := 0; j < 1000; j++ {
|
||||||
|
_ = u.Unpack(userRequest, user)
|
||||||
|
_ = u.Unpack(goodRequest, good)
|
||||||
|
_ = u.Unpack(orderRequest, order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue