0x4a52466c696e74 1 年間 前
コミット
5b258d9148
9 ファイル変更152 行追加677 行削除
  1. 77 23
      doc.go
  2. 12 0
      response.go
  3. 27 29
      rest.go
  4. 11 0
      serialize.go
  5. 0 161
      tests/app_avatar_test.go
  6. 0 9
      tests/z_example_avatar_test.goo
  7. 0 335
      tests/z_test.goo
  8. 0 117
      tests/z_test_api.goo
  9. 25 3
      z_test.go

+ 77 - 23
doc.go

@@ -3,54 +3,75 @@ package rest
 import (
 	"log"
 	"sync"
+	"sync/atomic"
 	"time"
 )
 
 type ExampleGroup struct {
-	ID   int64
-	Name string
-}
-
-type ExamplePermission struct {
+	*Fielder
 	ID   int64
 	Name string
 }
 
 type ExampleUser struct {
-	ID          int64
-	Name        string
-	Token       string
-	Group       *ExampleGroup
-	Permissions []*ExamplePermission
+	*Fielder
+	ID    int64
+	Name  string
+	Token string
+	Group *ExampleGroup
 }
 
 // ExapmleApp реализует интерфейс IApplication, который отдает серверу параметры подключения,
 // секретный ключ шифрования токена авторизации,
 // а так же обрабатывает поступающие запросы
 type ExampleApp struct {
-	addr        string
-	secret      []byte
-	users       *sync.Map
-	groups      *sync.Map
-	permissions *sync.Map
+	SAddr     string
+	SSecret   []byte
+	Users     *sync.Map
+	Groups    *sync.Map
+	IDCounter int64
+}
+
+func (s *ExampleApp) GenerateID() int64 {
+	id := atomic.AddInt64(&s.IDCounter, 1)
+	return id
 }
 
 func (s *ExampleApp) Addr() string {
-	return s.addr
+	return s.SAddr
 }
 
 func (s *ExampleApp) Secret() []byte {
-	return s.secret
+	return s.SSecret
 }
 
 func (s *ExampleApp) Executer(r *Request) (IExecuter, bool) {
 	return nil, false
 }
 
+var ExampleEngine *ExampleApp
+
 // ExampleNew показывает пример создания и запуска сервера с RestAPI
 func ExampleNew() {
 	// создаем новый сервер с использованием приложения ExampleApp
-	restServ := New(&ExampleApp{})
+	ExampleEngine = &ExampleApp{
+		SAddr:     "localhost:8080",
+		SSecret:   []byte("top-secret"),
+		Users:     new(sync.Map),
+		Groups:    new(sync.Map),
+		IDCounter: 1,
+	}
+	// Добавляем публичную группу
+	groupID := ExampleEngine.GenerateID()
+	ExampleEngine.Groups.Store(
+		groupID,
+		&ExampleGroup{
+			ID:   groupID,
+			Name: "public",
+		},
+	)
+	// создаем сервер
+	restServ := New(ExampleEngine)
 	// пробуем запустить его. Если через секунду запуск не удался, будет возвращена ошибка
 	if err := restServ.Listen(time.Second); err != nil {
 		// ошибка запуска
@@ -58,15 +79,48 @@ func ExampleNew() {
 	}
 }
 
+////////////////////////////////////////////////////////////
+
 type ExampleRequestRegister struct {
-	ID      int64   `rest:"id"`
-	Name    string  `rest:"name"`
-	RoleIDS []int64 `rest:"role_ids"`
-	GroupID int64
+	ID      int64
+	Name    string  `rest:"required"`
+	RoleIDS []int64 `rest:"required"`
+	GroupID int64   `rest:"required"`
+	// Объект группы, который определяется в валидации.
+	// В боевых условиях это поле должно быть приватное.
+	Group *ExampleGroup
 }
 
 func (s *ExampleRequestRegister) Validate(req *Request) *Response {
+	// пользователь не должен быть авторизован
+	if req.auth != nil {
+		return ResponseErrorMessage("AlreadyAuthorized", "User is already authorized", 500)
+	}
+	// проверяем имя
+	//if s.Name = strings.TrimSpace()
 	// вилидируем группу
-	//group, check := req.
+	group, check := ExampleEngine.Groups.Load(s.GroupID)
+	if !check {
+		return ResponseErrorMessage("GroupNotFound", "Group is not found", 500)
+	}
+	// Устанавливаем группу для использования в методе выполняния,
+	// чтобы не выбирать её снова
+	s.Group = group.(*ExampleGroup)
 	return nil
 }
+
+func (s *ExampleRequestRegister) Execute(req *Request) *Response {
+	userID := ExampleEngine.GenerateID()
+	user := &ExampleUser{
+		ID:    userID,
+		Name:  s.Name,
+		Group: s.Group,
+	}
+	user.Fielder = NewFielder(user)
+	ExampleEngine.Users.Store(userID, user)
+	fields, err := user.Fielder.Fields(req.Data().Slice("fields", nil)...)
+	if err != nil {
+		return ResponseErrorMessage("FieldsError", err.Error(), 500)
+	}
+	return ResponseSuccess(fields, nil)
+}

+ 12 - 0
response.go

@@ -111,6 +111,18 @@ func (s *Response) Send(w http.ResponseWriter) error {
 	return nil
 }
 
+// Успрешный ответ
+
+func ResponseSuccess(data mjson.Map, files map[string]io.ReadCloser) *Response {
+	return &Response{
+		code:    200,
+		data:    data,
+		files:   files,
+		err:     nil,
+		errArgs: nil,
+	}
+}
+
 // Конструкторы ошибок /////////////////////////////////////////
 
 func ResponseError(err string, args mjson.Map, code int) *Response {

+ 27 - 29
rest.go

@@ -154,36 +154,34 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 
 	// get command
 
-	/*
-		//command, check := s.commands.GetCommand(r.URL.Path)
-		command, check := s.app.Executer(rr)
-		if !check {
-			responseNotFound(w)
-			return
-		}
+	//command, check := s.commands.GetCommand(r.URL.Path)
+	command, check := s.app.Executer(rr)
+	if !check {
+		responseNotFound(w)
+		return
+	}
 
-				// serialize
-				if err := serializeRequest(command, rr.data, rr.files, ""); err != nil {
-					responseError(w, err, 500)
-					return
-				}
+	// serialize
+	if err := serialize(rr.data, &command); err != nil {
+		responseError(w, err, 500)
+		return
+	}
 
-				// validate
-				if validator, check := command.(IValidator); check {
-					resp := validator.Validate(rr)
-					if resp != nil {
-						if err := resp.Send(w); err != nil {
-							responseError(w, err, 500)
-						}
-						return
-					}
-				}
+	// validate
+	if validator, check := command.(IValidator); check {
+		resp := validator.Validate(rr)
+		if resp != nil {
+			if err := resp.Send(w); err != nil {
+				responseError(w, err, 500)
+			}
+			return
+		}
+	}
 
-				// execute
-				resp := command.Execute(rr)
-				if err := resp.Send(w); err != nil {
-					responseError(w, err, 500)
-				}
-			resp.Close()
-	*/
+	// execute
+	resp := command.Execute(rr)
+	if err := resp.Send(w); err != nil {
+		responseError(w, err, 500)
+	}
+	resp.Close()
 }

+ 11 - 0
serialize.go

@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"reflect"
 	"strings"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
 )
 
 func parseVal(from, to reflect.Value, fieldName string) IErrorArgs {
@@ -140,3 +142,12 @@ func parseMap(from, to reflect.Value, fieldName string) IErrorArgs {
 	}
 	return nil
 }
+
+func serialize(from json.Map, to any) IErrorArgs {
+	elemTo := reflect.ValueOf(to).Elem()
+	return parseType(
+		reflect.ValueOf(&from),
+		elemTo,
+		"",
+	)
+}

+ 0 - 161
tests/app_avatar_test.go

@@ -1,161 +0,0 @@
-package test_test
-
-import (
-	"crypto/rand"
-	"crypto/sha256"
-	"encoding/hex"
-	"fmt"
-	"io"
-	"log"
-	"os"
-	"time"
-
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-	"git.ali33.ru/fcg-xvii/rest"
-)
-
-// AvatarApp по запросу веб сервера создает объекты команд и возвращает их серверу
-type AvatarApp struct {
-	imagePath string
-}
-
-func (s *AvatarApp) Addr() string {
-	return ":7000"
-}
-
-func (s *AvatarApp) Secret() []byte {
-	return []byte("my-top-secret-123456-$$")
-}
-
-// ImagePath формирует путь к файлу аватара пользователя по его хэшу
-func (s *AvatarApp) ImagePath(hash string) string {
-	return fmt.Sprintf("%s/%s.jpg", s.imagePath, hash)
-}
-
-// Executer создает команду по запросу веб сервера
-func (s *AvatarApp) Executer(r *rest.Request) (rest.IExecuter, bool) {
-	// проверяем входной URL
-	switch r.RPath() {
-	case "/register": // Регистрация нового порльзователя
-		return &RequestReg{}, true
-	}
-	return nil, false
-}
-
-///////////////////////////////////////
-
-// RequestReg реализует команду регистрации нового пользователя.
-type RequestReg struct {
-	// имя пользователя
-	Name string `rest:"required" json:"name"`
-}
-
-// HashString генерирует хэш для строки.
-// Возвращает хэш-строку и возможную ошибку.
-func (s *RequestReg) HashString() (string, error) {
-	data := make([]byte, 16)
-	if _, err := rand.Read(data); err != nil {
-		return "", err
-	}
-
-	hash := sha256.Sum256(data)
-	hashString := hex.EncodeToString(hash[:])
-	return hashString, nil
-}
-
-// Validate проверяет запрос на корректность входных данных (не обязательный метод).
-func (s *RequestReg) Validate(r *rest.Request) *rest.Response {
-	if r.IsAuth() {
-		return rest.ResponseError("AlreadyAuth", nil, 500)
-	}
-	return nil
-}
-
-// Execute реализует логику команды. Выполняется после валидации
-func (s *RequestReg) Execute(r *rest.Request) *rest.Response {
-	// генерируем хэш нового пользователя
-	hash, err := s.HashString()
-	if err != nil {
-		return rest.ResponseErrorMessage("HashGenerate", err.Error(), 500)
-	}
-	// создаем токен авторизации (бессрочный)
-	token, err := r.GenerateToken(
-		json.Map{
-			"name": s.Name,
-			"hash": hash,
-		},
-		0,
-	)
-	if err != nil {
-		return rest.ResponseErrorMessage("AuthTokenGenerate", err.Error(), 500)
-	}
-	// создаем ответ
-	resp := rest.NewResponse()
-	resp.KeySet("token", token)
-
-	return resp
-}
-
-// swagger:response registrationResponse
-type RegistrationResponseWrapper struct {
-	// Встроенный объект ответа
-	//
-	// in: body
-	Body struct {
-		// Токен авторизации для пользователя
-		Token string `json:"token"`
-	}
-}
-
-////////////////////////////////////////////////////////////////
-
-type RequestUpload struct {
-	app       *AvatarApp
-	AvatarJpg io.Reader `rest:"required;file"`
-}
-
-func (s *RequestUpload) Validate(r *rest.Request) *rest.Response {
-	if !r.IsAuth() {
-		return rest.ResponseError("NotAuth", nil, 500)
-	}
-	return nil
-}
-
-func (s *RequestUpload) Execute(r *rest.Request) *rest.Response {
-	mAuth := r.Auth()
-	// открываем файл для обновления
-	f, err := os.OpenFile(s.app.ImagePath(mAuth.String("hash", "")), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
-	if err != nil {
-		return rest.ResponseErrorMessage("AvatarFileOpen", err.Error(), 500)
-	}
-	defer f.Close()
-	// обновляем файл
-	if _, err = io.Copy(f, s.AvatarJpg); err != nil {
-		return rest.ResponseErrorMessage("AvatarFileWrite", err.Error(), 500)
-	}
-	// файл обновлён
-	resp := rest.NewResponse()
-	resp.KeySet("ok", true)
-	return resp
-}
-
-/////////////////////////////////////////////////////////////////
-
-func RestNew() {
-	// объект приложения (rest.IApp)
-	app := &AvatarApp{}
-
-	// создание сервера
-	rest := rest.New(app)
-
-	// запуск
-	if err := rest.Listen(time.Second); err != nil {
-		log.Fatal(err)
-	}
-}
-
-func ExampleRestNew() {
-	RestNew()
-	ch := make(chan struct{})
-	<-ch
-}

+ 0 - 9
tests/z_example_avatar_test.goo

@@ -1,9 +0,0 @@
-package rest_test
-
-import "fmt"
-
-func ExampleHello() {
-	message := rest.Hello("Alice")
-	fmt.Println(message)
-	// Output: Hello, Alice!
-}

+ 0 - 335
tests/z_test.goo

@@ -1,335 +0,0 @@
-package test_test
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"log"
-	"mime/multipart"
-	"net/http"
-	"net/textproto"
-	"testing"
-	"time"
-
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-)
-
-var (
-	AddrCorrect   = "localhost:7000"
-	AddrIncorrect = "1.1.1.1:7000"
-	Secret        = []byte("itsMyWay")
-)
-
-func URL(path string) string {
-	return fmt.Sprintf("http://%s%s", AddrCorrect, path)
-}
-
-func sendRequest(uri string, data json.Map, files map[string]io.ReadCloser, auth string) ([]byte, error) {
-	log.Println(uri)
-	body := &bytes.Buffer{}
-	var contentType string
-
-	if len(files) == 0 {
-		// Если файлов нет, используем application/json
-		contentType = "application/json"
-		jsonData, err := json.Marshal(data)
-		if err != nil {
-			return nil, err
-		}
-		body.Write(jsonData)
-	} else {
-		// Если есть файлы, используем multipart/form-data
-		writer := multipart.NewWriter(body)
-
-		// Прикрепление файлов
-		for fieldName, file := range files {
-			part, err := writer.CreateFormFile(fieldName, fieldName)
-			if err != nil {
-				return nil, err
-			}
-			_, err = io.Copy(part, file)
-			if err != nil {
-				return nil, err
-			}
-			file.Close()
-		}
-
-		// Прикрепление JSON как части с Content-Type application/json
-		jsonHeader := make(textproto.MIMEHeader)
-		jsonHeader.Set("Content-Disposition", `form-data; name="data"`)
-		jsonHeader.Set("Content-Type", "application/json")
-		jsonPart, err := writer.CreatePart(jsonHeader)
-		if err != nil {
-			return nil, err
-		}
-		jsonPart.Write(data.JSON())
-
-		err = writer.Close()
-		if err != nil {
-			return nil, err
-		}
-
-		contentType = writer.FormDataContentType()
-	}
-
-	req, err := http.NewRequest("POST", uri, body)
-	if err != nil {
-		return nil, err
-	}
-
-	if len(auth) > 0 {
-		req.Header.Add("Authorization", "Bearer "+auth)
-	}
-
-	req.Header.Add("Content-Type", contentType)
-
-	client := &http.Client{}
-	resp, err := client.Do(req)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	rBody, _ := ioutil.ReadAll(resp.Body)
-
-	if resp.StatusCode != http.StatusOK {
-		err = fmt.Errorf("failed sending request: %s", resp.Status)
-	}
-
-	return rBody, err
-}
-
-func initFile(data []byte) io.ReadCloser {
-	buf := bytes.NewBuffer(data)
-	return io.NopCloser(buf)
-}
-
-//////////////////////////////////////////////////////////////////
-
-type tHello struct {
-	Name string `rest:"required"`
-}
-
-func (s *tHello) Validate(r *Request) *Response {
-	log.Println("validator...")
-	return nil
-}
-
-func (s *tHello) Execute(r *Request) *Response {
-	log.Println("execute...")
-	resp := NewResponse()
-	resp.KeySet("response", fmt.Sprintf("Hello, %v", s.Name))
-	return resp
-}
-
-type tApp struct{}
-
-func (s *tApp) Executer(r *Request) (IExecuter, bool) {
-	switch r.RPath() {
-	case "/hello":
-		return &tHello{}, true
-	}
-	return nil, false
-}
-
-//////////////////////////////////////////////////////////////////
-
-func TestListen(t *testing.T) {
-	rest := NewRest(AddrCorrect, Secret, &tApp{})
-	err := rest.Listen(time.Second)
-	if err != nil {
-		t.Log("open", err, rest.opened.Load())
-		return
-	}
-	t.Log("close", rest.Close(), rest.opened.Load())
-}
-
-func TestRequest(t *testing.T) {
-	rest := NewRest(AddrCorrect, Secret, &tApp{})
-	err := rest.Listen(time.Second)
-	if err != nil {
-		t.Log("open", err, rest.opened.Load())
-		return
-	}
-	defer rest.Close()
-	body, err := sendRequest(
-		URL("/hello"),
-		json.Map{
-			"name": "Anonimous",
-		},
-		nil,
-		"",
-	)
-	t.Log(string(body), err)
-}
-
-/*
-func TestToken(t *testing.T) {
-	commands := NewCommandStore()
-	auth := &AuthDebug{}
-	commands.AddCommand("/register", auth)
-	commands.AddCommand("/login", auth)
-	rest := NewRest(AddrCorrect, Secret, commands)
-	err := rest.Listen(time.Second)
-	if err != nil {
-		t.Log("open", err, rest.opened.Load())
-		return
-	}
-	defer rest.Close()
-
-	// register
-	body, err := sendRequest(
-		URL("/register"),
-		json.Map{
-			"login": "test",
-		},
-		nil,
-		"",
-	)
-	if err != nil {
-		t.Fatal(err, string(body))
-	}
-	var jm json.Map
-	json.Unmarshal(body, &jm)
-	token := jm.StringVal("token", "")
-	t.Log("token", token)
-
-	// auth
-	body, err = sendRequest(
-		URL("/login"),
-		json.Map{},
-		nil,
-		token,
-	)
-	if err != nil {
-		t.Fatal(err, string(body))
-	}
-}
-
-func newFielderTest(id, parentID int64, name string, price float64, child *tFielderTest) *tFielderTest {
-	f := &tFielderTest{
-		ID:       id,
-		Password: "top-secret",
-		ParentID: parentID,
-		Name:     name,
-		Price:    price,
-	}
-	f.Fielder = NewFielder(f)
-	return f
-}
-
-type tFielderTest struct {
-	*Fielder
-	ID       int64
-	Password string
-	ParentID int64
-	Name     string
-	Price    float64
-	Child    *tFielderTest
-}
-
-func (s *tFielderTest) FieldCheck(name string) bool {
-	if name == "password" {
-		return false
-	}
-	return true
-}
-
-func TestFielder(t *testing.T) {
-	f := newFielderTest(1, 10, "VTBR", 100, nil)
-	f1 := newFielderTest(10, 100, "SBER", 1000, nil)
-	f.Child = f1
-
-	fm, err := f.Fields(
-		"id",
-		"name",
-		"parent_id",
-		"price",
-		"password",
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-	fm.LogPretty()
-
-	f.ID = 11
-
-	fm, err = f.Fields(
-		"id",
-		"name",
-		"parent_id",
-		"price",
-		map[string]any{
-			"name": "child",
-			"fields": []any{
-				"id",
-				"name",
-				"parent_id",
-				map[string]any{
-					"name": "child",
-					"fields": []any{
-						"id",
-					},
-				},
-			},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-	fm.LogPretty()
-}
-
-func TestCase(t *testing.T) {
-	// Тестирование функций
-	testCamel := "ParentID"
-	testSnake := "parent_id"
-
-	snake := CamelToSnake(testCamel)
-	camel := SnakeToCamel(testSnake)
-
-	println(testCamel, "->", snake)
-	println(testSnake, "->", camel)
-}
-
-// Serialize /////////////////////////////////////////////////
-
-type tSChild struct {
-	ID       int64 `rest:"required"`
-	ParentID int64
-	Name     string
-}
-
-type tSerializeCommand struct {
-	ID          int64 `rest:"required;"`
-	ParentID    int64
-	Name        string
-	Child       *tSChild      `rest:"required"`
-	TestFileTxt io.ReadCloser `rest:"required;file"`
-}
-
-func TestSerialize(t *testing.T) {
-	jm := json.Map{
-		"id":        10,
-		"parent_id": 1,
-		"name":      "Cooler",
-		"child": map[string]any{
-			"id":        101,
-			"parent_id": 10,
-			"name":      "Childer...",
-		},
-	}
-	files := map[string]io.ReadCloser{
-		"test_file.txt": io.NopCloser(bytes.NewBuffer([]byte("Test file contents"))),
-	}
-
-	r := &tSerializeCommand{}
-
-	if err := serializeRequest(r, jm, files, ""); err != nil {
-		t.Fatal(err)
-	}
-
-	t.Log(r)
-}
-*/

+ 0 - 117
tests/z_test_api.goo

@@ -1,117 +0,0 @@
-package rest_test
-
-import (
-	"bytes"
-	"errors"
-	"io"
-	"log"
-
-	"git.ali33.ru/fcg-xvii/go-tools/json"
-)
-
-type ObjectDemo struct{}
-
-func (s *ObjectDemo) Validate(r *Request) *Response {
-	key := r.data.KeysExists([]string{"get-one", "get-two"})
-	if key != "" {
-		return ResponseErrKeyNotExists(key)
-	}
-	if !r.data.IsBoolean("get-one") {
-		return ResponseErrKeyInvalidType("get-one", "boolean")
-	}
-	if !r.data.IsBoolean("get-two") {
-		return ResponseErrKeyInvalidType("get-two", "boolean")
-	}
-	return nil
-}
-
-func (s *ObjectDemo) Execute(r *Request) *Response {
-	if fKeys := r.FileKeys(); len(fKeys) > 0 {
-		log.Println("FILES", fKeys)
-		tFile, _ := r.File(fKeys[0])
-		fContent, _ := io.ReadAll(tFile)
-		log.Println("FILE CONTENT", string(fContent))
-	}
-	resp := NewResponse()
-	var fileNames []string
-	if r.data.Bool("get-one", true) {
-		fileNames = append(fileNames, "get-one.txt")
-		buf := bytes.NewBuffer([]byte("get-one txt file :)"))
-		resp.FileSet("get-one.txt", io.NopCloser(buf))
-	}
-	if r.data.Bool("get-two", true) {
-		fileNames = append(fileNames, "get-two.txt")
-		buf := bytes.NewBuffer([]byte("get-two txt file :)"))
-		resp.FileSet("get-two.txt", io.NopCloser(buf))
-	}
-	resp.KeySet("file_names", fileNames)
-	return resp
-}
-
-////////////////////////////////////////////////////
-
-type JSONDebug struct{}
-
-func (s *JSONDebug) Validate(r *Request) *Response {
-	return nil
-}
-
-func (s *JSONDebug) Execute(r *Request) *Response {
-	resp := NewResponse()
-	resp.KeySet("completed", true)
-	return resp
-}
-
-////////////////////////////////////////////////////
-
-type AuthDebug struct{}
-
-func (s *AuthDebug) Validate(r *Request) *Response {
-	if r.RPath() == "/register" {
-		data := r.Data()
-		if !data.KeyExists("login") {
-			return ResponseErrKeyNotExists("login")
-		}
-		return nil
-	} else if r.RPath() == "/login" {
-		if !r.IsAuth() {
-			return ResponseError(
-				errors.New("NotAuth"),
-				nil,
-				500,
-			)
-		}
-		return nil
-	}
-	return ResponseNotFound(nil)
-}
-
-func (s *AuthDebug) Execute(r *Request) *Response {
-	switch r.RPath() {
-	case "/register":
-		return s.execRegister(r)
-	case "/login":
-		return s.execLogin(r)
-	}
-	return nil
-}
-
-func (s *AuthDebug) execRegister(r *Request) *Response {
-	log.Println("REDISTER")
-	data := r.Data()
-	token, err := r.GenerateToken(json.Map{
-		"login": data.StringVal("login", ""),
-	}, 0)
-	if err != nil {
-		return ResponseError(err, nil, 500)
-	}
-	resp := NewResponse()
-	resp.KeySet("token", token)
-	return resp
-}
-
-func (s *AuthDebug) execLogin(r *Request) *Response {
-	log.Println("login", r.Auth())
-	resp := NewResponse()
-	return resp
-}

+ 25 - 3
z_test.go

@@ -103,14 +103,14 @@ func TestParseMap(t *testing.T) {
 }
 
 type Person struct {
-	Name   *string
+	Name   string `rest:"required"`
 	AgeNum int
 	Child  *Person
 	Childs []*Person
 }
 
 func (s *Person) String() string {
-	return fmt.Sprintf("Person{Name: %v, AgeNum: %d, Child: %v}", *s.Name, s.AgeNum, s.Child)
+	return fmt.Sprintf("Person{Name: %v, AgeNum: %d, Child: %v}", s.Name, s.AgeNum, s.Child)
 }
 
 func TestParseStruct(t *testing.T) {
@@ -141,5 +141,27 @@ func TestParseStruct(t *testing.T) {
 		t.Fatal(err)
 	}
 	t.Log(val2)
-	t.Log(*val2.Name)
+	t.Log(val2.Name)
+}
+
+func TestSerialize(t *testing.T) {
+	from := json.Map{
+		//"name":    "Иван",
+		"age_num": 30,
+		"child": json.Map{
+			"name":    "OOO",
+			"age_num": 10,
+		},
+	}
+
+	var to *Person
+
+	if err := serialize(from, &to); err != nil {
+		t.Fatal(err)
+	}
+	t.Log(to)
+}
+
+func TestEx(t *testing.T) {
+	ExampleNew()
 }