diff --git a/services/project/backup.go b/services/project/backup.go index ffbc7d80..1864e162 100644 --- a/services/project/backup.go +++ b/services/project/backup.go @@ -269,292 +269,6 @@ func GetBackup(projectID int, store db.Store) (*BackupFormat, error) { return backup.format() } -func marshalValue(v reflect.Value) (interface{}, error) { - // Handle pointers - if v.Kind() == reflect.Ptr { - if v.IsNil() { - return nil, nil - } - return marshalValue(v.Elem()) - } - - // Handle structs - if v.Kind() == reflect.Struct { - typeOfV := v.Type() - result := make(map[string]interface{}) - - for i := 0; i < v.NumField(); i++ { - fieldValue := v.Field(i) - fieldType := typeOfV.Field(i) - - // Handle anonymous fields (embedded structs) - if fieldType.Anonymous { - embeddedValue, err := marshalValue(fieldValue) - if err != nil { - return nil, err - } - if embeddedMap, ok := embeddedValue.(map[string]interface{}); ok { - // Merge embedded struct fields into parent result map - for k, v := range embeddedMap { - result[k] = v - } - } - continue - } - - tag := fieldType.Tag.Get("backup") - - // Check if the field should be backed up - if tag == "-" { - continue // Skip fields with backup:"-" - } else if tag == "" { - // Get the field name from the "db" tag - tag = fieldType.Tag.Get("db") - if tag == "" || tag == "-" { - continue // Skip if "db" tag is empty or "-" - } - } - - // Recursively process the field value - value, err := marshalValue(fieldValue) - if err != nil { - return nil, err - } - - result[tag] = value - } - return result, nil - } - - // Handle slices and arrays - if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { - if v.IsNil() { - return nil, nil - } - var result []interface{} - for i := 0; i < v.Len(); i++ { - elemValue, err := marshalValue(v.Index(i)) - if err != nil { - return nil, err - } - result = append(result, elemValue) - } - return result, nil - } - - // Handle maps - if v.Kind() == reflect.Map { - if v.IsNil() { - return nil, nil - } - result := make(map[string]interface{}) - for _, key := range v.MapKeys() { - // Assuming the key is a string - mapKey := fmt.Sprintf("%v", key.Interface()) - mapValue, err := marshalValue(v.MapIndex(key)) - if err != nil { - return nil, err - } - result[mapKey] = mapValue - } - return result, nil - } - - // Handle other types (int, string, etc.) - return v.Interface(), nil -} - -// UnmarshalStruct deserializes JSON data into a struct, -// using the "db" tag for field names and excluding fields with backup:"-". -func UnmarshalStruct(data []byte, v interface{}) error { - // Parse the JSON data into an interface{} - var jsonData interface{} - if err := json.Unmarshal(data, &jsonData); err != nil { - return err - } - // Start the recursive unmarshaling process - return unmarshalValue(jsonData, reflect.ValueOf(v)) -} - -func unmarshalValue(data interface{}, v reflect.Value) error { - // Handle pointers - if v.Kind() == reflect.Ptr { - // Initialize pointer if it's nil - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - return unmarshalValue(data, v.Elem()) - } - - // Handle structs - if v.Kind() == reflect.Struct { - // Data should be a map - m, ok := data.(map[string]interface{}) - if !ok { - return fmt.Errorf("expected object for struct, got %T", data) - } - return unmarshalStruct(m, v) - } - - // Handle slices and arrays - if v.Kind() == reflect.Slice { - // Data should be an array - dataSlice, ok := data.([]interface{}) - if !ok { - return fmt.Errorf("expected array for slice, got %T", data) - } - // Create a new slice - slice := reflect.MakeSlice(v.Type(), len(dataSlice), len(dataSlice)) - for i := 0; i < len(dataSlice); i++ { - elem := slice.Index(i) - if err := unmarshalValue(dataSlice[i], elem); err != nil { - return err - } - } - v.Set(slice) - return nil - } - - // Handle maps - if v.Kind() == reflect.Map { - // Data should be a map - dataMap, ok := data.(map[string]interface{}) - if !ok { - return fmt.Errorf("expected object for map, got %T", data) - } - mapType := v.Type() - mapValue := reflect.MakeMap(mapType) - for key, value := range dataMap { - keyVal := reflect.ValueOf(key).Convert(mapType.Key()) - valVal := reflect.New(mapType.Elem()).Elem() - if err := unmarshalValue(value, valVal); err != nil { - return err - } - mapValue.SetMapIndex(keyVal, valVal) - } - v.Set(mapValue) - return nil - } - - // Handle basic types - if err := setBasicType(data, v); err != nil { - return err - } - - return nil -} - -func unmarshalStruct(data map[string]interface{}, v reflect.Value) error { - t := v.Type() - - // Build a map of db tags to field indices - fieldMap := make(map[string]int) - for i := 0; i < t.NumField(); i++ { - fieldType := t.Field(i) - - // Skip fields with backup:"-" - if backupTag := fieldType.Tag.Get("backup"); backupTag == "-" { - continue - } - - // Get the field name from the "db" tag - dbTag := fieldType.Tag.Get("db") - if dbTag == "" || dbTag == "-" { - continue - } - - fieldMap[dbTag] = i - } - - // Iterate over the JSON data and set struct fields - for key, value := range data { - if index, ok := fieldMap[key]; ok { - field := v.Field(index) - if !field.CanSet() { - continue // Skip unexportable fields - } - if err := unmarshalValue(value, field); err != nil { - return err - } - } - } - - return nil -} - -func setBasicType(data interface{}, v reflect.Value) error { - if !v.CanSet() { - return fmt.Errorf("cannot set value of type %v", v.Type()) - } - - switch v.Kind() { - case reflect.Bool: - b, ok := data.(bool) - if !ok { - return fmt.Errorf("expected bool for field, got %T", data) - } - v.SetBool(b) - case reflect.String: - s, ok := data.(string) - if !ok { - return fmt.Errorf("expected string for field, got %T", data) - } - v.SetString(s) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n, ok := toFloat64(data) - if !ok { - return fmt.Errorf("expected number for field, got %T", data) - } - v.SetInt(int64(n)) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n, ok := toFloat64(data) - if !ok { - return fmt.Errorf("expected number for field, got %T", data) - } - v.SetUint(uint64(n)) - case reflect.Float32, reflect.Float64: - n, ok := toFloat64(data) - if !ok { - return fmt.Errorf("expected number for field, got %T", data) - } - v.SetFloat(n) - default: - return fmt.Errorf("unsupported kind %v", v.Kind()) - } - return nil -} - -func toFloat64(data interface{}) (float64, bool) { - switch n := data.(type) { - case float64: - return n, true - case float32: - return float64(n), true - case int: - return float64(n), true - case int64: - return float64(n), true - case int32: - return float64(n), true - case int16: - return float64(n), true - case int8: - return float64(n), true - case uint: - return float64(n), true - case uint64: - return float64(n), true - case uint32: - return float64(n), true - case uint16: - return float64(n), true - case uint8: - return float64(n), true - default: - return 0, false - } -} - func (b *BackupFormat) Marshal() (res string, err error) { data, err := marshalValue(reflect.ValueOf(b)) if err != nil { @@ -572,7 +286,13 @@ func (b *BackupFormat) Marshal() (res string, err error) { } func (b *BackupFormat) Unmarshal(res string) (err error) { - err = UnmarshalStruct([]byte(res), reflect.ValueOf(b)) + // Parse the JSON data into a map + var jsonData interface{} + if err = json.Unmarshal([]byte(res), &jsonData); err != nil { + return + } + // Start the recursive unmarshaling process + err = unmarshalValueWithBackupTags(jsonData, reflect.ValueOf(b)) return } diff --git a/services/project/backup_marshal.go b/services/project/backup_marshal.go new file mode 100644 index 00000000..96ac485d --- /dev/null +++ b/services/project/backup_marshal.go @@ -0,0 +1,287 @@ +package project + +import ( + "fmt" + "reflect" +) + +func marshalValue(v reflect.Value) (interface{}, error) { + // Handle pointers + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil, nil + } + return marshalValue(v.Elem()) + } + + // Handle structs + if v.Kind() == reflect.Struct { + typeOfV := v.Type() + result := make(map[string]interface{}) + + for i := 0; i < v.NumField(); i++ { + fieldValue := v.Field(i) + fieldType := typeOfV.Field(i) + + // Handle anonymous fields (embedded structs) + if fieldType.Anonymous { + embeddedValue, err := marshalValue(fieldValue) + if err != nil { + return nil, err + } + if embeddedMap, ok := embeddedValue.(map[string]interface{}); ok { + // Merge embedded struct fields into parent result map + for k, v := range embeddedMap { + result[k] = v + } + } + continue + } + + tag := fieldType.Tag.Get("backup") + + // Check if the field should be backed up + if tag == "-" { + continue // Skip fields with backup:"-" + } else if tag == "" { + // Get the field name from the "db" tag + tag = fieldType.Tag.Get("db") + if tag == "" || tag == "-" { + continue // Skip if "db" tag is empty or "-" + } + } + + // Recursively process the field value + value, err := marshalValue(fieldValue) + if err != nil { + return nil, err + } + + result[tag] = value + } + return result, nil + } + + // Handle slices and arrays + if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { + if v.IsNil() { + return nil, nil + } + var result []interface{} + for i := 0; i < v.Len(); i++ { + elemValue, err := marshalValue(v.Index(i)) + if err != nil { + return nil, err + } + result = append(result, elemValue) + } + return result, nil + } + + // Handle maps + if v.Kind() == reflect.Map { + if v.IsNil() { + return nil, nil + } + result := make(map[string]interface{}) + for _, key := range v.MapKeys() { + // Assuming the key is a string + mapKey := fmt.Sprintf("%v", key.Interface()) + mapValue, err := marshalValue(v.MapIndex(key)) + if err != nil { + return nil, err + } + result[mapKey] = mapValue + } + return result, nil + } + + // Handle other types (int, string, etc.) + return v.Interface(), nil +} + +func setBasicType(data interface{}, v reflect.Value) error { + if !v.CanSet() { + return fmt.Errorf("cannot set value of type %v", v.Type()) + } + + switch v.Kind() { + case reflect.Bool: + b, ok := data.(bool) + if !ok { + return fmt.Errorf("expected bool for field, got %T", data) + } + v.SetBool(b) + case reflect.String: + s, ok := data.(string) + if !ok { + return fmt.Errorf("expected string for field, got %T", data) + } + v.SetString(s) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, ok := toFloat64(data) + if !ok { + return fmt.Errorf("expected number for field, got %T", data) + } + v.SetInt(int64(n)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + n, ok := toFloat64(data) + if !ok { + return fmt.Errorf("expected number for field, got %T", data) + } + v.SetUint(uint64(n)) + case reflect.Float32, reflect.Float64: + n, ok := toFloat64(data) + if !ok { + return fmt.Errorf("expected number for field, got %T", data) + } + v.SetFloat(n) + default: + return fmt.Errorf("unsupported kind %v", v.Kind()) + } + return nil +} + +func toFloat64(data interface{}) (float64, bool) { + switch n := data.(type) { + case float64: + return n, true + case float32: + return float64(n), true + case int: + return float64(n), true + case int64: + return float64(n), true + case int32: + return float64(n), true + case int16: + return float64(n), true + case int8: + return float64(n), true + case uint: + return float64(n), true + case uint64: + return float64(n), true + case uint32: + return float64(n), true + case uint16: + return float64(n), true + case uint8: + return float64(n), true + default: + return 0, false + } +} + +func unmarshalValueWithBackupTags(data interface{}, v reflect.Value) error { + // Handle pointers + if v.Kind() == reflect.Ptr { + // Initialize pointer if it's nil + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return unmarshalValueWithBackupTags(data, v.Elem()) + } + + // Handle structs + if v.Kind() == reflect.Struct { + // Data should be a map + m, ok := data.(map[string]interface{}) + if !ok { + return fmt.Errorf("expected object for struct, got %T", data) + } + return unmarshalStructWithBackupTags(m, v) + } + + // Handle slices and arrays + if v.Kind() == reflect.Slice { + dataSlice, ok := data.([]interface{}) + if !ok { + return fmt.Errorf("expected array for slice, got %T", data) + } + slice := reflect.MakeSlice(v.Type(), len(dataSlice), len(dataSlice)) + for i := 0; i < len(dataSlice); i++ { + elem := slice.Index(i) + if err := unmarshalValueWithBackupTags(dataSlice[i], elem); err != nil { + return err + } + } + v.Set(slice) + return nil + } + + // Handle maps + if v.Kind() == reflect.Map { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return fmt.Errorf("expected object for map, got %T", data) + } + mapType := v.Type() + mapValue := reflect.MakeMap(mapType) + for key, value := range dataMap { + keyVal := reflect.ValueOf(key).Convert(mapType.Key()) + valVal := reflect.New(mapType.Elem()).Elem() + if err := unmarshalValueWithBackupTags(value, valVal); err != nil { + return err + } + mapValue.SetMapIndex(keyVal, valVal) + } + v.Set(mapValue) + return nil + } + + // Handle basic types + return setBasicType(data, v) +} + +func unmarshalStructWithBackupTags(data map[string]interface{}, v reflect.Value) error { + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + fieldType := t.Field(i) + fieldValue := v.Field(i) + + // Handle anonymous fields (embedded structs) + if fieldType.Anonymous { + // Pass the entire data map to the embedded struct + if err := unmarshalStructWithBackupTags(data, fieldValue); err != nil { + return err + } + continue + } + + // Skip fields with backup:"-" + if backupTag := fieldType.Tag.Get("backup"); backupTag == "-" { + continue + } + + // Determine the JSON key to use + var jsonKey string + backupTag := fieldType.Tag.Get("backup") + if backupTag != "" { + jsonKey = backupTag + } else { + dbTag := fieldType.Tag.Get("db") + if dbTag != "" { + jsonKey = dbTag + } else { + continue // Skip if no backup or db tag + } + } + + // Check if the key exists in the data + if value, ok := data[jsonKey]; ok { + if !fieldValue.CanSet() { + continue // Skip fields that cannot be set + } + if value == nil { + continue + } + if err := unmarshalValueWithBackupTags(value, fieldValue); err != nil { + return err + } + } + } + + return nil +}