package rest import ( "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") { 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() { 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 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 } } 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() } // 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 } } // 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 } /////////////////////////////////////////////