package rest import ( "fmt" "reflect" "strings" "git.ali33.ru/fcg-xvii/go-tools/json" ) // IRestConverter реализует пользовательский пасер из значения, полученного из запроса (см. пример из time.go) type IRestConverter interface { // RestFrom реализeт конвертацию из данных запроса в объект RestFrom(any) IErrorArgs } func parseVal(from, to reflect.Value, fieldName string) IErrorArgs { if from.Kind() == reflect.Interface { from = from.Elem() } if !from.CanConvert(to.Type()) { return NewError( "FieldType", json.Map{ "field": fieldName, "expected": to.Type().String(), "received": from.Type().String(), }, ) } to.Set(from.Convert(to.Type())) return nil } func parseType(from, to reflect.Value, fieldName string) IErrorArgs { from = realValue(from) to = realValue(to) switch to.Kind() { case reflect.Interface: if from.Kind() != reflect.Invalid { to.Set(from) } return nil case reflect.Struct: return parseStruct(from, to, fieldName) case reflect.Slice: return parseSlice(from, to, fieldName) case reflect.Map: return parseMap(from, to, fieldName) default: return parseVal(from, to, fieldName) } } func realValue(val reflect.Value) reflect.Value { if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { if val.IsNil() && val.Kind() == reflect.Ptr { val.Set(reflect.New(val.Type().Elem())) } // check cval := val.Elem() if cval.Kind() == reflect.Invalid { return val } val = cval if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { return realValue(cval) } } return val } func parseSlice(from, to reflect.Value, fieldName string) IErrorArgs { if to.IsNil() { to.Set(reflect.MakeSlice(to.Type(), 0, from.Cap())) } eType := to.Type().Elem() // Перебираем элементы среза, откуда копируем. for i := 0; i < from.Len(); i++ { iFieldName := fmt.Sprintf("%v[%v]", fieldName, i) fromVal := from.Index(i) toVal := reflect.New(eType).Elem() if err := parseType(fromVal, toVal.Addr(), iFieldName); err != nil { return err } to.Set(reflect.Append(to, toVal)) } return nil } func parseStruct(from, to reflect.Value, fieldName string) IErrorArgs { if f, check := to.Interface().(IRestConverter); check { return f.RestFrom(from.Interface()) } if to.CanAddr() { if f, check := to.Addr().Interface().(IRestConverter); check { return f.RestFrom(from.Interface()) } } loop: for i := 0; i < to.NumField(); i++ { required := false // устанавливаем тип и указатель поля объекта, куда копируем fType := to.Type().Field(i) fVal := to.Field(i) // проверяем тег, если он есть tag := fType.Tag.Get("rest") if fType.Anonymous && fType.Type.Kind() == reflect.Ptr { elem := fType.Type.Elem() if elem.Kind() == reflect.Struct { if fVal.IsNil() { fVal.Set(reflect.New(elem)) } if ierr := parseStruct(from, fVal.Elem(), prefixFieldName(fieldName, fType.Name)); ierr != nil { return ierr } continue loop } } if len(tag) > 0 { required = strings.Contains(tag, "required") } var rVal reflect.Value switch from.Kind() { case reflect.Struct: if rVal = from.FieldByName(fType.Name); !rVal.IsValid() { rVal = from.FieldByName(camelToSnake(fType.Name)) } case reflect.Map: if rVal = from.MapIndex(reflect.ValueOf(fType.Name)); !rVal.IsValid() { rVal = from.MapIndex(reflect.ValueOf(camelToSnake(fType.Name))) } default: return NewError( "FieldType", json.Map{ "field": fieldName, "expected": to.Type().String(), "received": from.Type().String(), }, ) } if !rVal.IsValid() { if required { return ErrorMessage("RequiredField", fType.Name) } continue } if rVal.Kind() == reflect.Interface { rVal = rVal.Elem() } iFieldName := fmt.Sprintf("%v.%v", fieldName, fType.Name) if err := parseType(rVal, fVal.Addr(), iFieldName); err != nil { return err } } return nil } func parseMap(from, to reflect.Value, fieldName string) IErrorArgs { if to.IsNil() { to.Set(reflect.MakeMap(to.Type())) } switch from.Kind() { case reflect.Map: for _, key := range from.MapKeys() { iFieldName := fmt.Sprintf("%v[%v]", fieldName, key) fromVal := from.MapIndex(key) toVal := reflect.New(to.Type().Elem()).Elem() if err := parseType(fromVal, toVal.Addr(), iFieldName); err != nil { return err } to.SetMapIndex(key, toVal) } case reflect.Struct: for i := 0; i < from.NumField(); i++ { iFieldName := fmt.Sprintf("%v.%v", fieldName, from.Type().Field(i).Name) fromVal := from.Field(i) toVal := reflect.New(to.Type().Elem()).Elem() if err := parseType(fromVal, toVal.Addr(), iFieldName); err != nil { return err } fName := camelToSnake(from.Type().Field(i).Name) to.SetMapIndex(reflect.ValueOf(fName), toVal) } default: return NewError( "FieldType", json.Map{ "field": fieldName, "expected": to.Type().String(), "received": from.Type().String(), }, ) } return nil } // Serialize преобразует словарь from в объект произвольного типа func Serialize(from json.Map, to any) IErrorArgs { elemTo := reflect.ValueOf(to).Elem() return parseType( reflect.ValueOf(&from), elemTo, "", ) }