This commit is contained in:
sujit
2025-08-06 09:14:55 +05:45
parent 2eb1918b5e
commit 29d09d2d11
16 changed files with 3204 additions and 3285 deletions

View File

@@ -3,7 +3,6 @@ package handlers
import (
"context"
"fmt"
"reflect"
"strconv"
"strings"
"time"
@@ -13,302 +12,249 @@ import (
"github.com/oarkflow/mq/dag"
)
// FormatHandler handles various data formatting operations
// FormatHandler handles 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 {
func (h *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}
err := json.Unmarshal(task.Payload, &data)
if err != nil {
return mq.Result{Error: fmt.Errorf("failed to unmarshal task payload: %w", err)}
}
// 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}
formatType, ok := h.Payload.Data["format_type"].(string)
if !ok {
return mq.Result{Error: fmt.Errorf("format_type not specified")}
}
// 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)
var result map[string]any
switch formatType {
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 {
result = h.formatToString(data)
case "number":
result = h.formatToNumber(data)
case "date":
result = h.formatDate(data)
case "currency":
result = h.formatCurrency(data)
case "uppercase":
return strings.ToUpper(str), nil
result = h.formatUppercase(data)
case "lowercase":
return strings.ToLower(str), nil
case "title":
return strings.Title(str), nil
result = h.formatLowercase(data)
case "capitalize":
result = h.formatCapitalize(data)
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
result = h.formatTrim(data)
default:
return mq.Result{Error: fmt.Errorf("unsupported format_type: %s", formatType)}
}
resultPayload, err := json.Marshal(result)
if err != nil {
return mq.Result{Error: fmt.Errorf("failed to marshal result: %w", err)}
}
return mq.Result{Payload: resultPayload, Ctx: ctx}
}
func (h *FormatHandler) formatToString(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
result[key] = fmt.Sprintf("%v", value)
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatToNumber(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if str, ok := value.(string); ok {
if num, err := strconv.ParseFloat(str, 64); err == nil {
result[key] = num
} else {
result[key] = value // Keep original if conversion fails
}
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatDate(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
dateFormat := h.getDateFormat()
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if str, ok := value.(string); ok {
if t, err := time.Parse(time.RFC3339, str); err == nil {
result[key] = t.Format(dateFormat)
} else if t, err := time.Parse("2006-01-02", str); err == nil {
result[key] = t.Format(dateFormat)
} else {
result[key] = value // Keep original if parsing fails
}
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatCurrency(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
currency := h.getCurrency()
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if num, ok := value.(float64); ok {
result[key] = fmt.Sprintf("%s%.2f", currency, num)
} else if str, ok := value.(string); ok {
if num, err := strconv.ParseFloat(str, 64); err == nil {
result[key] = fmt.Sprintf("%s%.2f", currency, num)
} else {
result[key] = value
}
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatUppercase(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if str, ok := value.(string); ok {
result[key] = strings.ToUpper(str)
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatLowercase(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if str, ok := value.(string); ok {
result[key] = strings.ToLower(str)
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatCapitalize(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if str, ok := value.(string); ok {
result[key] = strings.Title(strings.ToLower(str))
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) formatTrim(data map[string]any) map[string]any {
result := make(map[string]any)
fields := h.getTargetFields(data)
for key, value := range data {
if len(fields) == 0 || contains(fields, key) {
if str, ok := value.(string); ok {
result[key] = strings.TrimSpace(str)
} else {
result[key] = value
}
} else {
result[key] = value
}
}
return result
}
func (h *FormatHandler) getTargetFields(data map[string]any) []string {
if fields, ok := h.Payload.Data["fields"].([]interface{}); ok {
var result []string
for _, field := range fields {
if str, ok := field.(string); ok {
result = append(result, str)
}
}
return str, nil
default:
return str, nil
return result
}
return 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)
func (h *FormatHandler) getDateFormat() string {
if format, ok := h.Payload.Data["date_format"].(string); ok {
return format
}
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
return "2006-01-02" // Default date format
}
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")
func (h *FormatHandler) getCurrency() string {
if currency, ok := h.Payload.Data["currency"].(string); ok {
return currency
}
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
return "$" // Default currency symbol
}
// Factory functions for different format types
func NewDateFormatter(id, sourceField, targetField string, config map[string]string) *FormatHandler {
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
func NewFormatHandler(id 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,
Operation: dag.Operation{ID: id, Key: "format", Type: dag.Function, Tags: []string{"data", "transformation"}},
}
}