package rest import ( "context" "database/sql" "fmt" "gorm.io/gorm" "reflect" "strconv" "sync" "time" ) var ( DefaultFormatter = NewFormatter() DefaultNullDisplay = "" ) type FormatFunc func(ctx context.Context, value interface{}, model interface{}, scm *Schema) interface{} 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 interface{}, model interface{}, scm *Schema) interface{} { v, ok := formatter.callbacks.Load(format) if ok { return v.(FormatFunc)(ctx, value, model, scm) } return value } func (formatter *Formatter) formatModel(ctx context.Context, val interface{}, schemas []*Schema, stmt *gorm.Statement) interface{} { values := make(map[string]interface{}) refVal := reflect.Indirect(reflect.ValueOf(val)) for _, field := range schemas { f := stmt.Schema.LookUpField(field.Column) values[field.Column] = formatter.Format(ctx, field.Format, refVal.FieldByName(f.Name).Interface(), val, field) } return values } func (formatter *Formatter) formatModels(ctx context.Context, val interface{}, schemas []*Schema, stmt *gorm.Statement) interface{} { reflectValue := reflect.Indirect(reflect.ValueOf(val)) if reflectValue.Type().Kind() != reflect.Slice { return nil } length := reflectValue.Len() values := make([]interface{}, length) for i := 0; i < length; i++ { values[i] = formatter.formatModel(ctx, reflectValue.Index(i).Interface(), schemas, stmt) } return values } func stringFormat(ctx context.Context, value interface{}, model interface{}, scm *Schema) interface{} { return fmt.Sprint(value) } func integerFormat(ctx context.Context, value interface{}, model interface{}, scm *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 interface{}, scm *Schema) interface{} { var ( n float64 ) switch value.(type) { case float32, float64: n = float64(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 interface{}, scm *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 interface{}, scm *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 DefaultNullDisplay } func datetimeFormat(ctx context.Context, value interface{}, model interface{}, scm *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 durationFormat(ctx context.Context, value interface{}, model interface{}, scm *Schema) interface{} { var ( hour int min int sec int ) n := integerFormat(ctx, value, model, scm).(int) hour = n / 3600 min = (n - hour*3600) / 60 sec = n - hour*3600 - min*60 return fmt.Sprintf("%02d:%02d:%02d", hour, min, sec) } func dropdownFormat(ctx context.Context, value interface{}, model interface{}, scm *Schema) interface{} { attributes := scm.getProperties() if attributes != nil && attributes.Values != nil { for _, v := range attributes.Values { if v.Value == value { return v.Label } } } return value } func NewFormatter() *Formatter { formatter := &Formatter{} formatter.Register("string", stringFormat) formatter.Register("integer", integerFormat) formatter.Register("decimal", decimalFormat) formatter.Register("date", dateFormat) formatter.Register("time", timeFormat) formatter.Register("datetime", datetimeFormat) formatter.Register("duration", durationFormat) formatter.Register("dropdown", dropdownFormat) return formatter }