Browse Source

in progress

0x4a52466c696e74 1 year ago
parent
commit
f894522441
7 changed files with 365 additions and 420 deletions
  1. 0 42
      command_store.go
  2. 0 126
      doc.go
  3. 26 1
      errors.go
  4. 160 59
      fielder.go
  5. 37 59
      response.go
  6. 24 6
      rest.go
  7. 118 127
      z_test.go

+ 0 - 42
command_store.go

@@ -1,42 +0,0 @@
-package rest
-
-import "sync"
-
-func NewCommandStore() *CommandStore {
-	return &CommandStore{
-		commands: make(map[string]ICommand),
-		locker:   &sync.RWMutex{},
-	}
-}
-
-type CommandStore struct {
-	commands map[string]ICommand
-	locker   *sync.RWMutex
-}
-
-func (s *CommandStore) AddCommand(uri string, command ICommand) {
-	s.locker.Lock()
-	s.commands[uri] = command
-	s.locker.Unlock()
-}
-
-func (s *CommandStore) AddCommandObjects(uri string, validator IValidator, executor IExecuter) {
-	s.AddCommand(uri, NewCommand(validator, executor))
-}
-
-func (s *CommandStore) GetCommand(uri string) (ICommand, bool) {
-	s.locker.RLock()
-	command, check := s.commands[uri]
-	s.locker.RUnlock()
-	return command, check
-}
-
-func (s *CommandStore) AddCommandMethods(uri string, validate func(*Request) *Response, execute func(*Request) *Response) {
-	s.AddCommand(
-		uri,
-		NewCommand(
-			NewValidator(validate),
-			NewExecuter(execute),
-		),
-	)
-}

+ 0 - 126
doc.go

@@ -1,126 +0,0 @@
-package rest
-
-import (
-	"log"
-	"sync"
-	"sync/atomic"
-	"time"
-)
-
-type ExampleGroup struct {
-	*Fielder
-	ID   int64
-	Name string
-}
-
-type ExampleUser struct {
-	*Fielder
-	ID    int64
-	Name  string
-	Token string
-	Group *ExampleGroup
-}
-
-// ExapmleApp реализует интерфейс IApplication, который отдает серверу параметры подключения,
-// секретный ключ шифрования токена авторизации,
-// а так же обрабатывает поступающие запросы
-type ExampleApp struct {
-	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.SAddr
-}
-
-func (s *ExampleApp) Secret() []byte {
-	return s.SSecret
-}
-
-func (s *ExampleApp) Executer(r *Request) (IExecuter, bool) {
-	return nil, false
-}
-
-var ExampleEngine *ExampleApp
-
-// ExampleNew показывает пример создания и запуска сервера с RestAPI
-func ExampleNew() {
-	// создаем новый сервер с использованием приложения 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 {
-		// ошибка запуска
-		log.Fatal(err)
-	}
-}
-
-////////////////////////////////////////////////////////////
-
-type ExampleRequestRegister struct {
-	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 := 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)
-}

+ 26 - 1
errors.go

@@ -5,6 +5,8 @@ import (
 )
 
 type IErrorArgs interface {
+	Name() string
+	Args() json.Map
 	Error() string
 	Map() json.Map
 }
@@ -15,6 +17,7 @@ var (
 	ErrAlreadyOpened  = "AlreadyOpened"
 	ErrNotOpened      = "NotOpened"
 	ErrKeyNotExists   = "KeyNotExists"
+	ErrKeyWrongValue  = "KeyWrongValue"
 	ErrKeyInvalidType = "KeyInvalidType"
 	ErrNotFound       = "NotFound"
 )
@@ -28,6 +31,16 @@ func NewErrPrtexpected(field string) IErrorArgs {
 	}
 }
 
+func ErrorFiled(field, message string) IErrorArgs {
+	return &Error{
+		name: "ErrField",
+		args: json.Map{
+			"field":   field,
+			"message": message,
+		},
+	}
+}
+
 func NewErrFieldRequired(field string) IErrorArgs {
 	return &Error{
 		name: "FieldRequired",
@@ -76,8 +89,20 @@ type Error struct {
 	args json.Map
 }
 
+func (s *Error) Name() string {
+	return s.name
+}
+
+func (s *Error) Args() json.Map {
+	return s.args
+}
+
 func (s *Error) Error() string {
-	return s.Map().JSONPrettyString()
+	m := json.Map{
+		"name": s.name,
+		"args": s.args,
+	}
+	return m.JSONString()
 }
 
 func (s *Error) Map() json.Map {

+ 160 - 59
fielder.go

@@ -1,88 +1,189 @@
 package rest
 
 import (
-	"errors"
 	"fmt"
+	"io"
+	"log"
 	"reflect"
+	"strings"
 
 	"git.ali33.ru/fcg-xvii/go-tools/json"
 )
 
-type IFielder interface {
-	Fields(...any) (json.Map, error)
-}
-
-type IFieldChecker interface {
-	FieldCheck(string) bool
+// Field реализует ...
+type Field struct {
+	Name  string
+	Names []any
 }
 
-/////////////////////////////////////////////////
+type FieldList []*Field
 
-func NewFielder(source any) *Fielder {
-	res := &Fielder{
-		source: source,
-		fields: make(map[string]reflect.Value),
+func (s FieldList) Field(name string) (*Field, bool) {
+	for _, f := range s {
+		if f.Name == name {
+			return f, true
+		}
 	}
-	res._Prepare()
-	return res
+	return nil, false
 }
 
-type Fielder struct {
-	source  any
-	fields  map[string]reflect.Value
-	checker IFieldChecker
+// IFielder реадизует интерфейс завершения формирования полей объекта в "ручном" режиме.
+// RestFields будет вызван после завершения автматического формирования полей объекта
+// result - массив с полями, сформированными автоматически, в него можно вносить правки
+// files - глобальный массив файловых дескрипторов, который будет передан в ответе клиенту
+type IFielder interface {
+	RestFields(result json.Map, files map[string]io.ReadCloser, names FieldList)
 }
 
-func (s *Fielder) _Prepare() {
-	s.checker, _ = s.source.(IFieldChecker)
-	val := reflect.ValueOf(s.source).Elem()
-	for i := 0; i < val.NumField(); i++ {
-		fName := val.Type().Field(i)
-		field := val.Field(i)
-		s.fields[camelToSnake(fName.Name)] = field
+func fieldsDefault(t reflect.Type) (res []any) {
+	for i := 0; i < t.NumField(); i++ {
+		fType := t.Field(i)
+		// проверяем тег, если он есть
+		tag := fType.Tag.Get("rest")
+		if len(tag) > 0 && strings.Contains(tag, "default") {
+			res = append(res, camelToSnake(fType.Name))
+		}
 	}
+	return
 }
 
-func (s *Fielder) Fields(names ...any) (json.Map, error) {
-	res := make(json.Map)
-	for _, rName := range names {
-		var name string
-		var fields []any
-		if sName, check := rName.(string); check {
-			name = sName
-		} else if sMap, check := rName.(map[string]any); check {
-			name = fmt.Sprint(sMap["name"])
-			if rFields, check := sMap["fields"]; check {
-				if rxFields, check := rFields.([]any); check {
-					fields = rxFields
-				}
-			}
+func parseName(val reflect.Value) (res *Field, err error) {
+	switch val.Kind() {
+	case reflect.String:
+		return &Field{
+			Name: camelToSnake(val.String()),
+		}, nil
+	case reflect.Map:
+		jm := make(json.Map)
+		jmVal := reflect.ValueOf(jm)
+		if !val.CanConvert(jmVal.Type()) {
+			err = fmt.Errorf("expected map[string]any")
+		}
+		jm = val.Convert(jmVal.Type()).Interface().(json.Map)
+		name := jm.String("name", "")
+		if len(name) == 0 {
+			err = fmt.Errorf("name is empty")
+			return
 		}
-		permission := true
-		if s.checker != nil {
-			permission = s.checker.FieldCheck(name)
+		name = camelToSnake(name)
+		fields := jm.Slice("fields", nil)
+		res = &Field{
+			Name:  name,
+			Names: fields,
 		}
-		val, check := s.fields[name]
-		if check && permission {
-			iface := val.Interface()
-			if val.Kind() == reflect.Ptr {
-				if val.IsNil() {
-					res[name] = nil
-				} else {
-					if fielder, check := iface.(IFielder); check {
-						xVal, err := fielder.Fields(fields...)
-						if err != nil {
-							return nil, err
-						}
-						res[name] = xVal
+		return
+	default:
+		err = fmt.Errorf("invalid request %s", val.Kind())
+	}
+	return
+}
+
+func fieldVal(val reflect.Value, fieldName string, files map[string]io.ReadCloser, names ...any) (res reflect.Value, err IErrorArgs) {
+	switch val.Kind() {
+	case reflect.Ptr, reflect.Interface:
+		return fieldVal(val.Elem(), fieldName, files, names...)
+	case reflect.Struct:
+		if len(names) == 0 {
+			names = fieldsDefault(val.Type())
+		}
+		jm := json.Map{}
+		var fields FieldList
+	loop:
+		for _, name := range names {
+			rField, rErr := parseName(reflect.ValueOf(name))
+			if rErr != nil {
+				message := fmt.Sprintf("%v: %v", name, err)
+				err = ErrorFiled(fieldName, message)
+				return
+			}
+			fields = append(fields, rField)
+			var field reflect.Value
+			for i := 0; i < val.NumField(); i++ {
+				f := val.Type().Field(i)
+				if camelToSnake(f.Name) == rField.Name {
+					tag := f.Tag.Get("rest")
+					if len(tag) > 0 && strings.Contains(tag, "ignore") {
+						continue loop
 					}
+					field = val.Field(i)
+					break
 				}
-			} else {
-				res[name] = val.Interface()
 			}
-		} else {
-			return nil, errors.New(name)
+			if !field.IsValid() {
+				err = ErrorFiled(rField.Name, "field is not found")
+				return
+			}
+			var fVal reflect.Value
+			if fVal, err = fieldVal(
+				field,
+				fmt.Sprintf("%v.%v", fieldName, rField.Name),
+				files,
+				rField.Names...,
+			); err != nil {
+				return
+			}
+			jm[rField.Name] = fVal.Interface()
+		}
+		log.Println("----->")
+		if fielder, check := val.Interface().(IFielder); check {
+			fielder.RestFields(jm, files, fields)
+		} else if val.CanAddr() {
+			if fielder, check := val.Addr().Interface().(IFielder); check {
+				fielder.RestFields(jm, files, fields)
+			}
+		}
+		res = reflect.ValueOf(jm)
+
+		return
+	case reflect.Slice:
+		sl := make([]any, val.Len())
+		for i := 0; i < val.Len(); i++ {
+			fName := fmt.Sprintf("%v[%v]", fieldName, i)
+			var rVal reflect.Value
+			rVal, err = fieldVal(
+				val.Index(i),
+				fName,
+				files,
+				names...,
+			)
+			if err != nil {
+				return
+			}
+			sl[i] = rVal.Interface()
 		}
+		res = reflect.ValueOf(sl)
+		return
+	case reflect.Map:
+		res = reflect.MakeMap(val.Type())
+		for _, key := range val.MapKeys() {
+			fName := fmt.Sprintf("%v[%v]", fieldName, key.String())
+			var rVal reflect.Value
+			iVal := val.MapIndex(key)
+			rVal, err = fieldVal(iVal, fName, files)
+			if err != nil {
+				return
+			}
+			res.SetMapIndex(key, rVal)
+		}
+		return
+	default:
+		res = val
+		return
+	}
+}
+
+// Fields позволяет получить значения объекта в json
+func Fields(obj any, files map[string]io.ReadCloser, names ...any) (json.Map, IErrorArgs) {
+	sVal := reflect.ValueOf(obj)
+	rVal, err := fieldVal(sVal.Elem(), "", files, names...)
+	if err != nil {
+		return nil, err
+	}
+	res, check := rVal.Interface().(json.Map)
+	if !check {
+		return nil, NewErrorMessage("ErrFields", "Empty object")
 	}
 	return res, nil
 }
+
+/////////////////////////////////////////////

+ 37 - 59
response.go

@@ -3,7 +3,6 @@ package rest
 import (
 	"bytes"
 	"encoding/json"
-	"errors"
 	"io"
 	"log"
 	"mime/multipart"
@@ -22,11 +21,14 @@ func NewResponse() *Response {
 
 // Response реализует объект ответа
 type Response struct {
-	data    mjson.Map
-	files   map[string]io.ReadCloser
-	err     error
-	errArgs mjson.Map
-	code    int
+	data  mjson.Map
+	files map[string]io.ReadCloser
+	err   IErrorArgs
+	code  int
+}
+
+func (s *Response) IsError() bool {
+	return s.err != nil
 }
 
 // KeySet устанавливает значение в словаре ответа по ключу
@@ -39,12 +41,6 @@ func (s *Response) FileSet(name string, file io.ReadCloser) {
 	s.files[name] = file
 }
 
-// SetError устанавливает ошибку в ответе (http код и текст ошибки)
-func (s *Response) SetError(code int, err error) {
-	s.code = code
-	s.err = err
-}
-
 // Close закрывает ресурсы ответа после завершения отдачи серверу
 func (s *Response) Close() {
 	for _, file := range s.files {
@@ -53,23 +49,20 @@ func (s *Response) Close() {
 }
 
 // Send отправляет запрос серверу
-func (s *Response) Send(w http.ResponseWriter) error {
+func (s *Response) Send(w http.ResponseWriter) IErrorArgs {
 	log.Println("SEND...")
 	defer s.Close()
-	if s.err != nil {
+	if s.IsError() {
 		w.Header().Set("Content-Type", "application/json")
 		w.WriteHeader(s.code)
-		body := mjson.Map{
-			"error": s.err.Error(),
-			"args":  s.errArgs,
-		}
-		w.Write(body.JSON())
+		w.Write([]byte(s.err.Error()))
 		return nil
 	}
 	// Если есть файлы, то используем multipart
 	if len(s.files) > 0 {
 		var b bytes.Buffer
 		writer := multipart.NewWriter(&b)
+		w.Header().Set("Content-Type", writer.FormDataContentType())
 
 		// Добавляем JSON-данные как часть
 		partHeader := make(textproto.MIMEHeader)
@@ -77,26 +70,26 @@ func (s *Response) Send(w http.ResponseWriter) error {
 		partHeader.Set("Content-Disposition", `form-data; name="data"`)
 		dataPart, err := writer.CreatePart(partHeader)
 		if err != nil {
-			return err
+			return NewErrorMessage("ErrResponsePartDataCreate", err.Error())
 		}
 		if err := json.NewEncoder(dataPart).Encode(s.data); err != nil {
-			return err
+			return NewErrorMessage("ErrResponseDataJsonEncode", err.Error())
 		}
 
 		// Добавляем файлы
 		for filename, file := range s.files {
 			part, err := writer.CreateFormFile("file", filename)
 			if err != nil {
-				return err
+				return NewErrorMessage("ErrResponsePartFileCreate", err.Error())
 			}
 			if _, err := io.Copy(part, file); err != nil {
-				return err
+				return NewErrorMessage("ErrResponsePartFileCopy", err.Error())
 			}
 		}
 
 		// Закрываем multipart writer
 		if err := writer.Close(); err != nil {
-			return err
+			return NewErrorMessage("ErrResponsePartFileClose", err.Error())
 		}
 
 		// Отправляем multipart response
@@ -105,7 +98,9 @@ func (s *Response) Send(w http.ResponseWriter) error {
 	} else {
 		// Если нет файлов, просто отправляем JSON
 		w.Header().Set("Content-Type", "application/json")
-		return json.NewEncoder(w).Encode(s.data)
+		if err := json.NewEncoder(w).Encode(s.data); err != nil {
+			return NewErrorMessage("ErrResponseDataJsonEncode", err.Error())
+		}
 	}
 
 	return nil
@@ -115,61 +110,44 @@ func (s *Response) Send(w http.ResponseWriter) error {
 
 func ResponseSuccess(data mjson.Map, files map[string]io.ReadCloser) *Response {
 	return &Response{
-		code:    200,
-		data:    data,
-		files:   files,
-		err:     nil,
-		errArgs: nil,
+		code:  200,
+		data:  data,
+		files: files,
 	}
 }
 
 // Конструкторы ошибок /////////////////////////////////////////
 
-func ResponseError(err string, args mjson.Map, code int) *Response {
+func ResponseErrorArgs(err IErrorArgs, code int) *Response {
 	return &Response{
-		code:    code,
-		err:     errors.New(err),
-		errArgs: args,
+		code: code,
+		err:  err,
 	}
 }
 
 func ResponseErrorMessage(err, message string, code int) *Response {
 	return &Response{
 		code: code,
-		err:  errors.New(err),
-		errArgs: mjson.Map{
-			"message": message,
-		},
+		err:  NewErrorMessage(err, message),
 	}
 }
 
 ///////////////////////////////////////////
 
-func ResponseErrKeyNotExists(key string) *Response {
-	return ResponseError(
-		ErrKeyNotExists,
-		mjson.Map{
-			"key": key,
-		},
-		500,
-	)
+func ResponseError(err IErrorArgs) *Response {
+	return &Response{
+		code: 500,
+		err:  err,
+	}
 }
 
-func ResponseErrKeyInvalidType(key, kType string) *Response {
-	return ResponseError(
-		ErrKeyInvalidType,
-		mjson.Map{
-			"key":  key,
-			"type": kType,
-		},
-		500,
-	)
+func ResponseErrField(field, message string) *Response {
+	return ResponseError(ErrorFiled(field, message))
 }
 
-func ResponseNotFound(data mjson.Map) *Response {
+func ResponseNotFound(path string) *Response {
 	return &Response{
-		code:    404,
-		err:     errors.New("ErrNotFound"),
-		errArgs: data,
+		code: 404,
+		err:  NewErrorMessage("ErrNotFound", path),
 	}
 }

+ 24 - 6
rest.go

@@ -29,6 +29,10 @@ type Rest struct {
 	app    IApplication
 }
 
+func (s *Rest) App() IApplication {
+	return s.app
+}
+
 func (s *Rest) TokenGenerate(m mjson.Map, expire int64) (string, error) {
 	token := jwt.New(jwt.SigningMethodHS256)
 
@@ -76,7 +80,7 @@ func responseNotFound(w http.ResponseWriter) {
 	w.WriteHeader(404)
 }
 
-func responseError(w http.ResponseWriter, err error, code int) {
+func responseError(w http.ResponseWriter, err IErrorArgs, code int) {
 	w.WriteHeader(code)
 	w.Write([]byte(err.Error()))
 }
@@ -94,6 +98,7 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 
 	// Парсим Bearer токен и извлекаем claims
 	authHeader := r.Header.Get("Authorization")
+	log.Println("AUTHHEAD", authHeader)
 	if authHeader != "" {
 		if parts := strings.Split(authHeader, " "); len(parts) == 2 && parts[0] == "Bearer" {
 			tokenString := parts[1]
@@ -116,11 +121,14 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
+	log.Println(r.Header.Get("Content-Type"))
+
 	// Если это многокомпонентный запрос, обрабатываем файлы
 	if strings.Index(r.Header.Get("Content-Type"), "multipart/form-data") == 0 {
 		err := r.ParseMultipartForm(32 << 20) // max memory 32MB, после этого файлы будут сохранены во временных файлах
+		log.Println(err)
 		if err != nil {
-			responseError(w, fmt.Errorf("failed to parse multipart form: %w", err), 500)
+			responseError(w, NewErrorMessage("ErrMultipartParse", err.Error()), 500)
 			return
 		}
 		multiPartForm := r.MultipartForm
@@ -129,7 +137,7 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 		if check {
 			err := json.NewDecoder(bytes.NewBuffer([]byte(data[0]))).Decode(&rr.data)
 			if err != nil {
-				responseError(w, fmt.Errorf("failed to decode JSON: %w", err), 500)
+				responseError(w, NewErrorMessage("ErrMultipartDataParse", err.Error()), 500)
 				return
 			}
 		}
@@ -138,7 +146,14 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 			for _, header := range headers {
 				file, err := header.Open()
 				if err != nil {
-					responseError(w, fmt.Errorf("failed to open file %s: %w", filename, err), 500)
+					err := NewError(
+						"ErrMultipartFileParse",
+						mjson.Map{
+							"filename": filename,
+							"message":  err.Error(),
+						},
+					)
+					responseError(w, err, 500)
 				}
 				rr.files[filename] = file
 			}
@@ -147,11 +162,13 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 	} else {
 		err := json.NewDecoder(r.Body).Decode(&rr.data)
 		if err != nil {
-			responseError(w, fmt.Errorf("failed to decode JSON: %w", err), 500)
+			responseError(w, NewErrorMessage("ErrDataParse", err.Error()), 500)
 			return
 		}
 	}
 
+	log.Println("RR", rr)
+
 	// get command
 
 	//command, check := s.commands.GetCommand(r.URL.Path)
@@ -161,8 +178,9 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	log.Println(rr.data)
 	// serialize
-	if err := serialize(rr.data, &command); err != nil {
+	if err := serialize(rr.data, command); err != nil {
 		responseError(w, err, 500)
 		return
 	}

+ 118 - 127
z_test.go

@@ -1,7 +1,6 @@
 package rest
 
 import (
-	"fmt"
 	"log"
 	"reflect"
 	"testing"
@@ -9,159 +8,151 @@ import (
 	"git.ali33.ru/fcg-xvii/go-tools/json"
 )
 
-type Child struct {
-	ID   int64
-	Name string
-}
-
-func NewTFielder() *TFielder {
-	res := &TFielder{}
-	res.Fielder = NewFielder(res)
-	return res
-}
-
-type TFielder struct {
-	*Fielder
-	ID       int64
-	Name     string
-	NumArr   []int64
-	FloatArr []float64
-	StrArr   []string
-	AnyArr   []any
-	ChildArr []*Child
-	//MapInt   map[string]int64
-}
+func TestFieldsDefault(t *testing.T) {
+	type RR struct {
+		ID      int64  `rest:"default"`
+		Name    string `rest:"default"`
+		GroupID int64
+	}
 
-func TestParse(t *testing.T) {
-	// int8 в int64
-	lInt, rInt := byte(10), int64(0)
-	if err := parseVal(reflect.ValueOf(lInt), reflect.ValueOf(&rInt), "int"); err != nil {
-		t.Fatal(err)
+	rr := RR{}
+	fields := fieldsDefault(reflect.TypeOf(rr))
+	expected := []any{"id", "name"}
+	if len(fields) != len(expected) {
+		t.Errorf("expected %v, real %v", expected, fields)
 	}
-	// из строки в строку
-	lStr, rStr := "copy message", ""
-	if err := parseVal(reflect.ValueOf(lStr), reflect.ValueOf(&rStr), "string"); err != nil {
-		t.Fatal(err)
+	for i, val := range expected {
+		if fields[i] != val {
+			t.Errorf("expected %v, real %v", expected, fields)
+		}
 	}
-	// массивы
-	lArr := []any{"one", "two"}
-	rArr := []string{}
-	if err := parseSlice(reflect.ValueOf(lArr), reflect.ValueOf(&rArr), "slice"); err != nil {
-		t.Fatal(err)
+}
+
+func TestFielderName(t *testing.T) {
+	name := "Name"
+	if field, err := parseName(reflect.ValueOf(name)); field.Name != camelToSnake(name) || err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+		t.Errorf("expected %v, real %v", camelToSnake(name), field.Name)
 	}
-	// map -> объект
-	lMap := json.Map{
-		"id":      10,
-		"name":    "Fielder-Name",
-		"num_arr": []any{1, 2, 3, 4, 5},
-		"any_arr": []any{"one", "two", 3, 4},
-		"child_arr": []json.Map{
-			{
-				"id":   11,
-				"name": "Name-77",
-			},
+	obj := json.Map{
+		"name": "Object",
+		"fields": []any{
+			"okko",
+			"cooler",
 		},
 	}
-	var rObj TFielder
-	if err := parseStruct(reflect.ValueOf(lMap), reflect.ValueOf(&rObj), "struct"); err != nil {
-		t.Fatal(err)
+	field, err := parseName(reflect.ValueOf(obj))
+	if err != nil {
+		t.Error(err)
 	}
-	log.Println(rObj)
-}
-
-func TestParse2(t *testing.T) {
-	//var val []int
-	//val = 20
-	val := []any{1, 2, 3, 4, 5}
-	log.Println(val)
-	var val2 []*float64
-	//val2 := []int{1, 2, 3}
-	if err := parseType(reflect.ValueOf(&val), reflect.ValueOf(&val2), "val"); err != nil {
-		t.Fatal(err)
+	// check name
+	if field.Name != "object" {
+		t.Errorf("expected %v, real %v", "object", name)
 	}
-	log.Println("KK", val2, val)
-	for _, p := range val2 {
-		t.Log(*p)
+	// check fields
+	for i, val := range []any{"okko", "cooler"} {
+		if field.Names[i] != val {
+			t.Errorf("expected %v, real %v", val, field.Names[i])
+		}
 	}
 }
 
-func TestParseMap(t *testing.T) {
-	//var val map[string]int
-	//val := map[string]int{"a": 1, "b": 2, "c": 3}
-	val := struct {
-		Name   string
-		AgeNum int
-	}{
-		Name:   "Иван",
-		AgeNum: 30,
-	}
-	var val2 map[string]any
-	if err := parseType(reflect.ValueOf(&val), reflect.ValueOf(&val2), "val"); err != nil {
-		t.Fatal(err)
-	}
-	log.Println("KK", val2, val)
+type RR struct {
+	ID      int64  `rest:"default"`
+	Name    string `rest:"default"`
+	GroupID int64  `rest:"ignore"`
+	Child   *RR
+	Nums    []int64
+	Childs  []*RR
+	MM      json.Map
 }
 
-type Person struct {
-	Name   string `rest:"required"`
-	AgeNum int
-	Child  *Person
-	Childs []*Person
+func (s *RR) RestFields(result json.Map, fields FieldList) {
+	if _, check := fields.Field("group_id"); check {
+		result["group_id"] = s.GroupID * 1000
+	}
 }
 
-func (s *Person) String() string {
-	return fmt.Sprintf("Person{Name: %v, AgeNum: %d, Child: %v}", s.Name, s.AgeNum, s.Child)
-}
+func TestFielderVal(t *testing.T) {
 
-func TestParseStruct(t *testing.T) {
-	val := json.Map{
-		"name":    "Иван",
-		"age_num": 30,
-		"child": json.Map{
-			"name":    "OOO",
-			"age_num": 10,
-		},
-		"childs": []json.Map{
-			{
-				"name":    "OOO",
-				"age_num": 10,
+	rr := &RR{
+		ID:      1,
+		Name:    "okko",
+		GroupID: 2,
+		MM: json.Map{
+			"one": 1,
+			"obj": &RR{
+				ID:   1,
+				Name: "mm-obj",
 			},
+		},
+		Child: &RR{
+			ID:      3,
+			Name:    "cooler",
+			GroupID: 4,
+		},
+		Nums: []int64{1, 2, 3},
+		Childs: []*RR{
 			{
-				"name":    "OOO1",
-				"age_num": 10,
+				ID:      5,
+				Name:    "okko1",
+				GroupID: 6,
 			},
 			{
-				"name":    "OOO2",
-				"age_num": 10,
+				ID:      7,
+				Name:    "cooler1",
+				GroupID: 8,
 			},
 		},
 	}
-	var val2 Person
-	if err := parseType(reflect.ValueOf(&val), reflect.ValueOf(&val2), "val"); err != nil {
-		t.Fatal(err)
-	}
-	t.Log(val2)
-	t.Log(val2.Name)
-}
 
-func TestSerialize(t *testing.T) {
-	from := json.Map{
-		//"name":    "Иван",
-		"age_num": 30,
-		"child": json.Map{
-			"name":    "OOO",
-			"age_num": 10,
+	val, err := fieldVal(
+		reflect.ValueOf(rr),
+		"",
+		nil,
+		"id",
+		"name",
+		"nums",
+		"group_id",
+		"mm",
+		json.Map{
+			"name":   "childs",
+			"fields": []any{"id", "name", "childs"},
+		},
+		json.Map{
+			"name":   "child",
+			"fields": []any{"id"},
 		},
+	)
+	if err != nil {
+		t.Error(err, err.Args())
 	}
 
-	var to *Person
+	m := val.Interface().(json.Map)
+	m.LogPretty()
 
-	if err := serialize(from, &to); err != nil {
-		t.Fatal(err)
+	mVal, err := Fields(
+		rr,
+		nil,
+		"id",
+		"name",
+		"nums",
+		"mm",
+		"group_id",
+		json.Map{
+			"name":   "childs",
+			"fields": []any{"id", "name", "childs"},
+		},
+		json.Map{
+			"name":   "child",
+			"fields": []any{"id"},
+		},
+	)
+	if err != nil {
+		t.Error(err, err.Args())
 	}
-	t.Log(to)
-}
-
-func TestEx(t *testing.T) {
-	ExampleNew()
+	log.Println("========================================")
+	mVal.LogPretty()
 }