package rest import ( "errors" "fmt" "reflect" "strings" "git.ali33.ru/fcg-xvii/go-tools/json" ) type IRestFielder interface { RestFields(fieldName string, names FieldList) (any, IErrorArgs) } func FieldFromName(name string) *Field { return &Field{ Name: name, } } func FieldFromIface(val any) *Field { res := &Field{} if m, check := json.IsMap(val); check { res.Name = strings.TrimSpace(m.String("name", "")) if names := m.Slice("fields", nil); len(names) > 0 { res.Names = FieldListFromSlice(names) } } else { if str, check := val.(string); check { res.Name = str } } return res } // Field реализует ... type Field struct { Name string Names FieldList } func (s *Field) IsEmpty() bool { return len(s.Name) == 0 } func (s *Field) IsObject() bool { return len(s.Names) == 0 } func (s *Field) String() string { res := "{ " + s.Name if len(s.Names) > 0 { res += " [ " l := make([]string, len(s.Names)) for i, name := range s.Names { l[i] = name.String() } res += strings.Join(l, ", ") res += " ] " } res += " }" return res } ////////////////////////////////////////////// func FieldListFromNames(names ...string) FieldList { res := make(FieldList, 0, len(names)) for _, name := range names { res = append(res, FieldFromName(name)) } return res } func FieldListFromSlice(f []any) FieldList { res := make(FieldList, 0, len(f)) for _, v := range f { if fd := FieldFromIface(v); !fd.IsEmpty() { res = append(res, fd) } } return res } func FieldListFromObjects(f ...any) FieldList { return FieldListFromSlice(f) } type FieldList []*Field func (s FieldList) Field(name string) (*Field, bool) { for _, f := range s { if f.Name == name { return f, true } } return nil, false } // IFielder реализует интерфейс завершения формирования полей объекта в "ручном" режиме. // RestFields будет вызван после завершения автматического формирования полей объекта // result - массив с полями, сформированными автоматически, в него можно вносить правки // files - глобальный массив файловых дескрипторов, который будет передан в ответе клиенту type IFielderPost interface { RestFieldsPost(result json.Map, files map[string]IReadCloserLen, names FieldList) } type IFieldChecker interface { RestFieldCheck(fieldName string) bool } func fieldsDefault(t reflect.Type) (res FieldList) { for i := 0; i < t.NumField(); i++ { fType := t.Field(i) // проверяем тег, если он есть tag := fType.Tag.Get("rest") if len(tag) > 0 && (strings.Contains(tag, "default") || strings.Contains(tag, "fixed")) { res = append( res, &Field{ Name: fType.Name, }, ) } } return } func fieldsFixed(t reflect.Type) (res FieldNamesList) { for i := 0; i < t.NumField(); i++ { fType := t.Field(i) // проверяем тег, если он есть tag := fType.Tag.Get("rest") if len(tag) > 0 && strings.Contains(tag, "fixed") { res = append(res, fType.Name) } } return } func parseName(val reflect.Value) (res *Field, err error) { switch val.Kind() { case reflect.String: return &Field{ Name: 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 } fields := jm.Slice("names", nil) res = &Field{ Name: name, Names: FieldListFromSlice(fields), } return default: err = fmt.Errorf("invalid request %s", val.Kind()) } return } func fieldVal(val reflect.Value, fieldName string, files RequestFiles, names FieldList) (res reflect.Value, err IErrorArgs) { switch val.Kind() { case reflect.Ptr, reflect.Interface: return fieldVal(val.Elem(), fieldName, files, names) case reflect.Struct: // check fielder interface if f, check := val.Interface().(IRestFielder); check { rVal, err := f.RestFields(fieldName, names) if err != nil { return reflect.Value{}, err } return reflect.ValueOf(rVal), nil } // check fieler addr if val.CanAddr() { // check fielder interface if f, check := val.Addr().Interface().(IRestFielder); check { rVal, err := f.RestFields(fieldName, names) if err != nil { return reflect.Value{}, err } return reflect.ValueOf(rVal), nil } } // parse struct public fields if len(names) == 0 { names = fieldsDefault(val.Type()) } jm := json.Map{} addFieldVal := func(name *Field) bool { var field reflect.Value for i := 0; i < val.NumField(); i++ { f := val.Type().Field(i) if f.Name == name.Name { tag := f.Tag.Get("rest") if len(tag) > 0 && strings.Contains(tag, "ignore") { return true } field = val.Field(i) break } } if !field.IsValid() { // возвращаем true потому что поле может быть кастомным и объявлено позже, если объект реализовн как интерфейс RestFileldsPost return true } var fVal reflect.Value if fVal, err = fieldVal( field, fmt.Sprintf("%v.%v", fieldName, name.Name), files, name.Names, ); err != nil { return false } if fVal.IsValid() { jm[name.Name] = fVal.Interface() } else { jm[name.Name] = nil } return true } for _, name := range names { if !addFieldVal(name) { return } } // fields fixed fFixed := fieldsFixed(val.Type()) for _, fixed := range fFixed { if !jm.KeyExists(fixed) { addFieldVal(FieldFromName(fixed)) } } // post (когда результирующий объект уже сформирован) if fielder, check := val.Interface().(IFielderPost); check { fielder.RestFieldsPost(jm, files, names) } else if val.CanAddr() { if fielder, check := val.Addr().Interface().(IFielderPost); check { fielder.RestFieldsPost(jm, files, names) } } 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, nil) if err != nil { return } res.SetMapIndex(key, rVal) } return default: res = val return } } func FieldsAny(obj any, files RequestFiles, names FieldList) (any, IErrorArgs) { sVal := reflect.ValueOf(obj) rVal, err := fieldVal(sVal.Elem(), "", files, names) if err != nil { return nil, err } return rVal.Interface(), nil } // Fields позволяет получить значения объекта в json func Fields(obj any, files RequestFiles, names FieldList) (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, ErrorMessage("ErrFields", "Empty object") } return res, nil } type FieldNamesList []string func (s FieldNamesList) Exists(name string) bool { for _, fName := range s { if fName == name { return true } } return false } // GetStructFields возвращает список полей структуры func FieldNames(s any) (FieldNamesList, error) { // Получаем тип и проверяем, что это структура t := reflect.TypeOf(s) if t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface { return FieldNames(reflect.ValueOf(s).Elem().Interface()) } else if t.Kind() != reflect.Struct { return nil, errors.New("expected struct last type") } // Создаем срез для хранения имен полей var fields []string // Перебираем поля структуры for i := 0; i < t.NumField(); i++ { field := t.Field(i) fields = append(fields, field.Name) } return fields, nil } ///////////////////////////////////////////// func OutFields(req IRequestIn, obj any, files RequestFiles, names FieldList) IRequestOut { if files == nil { files = make(RequestFiles) } if len(names) == 0 { names = req.Fields() } m, err := Fields(obj, files, names) if err != nil { return req.OutError(err) } return req.OutSuccess(m, files) } /* func OutFieldsReq(req IRequestIn, obj any, files RequestFiles, names FieldList) IRequestOut { if len(names) == 0 { names = req.Fields() } return OutFields(req, obj, files, names) } */