package rest import ( "errors" "fmt" "reflect" "strings" "git.ali33.ru/fcg-xvii/go-tools/json" ) type IRestFielder interface { RestFields(fieldName string, names ...any) (any, IErrorArgs) } // Field реализует ... type Field struct { Name string Names []any } 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) } 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") || strings.Contains(tag, "fixed")) { res = append(res, camelToSnake(fType.Name)) } } return } func fieldsFixed(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, "fixed") { res = append(res, camelToSnake(fType.Name)) } } return } 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 } name = camelToSnake(name) fields := jm.Slice("fields", nil) res = &Field{ Name: name, Names: fields, } return default: err = fmt.Errorf("invalid request %s", val.Kind()) } return } func fieldVal(val reflect.Value, fieldName string, files RequestFiles, 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: // 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{} var fields FieldList addFieldVal := func(name any) bool { rField, rErr := parseName(reflect.ValueOf(name)) if rErr != nil { message := fmt.Sprintf("%v: %v", name, err) err = ErrorFiled(fieldName, message) return false } 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") { return true } field = val.Field(i) break } } if !field.IsValid() { err = ErrorFiled(rField.Name, "field is not found") return false } var fVal reflect.Value if fVal, err = fieldVal( field, fmt.Sprintf("%v.%v", fieldName, rField.Name), files, rField.Names..., ); err != nil { return false } jm[rField.Name] = fVal.Interface() return true } for _, name := range names { if !addFieldVal(name) { return } } // fields fixed fFixed := fieldsFixed(val.Type()) for _, fixed := range fFixed { if !jm.KeyExists(fixed.(string)) { addFieldVal(fixed) } } // post (когда результирующий объект уже сформирован) if fielder, check := val.Interface().(IFielderPost); check { fielder.RestFieldsPost(jm, files, fields) } else if val.CanAddr() { if fielder, check := val.Addr().Interface().(IFielderPost); check { fielder.RestFieldsPost(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 } } func FieldsAny(obj any, files RequestFiles, names ...any) (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 ...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, 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, camelToSnake(field.Name)) } return fields, nil } ///////////////////////////////////////////// func OutFileds(req IRequestIn, obj any, files RequestFiles, names ...any) IRequestOut { 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 ...any) IRequestOut { if len(names) == 0 { names = req.RData().Slice("fields", nil) } return OutFileds(req, obj, files, names...) }