|
@@ -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
|
|
|
}
|
|
|
+
|
|
|
+/////////////////////////////////////////////
|