mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-05 16:06:55 +08:00
367 lines
10 KiB
Go
367 lines
10 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/oarkflow/json"
|
|
"github.com/oarkflow/mq"
|
|
"github.com/oarkflow/mq/dag"
|
|
)
|
|
|
|
// JSONHandler handles JSON parsing and stringification operations
|
|
type JSONHandler struct {
|
|
dag.Operation
|
|
OperationType string `json:"operation_type"` // "parse" or "stringify"
|
|
SourceField string `json:"source_field"` // field containing data to process
|
|
TargetField string `json:"target_field"` // field to store result
|
|
Options JSONOptions `json:"options"` // processing options
|
|
}
|
|
|
|
type JSONOptions struct {
|
|
Pretty bool `json:"pretty"` // pretty print JSON (stringify only)
|
|
Indent string `json:"indent"` // indentation string (stringify only)
|
|
EscapeHTML bool `json:"escape_html"` // escape HTML in JSON strings (stringify only)
|
|
ValidateOnly bool `json:"validate_only"` // only validate, don't parse (parse only)
|
|
ErrorOnInvalid bool `json:"error_on_invalid"` // return error if JSON is invalid
|
|
DefaultOnError any `json:"default_on_error"` // default value to use if parsing fails
|
|
StrictMode bool `json:"strict_mode"` // strict JSON parsing
|
|
AllowComments bool `json:"allow_comments"` // allow comments in JSON (parse only)
|
|
AllowTrailing bool `json:"allow_trailing"` // allow trailing commas (parse only)
|
|
}
|
|
|
|
func (j *JSONHandler) 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[j.SourceField]
|
|
if !exists {
|
|
return mq.Result{Error: fmt.Errorf("source field '%s' not found", j.SourceField), Ctx: ctx}
|
|
}
|
|
|
|
var result any
|
|
var err error
|
|
|
|
switch j.OperationType {
|
|
case "parse":
|
|
result, err = j.parseJSON(sourceValue)
|
|
case "stringify":
|
|
result, err = j.stringifyJSON(sourceValue)
|
|
default:
|
|
return mq.Result{Error: fmt.Errorf("unsupported operation type: %s", j.OperationType), Ctx: ctx}
|
|
}
|
|
|
|
if err != nil {
|
|
if j.Options.ErrorOnInvalid {
|
|
return mq.Result{Error: err, Ctx: ctx}
|
|
}
|
|
// Use default value if specified
|
|
if j.Options.DefaultOnError != nil {
|
|
result = j.Options.DefaultOnError
|
|
} else {
|
|
result = sourceValue // keep original value
|
|
}
|
|
}
|
|
|
|
// Set target field
|
|
targetField := j.TargetField
|
|
if targetField == "" {
|
|
targetField = j.SourceField // overwrite source if no target specified
|
|
}
|
|
data[targetField] = result
|
|
|
|
bt, _ := json.Marshal(data)
|
|
return mq.Result{Payload: bt, Ctx: ctx}
|
|
}
|
|
|
|
func (j *JSONHandler) parseJSON(value any) (any, error) {
|
|
// Convert value to string
|
|
jsonStr := fmt.Sprintf("%v", value)
|
|
|
|
// Validate only if requested
|
|
if j.Options.ValidateOnly {
|
|
var temp any
|
|
err := json.Unmarshal([]byte(jsonStr), &temp)
|
|
if err != nil {
|
|
return false, fmt.Errorf("invalid JSON: %v", err)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Preprocess if needed
|
|
if j.Options.AllowComments {
|
|
jsonStr = j.removeComments(jsonStr)
|
|
}
|
|
|
|
if j.Options.AllowTrailing {
|
|
jsonStr = j.removeTrailingCommas(jsonStr)
|
|
}
|
|
|
|
// Parse JSON
|
|
var result any
|
|
err := json.Unmarshal([]byte(jsonStr), &result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse JSON: %v", err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (j *JSONHandler) stringifyJSON(value any) (string, error) {
|
|
var result []byte
|
|
var err error
|
|
|
|
if j.Options.Pretty {
|
|
indent := j.Options.Indent
|
|
if indent == "" {
|
|
indent = " " // default indentation
|
|
}
|
|
result, err = json.MarshalIndent(value, "", indent)
|
|
} else {
|
|
result, err = json.Marshal(value)
|
|
}
|
|
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to stringify JSON: %v", err)
|
|
}
|
|
|
|
return string(result), nil
|
|
}
|
|
|
|
func (j *JSONHandler) removeComments(jsonStr string) string {
|
|
lines := strings.Split(jsonStr, "\n")
|
|
var cleanLines []string
|
|
|
|
for _, line := range lines {
|
|
// Remove single-line comments
|
|
if commentIndex := strings.Index(line, "//"); commentIndex != -1 {
|
|
line = line[:commentIndex]
|
|
}
|
|
cleanLines = append(cleanLines, line)
|
|
}
|
|
|
|
result := strings.Join(cleanLines, "\n")
|
|
|
|
// Remove multi-line comments (basic implementation)
|
|
for {
|
|
start := strings.Index(result, "/*")
|
|
if start == -1 {
|
|
break
|
|
}
|
|
end := strings.Index(result[start:], "*/")
|
|
if end == -1 {
|
|
break
|
|
}
|
|
result = result[:start] + result[start+end+2:]
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (j *JSONHandler) removeTrailingCommas(jsonStr string) string {
|
|
// Basic implementation - remove commas before closing brackets/braces
|
|
jsonStr = strings.ReplaceAll(jsonStr, ",}", "}")
|
|
jsonStr = strings.ReplaceAll(jsonStr, ",]", "]")
|
|
return jsonStr
|
|
}
|
|
|
|
// Advanced JSON handler for complex operations
|
|
type AdvancedJSONHandler struct {
|
|
dag.Operation
|
|
Operations []JSONOperation `json:"operations"` // chain of JSON operations
|
|
}
|
|
|
|
type JSONOperation struct {
|
|
Type string `json:"type"` // "parse", "stringify", "validate", "extract", "merge"
|
|
SourceField string `json:"source_field"` // field to operate on
|
|
TargetField string `json:"target_field"` // field to store result
|
|
Options JSONOptions `json:"options"` // operation options
|
|
Path string `json:"path"` // JSON path for extraction (extract only)
|
|
MergeWith string `json:"merge_with"` // field to merge with (merge only)
|
|
}
|
|
|
|
func (a *AdvancedJSONHandler) 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}
|
|
}
|
|
|
|
// Execute operations in sequence
|
|
for i, op := range a.Operations {
|
|
var result any
|
|
var err error
|
|
|
|
switch op.Type {
|
|
case "parse", "stringify":
|
|
handler := &JSONHandler{
|
|
OperationType: op.Type,
|
|
SourceField: op.SourceField,
|
|
TargetField: op.TargetField,
|
|
Options: op.Options,
|
|
}
|
|
|
|
tempData, _ := json.Marshal(data)
|
|
tempTask := &mq.Task{Payload: tempData}
|
|
|
|
handlerResult := handler.ProcessTask(ctx, tempTask)
|
|
if handlerResult.Error != nil {
|
|
return mq.Result{Error: fmt.Errorf("operation %d failed: %v", i+1, handlerResult.Error), Ctx: ctx}
|
|
}
|
|
|
|
if err := json.Unmarshal(handlerResult.Payload, &data); err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to unmarshal result from operation %d: %v", i+1, err), Ctx: ctx}
|
|
}
|
|
continue
|
|
|
|
case "validate":
|
|
result, err = a.validateJSON(data[op.SourceField])
|
|
case "extract":
|
|
result, err = a.extractFromJSON(data[op.SourceField], op.Path)
|
|
case "merge":
|
|
result, err = a.mergeJSON(data[op.SourceField], data[op.MergeWith])
|
|
default:
|
|
return mq.Result{Error: fmt.Errorf("unsupported operation type: %s", op.Type), Ctx: ctx}
|
|
}
|
|
|
|
if err != nil {
|
|
if op.Options.ErrorOnInvalid {
|
|
return mq.Result{Error: fmt.Errorf("operation %d failed: %v", i+1, err), Ctx: ctx}
|
|
}
|
|
result = op.Options.DefaultOnError
|
|
}
|
|
|
|
// Set target field
|
|
targetField := op.TargetField
|
|
if targetField == "" {
|
|
targetField = op.SourceField
|
|
}
|
|
data[targetField] = result
|
|
}
|
|
|
|
bt, _ := json.Marshal(data)
|
|
return mq.Result{Payload: bt, Ctx: ctx}
|
|
}
|
|
|
|
func (a *AdvancedJSONHandler) validateJSON(value any) (bool, error) {
|
|
jsonStr := fmt.Sprintf("%v", value)
|
|
var temp any
|
|
err := json.Unmarshal([]byte(jsonStr), &temp)
|
|
return err == nil, err
|
|
}
|
|
|
|
func (a *AdvancedJSONHandler) extractFromJSON(value any, path string) (any, error) {
|
|
// Basic JSON path extraction (simplified implementation)
|
|
// For production use, consider using a proper JSON path library
|
|
|
|
var jsonData any
|
|
if str, ok := value.(string); ok {
|
|
if err := json.Unmarshal([]byte(str), &jsonData); err != nil {
|
|
return nil, fmt.Errorf("invalid JSON: %v", err)
|
|
}
|
|
} else {
|
|
jsonData = value
|
|
}
|
|
|
|
// Split path and navigate
|
|
parts := strings.Split(strings.Trim(path, "."), ".")
|
|
current := jsonData
|
|
|
|
for _, part := range parts {
|
|
if part == "" {
|
|
continue
|
|
}
|
|
|
|
switch v := current.(type) {
|
|
case map[string]any:
|
|
current = v[part]
|
|
default:
|
|
return nil, fmt.Errorf("cannot navigate path '%s' at part '%s'", path, part)
|
|
}
|
|
}
|
|
|
|
return current, nil
|
|
}
|
|
|
|
func (a *AdvancedJSONHandler) mergeJSON(value1, value2 any) (any, error) {
|
|
// Convert both values to maps if they're JSON strings
|
|
var map1, map2 map[string]any
|
|
|
|
if str, ok := value1.(string); ok {
|
|
if err := json.Unmarshal([]byte(str), &map1); err != nil {
|
|
return nil, fmt.Errorf("invalid JSON in first value: %v", err)
|
|
}
|
|
} else if m, ok := value1.(map[string]any); ok {
|
|
map1 = m
|
|
} else {
|
|
return nil, fmt.Errorf("first value is not a JSON object")
|
|
}
|
|
|
|
if str, ok := value2.(string); ok {
|
|
if err := json.Unmarshal([]byte(str), &map2); err != nil {
|
|
return nil, fmt.Errorf("invalid JSON in second value: %v", err)
|
|
}
|
|
} else if m, ok := value2.(map[string]any); ok {
|
|
map2 = m
|
|
} else {
|
|
return nil, fmt.Errorf("second value is not a JSON object")
|
|
}
|
|
|
|
// Merge maps
|
|
result := make(map[string]any)
|
|
for k, v := range map1 {
|
|
result[k] = v
|
|
}
|
|
for k, v := range map2 {
|
|
result[k] = v // overwrites if key exists
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Factory functions
|
|
func NewJSONParser(id, sourceField, targetField string, options JSONOptions) *JSONHandler {
|
|
return &JSONHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "json_parse",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "json", "parse"},
|
|
},
|
|
OperationType: "parse",
|
|
SourceField: sourceField,
|
|
TargetField: targetField,
|
|
Options: options,
|
|
}
|
|
}
|
|
|
|
func NewJSONStringifier(id, sourceField, targetField string, options JSONOptions) *JSONHandler {
|
|
return &JSONHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "json_stringify",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "json", "stringify"},
|
|
},
|
|
OperationType: "stringify",
|
|
SourceField: sourceField,
|
|
TargetField: targetField,
|
|
Options: options,
|
|
}
|
|
}
|
|
|
|
func NewAdvancedJSONHandler(id string, operations []JSONOperation) *AdvancedJSONHandler {
|
|
return &AdvancedJSONHandler{
|
|
Operation: dag.Operation{
|
|
ID: id,
|
|
Key: "advanced_json",
|
|
Type: dag.Function,
|
|
Tags: []string{"data", "json", "advanced"},
|
|
},
|
|
Operations: operations,
|
|
}
|
|
}
|