241 lines
6.4 KiB
Go
241 lines
6.4 KiB
Go
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)
|
|
}
|