rest/formatter.go

241 lines
6.4 KiB
Go
Raw Normal View History

2024-12-11 17:29:01 +08:00
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)
}