Files
mq/handlers/format_handler.go
2025-08-06 08:25:43 +05:45

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,
}
}