mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-05 16:06:55 +08:00
315 lines
7.5 KiB
Go
315 lines
7.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/oarkflow/json"
|
|
"github.com/oarkflow/mq"
|
|
"github.com/oarkflow/mq/dag"
|
|
)
|
|
|
|
// FormatHandler handles various data formatting operations
|
|
type FormatHandler struct {
|
|
dag.Operation
|
|
FormatType string `json:"format_type"` // date, number, string, currency, etc.
|
|
SourceField string `json:"source_field"` // field to format
|
|
TargetField string `json:"target_field"` // field to store formatted result
|
|
FormatConfig map[string]string `json:"format_config"` // format-specific configuration
|
|
}
|
|
|
|
func (f *FormatHandler) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
var data map[string]any
|
|
if err := json.Unmarshal(task.Payload, &data); err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to unmarshal data: %v", err), Ctx: ctx}
|
|
}
|
|
|
|
// Get source value
|
|
sourceValue, exists := data[f.SourceField]
|
|
if !exists {
|
|
return mq.Result{Error: fmt.Errorf("source field '%s' not found", f.SourceField), Ctx: ctx}
|
|
}
|
|
|
|
// Format based on type
|
|
var formattedValue any
|
|
var err error
|
|
|
|
switch f.FormatType {
|
|
case "date":
|
|
formattedValue, err = f.formatDate(sourceValue)
|
|
case "number":
|
|
formattedValue, err = f.formatNumber(sourceValue)
|
|
case "currency":
|
|
formattedValue, err = f.formatCurrency(sourceValue)
|
|
case "string":
|
|
formattedValue, err = f.formatString(sourceValue)
|
|
case "boolean":
|
|
formattedValue, err = f.formatBoolean(sourceValue)
|
|
case "array":
|
|
formattedValue, err = f.formatArray(sourceValue)
|
|
default:
|
|
return mq.Result{Error: fmt.Errorf("unsupported format type: %s", f.FormatType), Ctx: ctx}
|
|
}
|
|
|
|
if err != nil {
|
|
return mq.Result{Error: err, Ctx: ctx}
|
|
}
|
|
|
|
// Set target field
|
|
targetField := f.TargetField
|
|
if targetField == "" {
|
|
targetField = f.SourceField // overwrite source if no target specified
|
|
}
|
|
data[targetField] = formattedValue
|
|
|
|
bt, _ := json.Marshal(data)
|
|
return mq.Result{Payload: bt, Ctx: ctx}
|
|
}
|
|
|
|
func (f *FormatHandler) formatDate(value any) (string, error) {
|
|
var t time.Time
|
|
var err error
|
|
|
|
switch v := value.(type) {
|
|
case string:
|
|
// Try parsing various date formats
|
|
formats := []string{
|
|
time.RFC3339,
|
|
"2006-01-02 15:04:05",
|
|
"2006-01-02",
|
|
"01/02/2006",
|
|
"02-01-2006",
|
|
"2006/01/02",
|
|
}
|
|
|
|
for _, format := range formats {
|
|
if t, err = time.Parse(format, v); err == nil {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to parse date string: %s", v)
|
|
}
|
|
case time.Time:
|
|
t = v
|
|
case int64:
|
|
t = time.Unix(v, 0)
|
|
case float64:
|
|
t = time.Unix(int64(v), 0)
|
|
default:
|
|
return "", fmt.Errorf("unsupported date type: %T", value)
|
|
}
|
|
|
|
// Get output format from config
|
|
outputFormat := f.FormatConfig["output_format"]
|
|
if outputFormat == "" {
|
|
outputFormat = "2006-01-02 15:04:05" // default format
|
|
}
|
|
|
|
return t.Format(outputFormat), nil
|
|
}
|
|
|
|
func (f *FormatHandler) formatNumber(value any) (string, error) {
|
|
var num float64
|
|
var err error
|
|
|
|
switch v := value.(type) {
|
|
case string:
|
|
num, err = strconv.ParseFloat(v, 64)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to parse number string: %s", v)
|
|
}
|
|
case int:
|
|
num = float64(v)
|
|
case int32:
|
|
num = float64(v)
|
|
case int64:
|
|
num = float64(v)
|
|
case float32:
|
|
num = float64(v)
|
|
case float64:
|
|
num = v
|
|
default:
|
|
return "", fmt.Errorf("unsupported number type: %T", value)
|
|
}
|
|
|
|
// Get precision from config
|
|
precision := 2
|
|
if p, exists := f.FormatConfig["precision"]; exists {
|
|
if parsed, err := strconv.Atoi(p); err == nil {
|
|
precision = parsed
|
|
}
|
|
}
|
|
|
|
// Get format style
|
|
style := f.FormatConfig["style"]
|
|
switch style {
|
|
case "scientific":
|
|
return fmt.Sprintf("%e", num), nil
|
|
case "percentage":
|
|
return fmt.Sprintf("%."+strconv.Itoa(precision)+"f%%", num*100), nil
|
|
default:
|
|
return fmt.Sprintf("%."+strconv.Itoa(precision)+"f", num), nil
|
|
}
|
|
}
|
|
|
|
func (f *FormatHandler) formatCurrency(value any) (string, error) {
|
|
num, err := f.formatNumber(value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
symbol := f.FormatConfig["symbol"]
|
|
if symbol == "" {
|
|
symbol = "$" // default currency symbol
|
|
}
|
|
|
|
position := f.FormatConfig["position"]
|
|
if position == "suffix" {
|
|
return num + " " + symbol, nil
|
|
}
|
|
return symbol + num, nil
|
|
}
|
|
|
|
func (f *FormatHandler) formatString(value any) (string, error) {
|
|
str := fmt.Sprintf("%v", value)
|
|
|
|
operation := f.FormatConfig["operation"]
|
|
switch operation {
|
|
case "uppercase":
|
|
return strings.ToUpper(str), nil
|
|
case "lowercase":
|
|
return strings.ToLower(str), nil
|
|
case "title":
|
|
return strings.Title(str), nil
|
|
case "trim":
|
|
return strings.TrimSpace(str), nil
|
|
case "truncate":
|
|
if lengthStr, exists := f.FormatConfig["length"]; exists {
|
|
if length, err := strconv.Atoi(lengthStr); err == nil && len(str) > length {
|
|
return str[:length] + "...", nil
|
|
}
|
|
}
|
|
return str, nil
|
|
default:
|
|
return str, nil
|
|
}
|
|
}
|
|
|
|
func (f *FormatHandler) formatBoolean(value any) (string, error) {
|
|
var boolVal bool
|
|
|
|
switch v := value.(type) {
|
|
case bool:
|
|
boolVal = v
|
|
case string:
|
|
lower := strings.ToLower(v)
|
|
boolVal = lower == "true" || lower == "yes" || lower == "1" || lower == "on"
|
|
case int, int32, int64:
|
|
boolVal = reflect.ValueOf(v).Int() != 0
|
|
case float32, float64:
|
|
boolVal = reflect.ValueOf(v).Float() != 0
|
|
default:
|
|
return "", fmt.Errorf("unsupported boolean type: %T", value)
|
|
}
|
|
|
|
trueValue := f.FormatConfig["true_value"]
|
|
falseValue := f.FormatConfig["false_value"]
|
|
|
|
if trueValue == "" {
|
|
trueValue = "true"
|
|
}
|
|
if falseValue == "" {
|
|
falseValue = "false"
|
|
}
|
|
|
|
if boolVal {
|
|
return trueValue, nil
|
|
}
|
|
return falseValue, nil
|
|
}
|
|
|
|
func (f *FormatHandler) formatArray(value any) (string, error) {
|
|
rv := reflect.ValueOf(value)
|
|
if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
|
|
return "", fmt.Errorf("value is not an array or slice")
|
|
}
|
|
|
|
separator := f.FormatConfig["separator"]
|
|
if separator == "" {
|
|
separator = ", "
|
|
}
|
|
|
|
var elements []string
|
|
for i := 0; i < rv.Len(); i++ {
|
|
elements = append(elements, fmt.Sprintf("%v", rv.Index(i).Interface()))
|
|
}
|
|
|
|
return strings.Join(elements, separator), nil
|
|
}
|
|
|
|
// Factory functions for different format types
|
|
func NewDateFormatter(id, sourceField, targetField string, config map[string]string) *FormatHandler {
|
|
return &FormatHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "format_date",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "format", "date"},
|
|
},
|
|
FormatType: "date",
|
|
SourceField: sourceField,
|
|
TargetField: targetField,
|
|
FormatConfig: config,
|
|
}
|
|
}
|
|
|
|
func NewNumberFormatter(id, sourceField, targetField string, config map[string]string) *FormatHandler {
|
|
return &FormatHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "format_number",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "format", "number"},
|
|
},
|
|
FormatType: "number",
|
|
SourceField: sourceField,
|
|
TargetField: targetField,
|
|
FormatConfig: config,
|
|
}
|
|
}
|
|
|
|
func NewCurrencyFormatter(id, sourceField, targetField string, config map[string]string) *FormatHandler {
|
|
return &FormatHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "format_currency",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "format", "currency"},
|
|
},
|
|
FormatType: "currency",
|
|
SourceField: sourceField,
|
|
TargetField: targetField,
|
|
FormatConfig: config,
|
|
}
|
|
}
|
|
|
|
func NewStringFormatter(id, sourceField, targetField string, config map[string]string) *FormatHandler {
|
|
return &FormatHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "format_string",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "format", "string"},
|
|
},
|
|
FormatType: "string",
|
|
SourceField: sourceField,
|
|
TargetField: targetField,
|
|
FormatConfig: config,
|
|
}
|
|
}
|