package rest import ( "context" "database/sql" "fmt" "git.nobla.cn/golang/rest/types" "gorm.io/gorm" "reflect" "strconv" "sync" "time" ) var ( DefaultFormatter = NewFormatter() DefaultNullDisplay = "" ) func init() { DefaultFormatter.Register("string", stringFormat) DefaultFormatter.Register("integer", integerFormat) DefaultFormatter.Register("decimal", decimalFormat) DefaultFormatter.Register("date", dateFormat) DefaultFormatter.Register("time", timeFormat) DefaultFormatter.Register("datetime", datetimeFormat) DefaultFormatter.Register("duration", durationFormat) DefaultFormatter.Register("dropdown", dropdownFormat) DefaultFormatter.Register("timestamp", datetimeFormat) DefaultFormatter.Register("percentage", percentageFormat) } type FormatFunc func(ctx context.Context, value any, model any, scm *types.Schema) any type Formatter struct { callbacks sync.Map } func (formatter *Formatter) Register(f string, fun FormatFunc) { formatter.callbacks.Store(f, fun) } func (formatter *Formatter) Format(ctx context.Context, format string, value any, model any, scm *types.Schema) any { v, ok := formatter.callbacks.Load(format) if ok { return v.(FormatFunc)(ctx, value, model, scm) } return value } func (formatter *Formatter) getModelValue(refValue reflect.Value, schema *types.Schema, stmt *gorm.Statement) any { if stmt.Schema == nil { return nil } field := stmt.Schema.LookUpField(schema.Column) if field == nil { return nil } return refValue.FieldByName(field.Name).Interface() } func (formatter *Formatter) formatModel(ctx context.Context, refValue reflect.Value, schemas []*types.Schema, stmt *gorm.Statement, format string) any { values := make(map[string]any) multiValues := make(map[string]multiValue) modelValue := refValue.Interface() refValue = reflect.Indirect(refValue) for _, scm := range schemas { switch format { case types.FormatRaw: values[scm.Column] = formatter.getModelValue(refValue, scm, stmt) case types.FormatBoth: v := multiValue{ Value: formatter.getModelValue(refValue, scm, stmt), } v.Text = formatter.Format(ctx, scm.Format, v.Value, modelValue, scm) multiValues[scm.Column] = v default: values[scm.Column] = formatter.Format(ctx, scm.Format, formatter.getModelValue(refValue, scm, stmt), modelValue, scm) } } if format == types.FormatBoth { return multiValues } else { return values } } func (formatter *Formatter) formatModels(ctx context.Context, val any, schemas []*types.Schema, stmt *gorm.Statement, format string) any { refValue := reflect.Indirect(reflect.ValueOf(val)) if refValue.Kind() != reflect.Slice { return []any{} } length := refValue.Len() values := make([]any, length) for i := 0; i < length; i++ { rowValue := refValue.Index(i) modelValue := rowValue.Interface() if formatModel, ok := modelValue.(types.FormatModel); ok { formatModel.Format(ctx, format, schemas) } values[i] = formatter.formatModel(ctx, rowValue, schemas, stmt, format) } return values } func stringFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { return fmt.Sprint(value) } func integerFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { var ( n int ) switch value.(type) { case float32, float64: n = int(reflect.ValueOf(value).Float()) case int, int8, int16, int32, int64: n = int(reflect.ValueOf(value).Int()) case uint, uint8, uint16, uint32, uint64: n = int(reflect.ValueOf(value).Uint()) case string: n, _ = strconv.Atoi(reflect.ValueOf(value).String()) case []byte: n, _ = strconv.Atoi(string(reflect.ValueOf(value).Bytes())) } return n } func decimalFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { var ( n float64 ) switch value.(type) { case float32, float64: n = reflect.ValueOf(value).Float() case int, int8, int16, int32, int64: n = float64(reflect.ValueOf(value).Int()) case uint, uint8, uint16, uint32, uint64: n = float64(reflect.ValueOf(value).Uint()) case string: n, _ = strconv.ParseFloat(reflect.ValueOf(value).String(), 64) case []byte: n, _ = strconv.ParseFloat(string(reflect.ValueOf(value).Bytes()), 64) } return n } func dateFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { if t, ok := value.(time.Time); ok { return t.Format("2006-01-02") } if t, ok := value.(*sql.NullTime); ok { if t != nil && t.Valid { return t.Time.Format("2006-01-02") } } if t, ok := value.(int64); ok { tm := time.Unix(t, 0) return tm.Format("2006-01-02") } return DefaultNullDisplay } func timeFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { if t, ok := value.(time.Time); ok { return t.Format("15:04:05") } if t, ok := value.(*sql.NullTime); ok { if t != nil && t.Valid { return t.Time.Format("15:04:05") } } if t, ok := value.(int64); ok { tm := time.Unix(t, 0) return tm.Format("15:04:05") } return value } func datetimeFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { if t, ok := value.(time.Time); ok { return t.Format("2006-01-02 15:04:05") } if t, ok := value.(*sql.NullTime); ok { if t != nil && t.Valid { return t.Time.Format("2006-01-02 15:04:05") } } if t, ok := value.(int64); ok { if t > 0 { tm := time.Unix(t, 0) return tm.Format("2006-01-02 15:04:05") } } return DefaultNullDisplay } func percentageFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { n := decimalFormat(ctx, value, model, schema).(float64) if n <= 1 { return fmt.Sprintf("%.2f%%", n*100) } else { return fmt.Sprintf("%.2f%%", n) } } func durationFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { var ( hour int minVal int sec int ) n := integerFormat(ctx, value, model, schema).(int) hour = n / 3600 minVal = (n - hour*3600) / 60 sec = n - hour*3600 - minVal*60 return fmt.Sprintf("%02d:%02d:%02d", hour, minVal, sec) } func dropdownFormat(ctx context.Context, value interface{}, model any, schema *types.Schema) interface{} { attributes := schema.Attribute if attributes.Values != nil { for _, v := range attributes.Values { if v.Value == value { return v.Label } } } return value } func NewFormatter() *Formatter { formatter := &Formatter{} return formatter } func RegisterFormat(f string, cb FormatFunc) { DefaultFormatter.Register(f, cb) }