Files
mq/workflow/processors.go
2025-09-18 07:42:17 +05:45

394 lines
11 KiB
Go

package workflow
import (
"context"
"fmt"
"log"
"strings"
"time"
)
// ProcessorFactory creates processor instances for different node types
type ProcessorFactory struct {
processors map[string]func() Processor
}
// NewProcessorFactory creates a new processor factory with all registered processors
func NewProcessorFactory() *ProcessorFactory {
factory := &ProcessorFactory{
processors: make(map[string]func() Processor),
}
// Register basic processors
factory.RegisterProcessor("task", func() Processor { return &TaskProcessor{} })
factory.RegisterProcessor("api", func() Processor { return &APIProcessor{} })
factory.RegisterProcessor("transform", func() Processor { return &TransformProcessor{} })
factory.RegisterProcessor("decision", func() Processor { return &DecisionProcessor{} })
factory.RegisterProcessor("timer", func() Processor { return &TimerProcessor{} })
factory.RegisterProcessor("parallel", func() Processor { return &ParallelProcessor{} })
factory.RegisterProcessor("sequence", func() Processor { return &SequenceProcessor{} })
factory.RegisterProcessor("loop", func() Processor { return &LoopProcessor{} })
factory.RegisterProcessor("filter", func() Processor { return &FilterProcessor{} })
factory.RegisterProcessor("aggregator", func() Processor { return &AggregatorProcessor{} })
factory.RegisterProcessor("error", func() Processor { return &ErrorProcessor{} })
// Register advanced processors
factory.RegisterProcessor("subdag", func() Processor { return &SubDAGProcessor{} })
factory.RegisterProcessor("html", func() Processor { return &HTMLProcessor{} })
factory.RegisterProcessor("sms", func() Processor { return &SMSProcessor{} })
factory.RegisterProcessor("auth", func() Processor { return &AuthProcessor{} })
factory.RegisterProcessor("validator", func() Processor { return &ValidatorProcessor{} })
factory.RegisterProcessor("router", func() Processor { return &RouterProcessor{} })
factory.RegisterProcessor("storage", func() Processor { return &StorageProcessor{} })
factory.RegisterProcessor("notify", func() Processor { return &NotifyProcessor{} })
factory.RegisterProcessor("webhook_receiver", func() Processor { return &WebhookReceiverProcessor{} })
return factory
}
// RegisterProcessor registers a new processor type
func (f *ProcessorFactory) RegisterProcessor(nodeType string, creator func() Processor) {
f.processors[nodeType] = creator
}
// CreateProcessor creates a processor instance for the given node type
func (f *ProcessorFactory) CreateProcessor(nodeType string) (Processor, error) {
creator, exists := f.processors[nodeType]
if !exists {
return nil, fmt.Errorf("unknown processor type: %s", nodeType)
}
return creator(), nil
}
// Basic Processors
// TaskProcessor handles task execution
type TaskProcessor struct{}
func (p *TaskProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
log.Printf("Executing task: %s", input.Node.Name)
// Execute the task based on configuration
config := input.Node.Config
// Simulate task execution based on script or command
if config.Script != "" {
log.Printf("Executing script: %s", config.Script)
} else if config.Command != "" {
log.Printf("Executing command: %s", config.Command)
}
time.Sleep(100 * time.Millisecond)
result := &ProcessingResult{
Success: true,
Data: map[string]interface{}{"task_completed": true, "task_name": input.Node.Name},
Message: fmt.Sprintf("Task %s completed successfully", input.Node.Name),
}
return result, nil
}
// APIProcessor handles API calls
type APIProcessor struct{}
func (p *APIProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
url := config.URL
if url == "" {
return &ProcessingResult{
Success: false,
Error: "URL not specified in API configuration",
}, nil
}
method := "GET"
if config.Method != "" {
method = strings.ToUpper(config.Method)
}
log.Printf("Making %s request to %s", method, url)
// Simulate API call
time.Sleep(200 * time.Millisecond)
// Mock response
response := map[string]interface{}{
"status": "success",
"url": url,
"method": method,
"data": "mock response data",
}
return &ProcessingResult{
Success: true,
Data: response,
Message: fmt.Sprintf("API call to %s completed", url),
}, nil
}
// TransformProcessor handles data transformation
type TransformProcessor struct{}
func (p *TransformProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
// Get transformation rules from Custom config
transforms, ok := config.Custom["transforms"].(map[string]interface{})
if !ok {
return &ProcessingResult{
Success: false,
Error: "No transformation rules specified",
}, nil
}
// Apply transformations to input data
result := make(map[string]interface{})
for key, rule := range transforms {
// Simple field mapping for now
if sourceField, ok := rule.(string); ok {
if value, exists := input.Data[sourceField]; exists {
result[key] = value
}
}
}
return &ProcessingResult{
Success: true,
Data: result,
Message: "Data transformation completed",
}, nil
}
// DecisionProcessor handles conditional logic
type DecisionProcessor struct{}
func (p *DecisionProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
condition := config.Condition
if condition == "" {
return &ProcessingResult{
Success: false,
Error: "No condition specified",
}, nil
}
// Simple condition evaluation
decision := p.evaluateCondition(condition, input.Data)
result := &ProcessingResult{
Success: true,
Data: map[string]interface{}{
"decision": decision,
"condition": condition,
},
Message: fmt.Sprintf("Decision made: %t", decision),
}
return result, nil
}
func (p *DecisionProcessor) evaluateCondition(condition string, data map[string]interface{}) bool {
// Simple condition evaluation - in real implementation, use expression parser
if strings.Contains(condition, "==") {
parts := strings.Split(condition, "==")
if len(parts) == 2 {
field := strings.TrimSpace(parts[0])
expectedValue := strings.TrimSpace(strings.Trim(parts[1], "\"'"))
if value, exists := data[field]; exists {
return fmt.Sprintf("%v", value) == expectedValue
}
}
}
// Default to true for simplicity
return true
}
// TimerProcessor handles time-based operations
type TimerProcessor struct{}
func (p *TimerProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
duration := 1 * time.Second
if config.Duration > 0 {
duration = config.Duration
} else if config.Schedule != "" {
// Simple schedule parsing - just use 1 second for demo
duration = 1 * time.Second
}
log.Printf("Timer waiting for %v", duration)
select {
case <-ctx.Done():
return &ProcessingResult{
Success: false,
Error: "Timer cancelled",
}, ctx.Err()
case <-time.After(duration):
return &ProcessingResult{
Success: true,
Data: map[string]interface{}{"waited": duration.String()},
Message: fmt.Sprintf("Timer completed after %v", duration),
}, nil
}
}
// ParallelProcessor handles parallel execution
type ParallelProcessor struct{}
func (p *ParallelProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
// This would typically trigger parallel execution of child nodes
// For now, just return success
return &ProcessingResult{
Success: true,
Data: map[string]interface{}{"parallel_execution": "started"},
Message: "Parallel execution initiated",
}, nil
}
// SequenceProcessor handles sequential execution
type SequenceProcessor struct{}
func (p *SequenceProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
// This would typically ensure sequential execution of child nodes
// For now, just return success
return &ProcessingResult{
Success: true,
Data: map[string]interface{}{"sequence_execution": "started"},
Message: "Sequential execution initiated",
}, nil
}
// LoopProcessor handles loop operations
type LoopProcessor struct{}
func (p *LoopProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
iterations := 1
if iterValue, ok := config.Custom["iterations"].(float64); ok {
iterations = int(iterValue)
}
results := make([]interface{}, 0, iterations)
for i := 0; i < iterations; i++ {
// In real implementation, this would execute child nodes
results = append(results, map[string]interface{}{
"iteration": i + 1,
"data": input.Data,
})
}
return &ProcessingResult{
Success: true,
Data: map[string]interface{}{"loop_results": results},
Message: fmt.Sprintf("Loop completed %d iterations", iterations),
}, nil
}
// FilterProcessor handles data filtering
type FilterProcessor struct{}
func (p *FilterProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
filterField, ok := config.Custom["field"].(string)
if !ok {
return &ProcessingResult{
Success: false,
Error: "No filter field specified",
}, nil
}
filterValue := config.Custom["value"]
// Simple filtering logic
if value, exists := input.Data[filterField]; exists {
if fmt.Sprintf("%v", value) == fmt.Sprintf("%v", filterValue) {
return &ProcessingResult{
Success: true,
Data: input.Data,
Message: "Filter passed",
}, nil
}
}
return &ProcessingResult{
Success: false,
Data: nil,
Message: "Filter failed",
}, nil
}
// AggregatorProcessor handles data aggregation
type AggregatorProcessor struct{}
func (p *AggregatorProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
operation := "sum"
if op, ok := config.Custom["operation"].(string); ok {
operation = op
}
field, ok := config.Custom["field"].(string)
if !ok {
return &ProcessingResult{
Success: false,
Error: "No aggregation field specified",
}, nil
}
// Simple aggregation - in real implementation, collect data from multiple sources
value := input.Data[field]
result := map[string]interface{}{
"operation": operation,
"field": field,
"result": value,
}
return &ProcessingResult{
Success: true,
Data: result,
Message: fmt.Sprintf("Aggregation completed: %s on %s", operation, field),
}, nil
}
// ErrorProcessor handles error scenarios
type ErrorProcessor struct{}
func (p *ErrorProcessor) Process(ctx context.Context, input ProcessingContext) (*ProcessingResult, error) {
config := input.Node.Config
errorMessage := "Simulated error"
if msg, ok := config.Custom["message"].(string); ok {
errorMessage = msg
}
shouldFail := true
if fail, ok := config.Custom["fail"].(bool); ok {
shouldFail = fail
}
if shouldFail {
return &ProcessingResult{
Success: false,
Error: errorMessage,
}, nil
}
return &ProcessingResult{
Success: true,
Data: map[string]interface{}{"error_handled": true},
Message: "Error processor completed without error",
}, nil
}