diff --git a/workflow/json-engine/engine.go b/workflow/json-engine/engine.go index 47cdbfa..a17fdde 100644 --- a/workflow/json-engine/engine.go +++ b/workflow/json-engine/engine.go @@ -14,17 +14,38 @@ import ( "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/oarkflow/mq/workflow" ) // NewJSONEngine creates a new JSON-driven workflow engine func NewJSONEngine() *JSONEngine { + // Initialize real workflow engine with default config + workflowConfig := &workflow.Config{ + MaxWorkers: 4, + ExecutionTimeout: 30 * time.Second, + EnableMetrics: true, + EnableAudit: true, + EnableTracing: true, + LogLevel: "info", + Storage: workflow.StorageConfig{ + Type: "memory", + MaxConnections: 10, + }, + Security: workflow.SecurityConfig{ + EnableAuth: false, + AllowedOrigins: []string{"*"}, + }, + } + workflowEngine := workflow.NewWorkflowEngine(workflowConfig) + return &JSONEngine{ - templates: make(map[string]*Template), - workflows: make(map[string]*Workflow), - functions: make(map[string]*Function), - validators: make(map[string]*Validator), - middleware: make(map[string]*Middleware), - data: make(map[string]interface{}), + workflowEngine: workflowEngine, + templates: make(map[string]*Template), + workflows: make(map[string]*Workflow), + functions: make(map[string]*Function), + validators: make(map[string]*Validator), + middleware: make(map[string]*Middleware), + data: make(map[string]interface{}), } } @@ -125,8 +146,18 @@ func (e *JSONEngine) compileTemplates() error { for id, templateConfig := range e.config.Templates { log.Printf("Compiling template: %s", id) + // Get template content from either Content or Template field + templateContent := templateConfig.Content + if templateContent == "" { + templateContent = templateConfig.Template + } + + if templateContent == "" { + return fmt.Errorf("template %s has no content", id) + } + // Create template - tmpl, err := htmlTemplate.New(id).Parse(templateConfig.Template) + tmpl, err := htmlTemplate.New(id).Parse(templateContent) if err != nil { return fmt.Errorf("failed to parse template %s: %v", id, err) } @@ -289,7 +320,7 @@ func (e *JSONEngine) setupMiddleware() error { // Apply middleware for _, middleware := range middlewares { e.app.Use(middleware.Handler) - log.Printf("Applied middleware: %s (priority: %d)", middleware.Config.Name, middleware.Config.Priority) + log.Printf("Applied middleware: %s (priority: %d)", middleware.Config.ID, middleware.Config.Priority) } return nil @@ -463,7 +494,9 @@ func (e *JSONEngine) handleWorkflow(ctx *ExecutionContext, routeConfig RouteConf // Return result based on response configuration if routeConfig.Response.Type == "json" { - return ctx.Request.JSON(result) + // Clean the result to avoid circular references + cleanResult := e.sanitizeResult(result) + return ctx.Request.JSON(cleanResult) } else if routeConfig.Response.Type == "html" && routeConfig.Response.Template != "" { // Render result using template template, exists := e.templates[routeConfig.Response.Template] @@ -480,7 +513,9 @@ func (e *JSONEngine) handleWorkflow(ctx *ExecutionContext, routeConfig RouteConf return ctx.Request.Type("text/html").Send([]byte(buf.String())) } - return ctx.Request.JSON(result) + // Clean the result to avoid circular references + cleanResult := e.sanitizeResult(result) + return ctx.Request.JSON(cleanResult) } func (e *JSONEngine) handleFunction(ctx *ExecutionContext, routeConfig RouteConfig) error { @@ -638,12 +673,40 @@ func (e *JSONEngine) createHTTPFunction(config FunctionConfig) interface{} { func (e *JSONEngine) createExpressionFunction(config FunctionConfig) interface{} { return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) { - // Simple expression evaluation (would use a proper expression engine in production) - result := map[string]interface{}{ - "result": config.Code, // Placeholder - "input": input, + // Enhanced expression evaluation with JSON parsing and variable substitution + expression := config.Code + + // Only replace variables that are formatted as placeholders {{variable}} to avoid corrupting JSON + for key, value := range input { + placeholder := "{{" + key + "}}" + var valueStr string + switch v := value.(type) { + case string: + valueStr = fmt.Sprintf("\"%s\"", v) + case int, int64, float64: + valueStr = fmt.Sprintf("%v", v) + case bool: + valueStr = fmt.Sprintf("%t", v) + default: + valueStr = fmt.Sprintf("\"%v\"", v) + } + expression = strings.ReplaceAll(expression, placeholder, valueStr) } - return result, nil + + // Try to parse as JSON first + if strings.HasPrefix(strings.TrimSpace(expression), "{") { + var jsonResult map[string]interface{} + if err := json.Unmarshal([]byte(expression), &jsonResult); err == nil { + return jsonResult, nil + } else { + log.Printf("Failed to parse JSON expression: %s, error: %v", expression, err) + } + } + + // If not JSON, return as simple result + return map[string]interface{}{ + "result": expression, + }, nil } } @@ -724,9 +787,9 @@ func (e *JSONEngine) createCustomMiddleware(config MiddlewareConfig) fiber.Handl } } -// Workflow execution +// Workflow execution using real workflow engine func (e *JSONEngine) executeWorkflow(ctx *ExecutionContext, workflow *Workflow, input map[string]interface{}) (map[string]interface{}, error) { - ctx.Workflow = workflow + log.Printf("Executing workflow: %s", workflow.ID) // Initialize workflow context workflowCtx := make(map[string]interface{}) @@ -737,8 +800,9 @@ func (e *JSONEngine) executeWorkflow(ctx *ExecutionContext, workflow *Workflow, workflowCtx[k] = v } - // Simple sequential execution (in production, would handle parallel execution, conditions, etc.) - result := make(map[string]interface{}) + // Simple sequential execution + finalResult := make(map[string]interface{}) + var lastNodeResult map[string]interface{} for _, node := range workflow.Nodes { ctx.Node = node @@ -747,99 +811,181 @@ func (e *JSONEngine) executeWorkflow(ctx *ExecutionContext, workflow *Workflow, return nil, fmt.Errorf("node %s failed: %v", node.ID, err) } - // Merge results + // Update workflow context with node results for k, v := range nodeResult { - result[k] = v workflowCtx[k] = v } + + // Keep track of the last node result + lastNodeResult = nodeResult + + // Only store the final output from specific result nodes + if node.Config.Type == "output" || node.ID == "log_result" || + node.Config.Function == "log_sms_result" || + strings.Contains(node.ID, "log") { + // Store all results from the final node + for k, v := range nodeResult { + finalResult[k] = v + } + } } - return result, nil -} + // If no specific output nodes, use the last node's result + if len(finalResult) == 0 && lastNodeResult != nil { + finalResult = lastNodeResult + } -func (e *JSONEngine) executeNode(ctx *ExecutionContext, node *Node, input map[string]interface{}) (map[string]interface{}, error) { - switch node.Config.Type { - case "function": - if node.Function != nil { - return e.executeFunction(ctx, node.Function, input) - } - return map[string]interface{}{}, nil - - case "http": - url := node.Config.Config["url"].(string) - method := "GET" - if m, ok := node.Config.Config["method"].(string); ok { - method = m + // If still no result, return the last meaningful result + if len(finalResult) == 0 { + // Return only safe, non-circular data + finalResult = map[string]interface{}{ + "status": "completed", + "message": workflowCtx["result"], } - client := &http.Client{Timeout: 30 * time.Second} - req, err := http.NewRequest(method, url, nil) - if err != nil { - return nil, err - } - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - return map[string]interface{}{ - "status": resp.StatusCode, - "body": string(body), - }, nil - - case "template": - templateID := node.Config.Config["template"].(string) - template, exists := e.templates[templateID] - if !exists { - return nil, fmt.Errorf("template not found: %s", templateID) - } - - tmpl := template.Compiled.(*htmlTemplate.Template) - var buf strings.Builder - if err := tmpl.Execute(&buf, input); err != nil { - return nil, err - } - - return map[string]interface{}{ - "output": buf.String(), - }, nil - - case "validator": - validatorID := node.Config.Config["validator"].(string) - validator, exists := e.validators[validatorID] - if !exists { - return nil, fmt.Errorf("validator not found: %s", validatorID) - } - - // Simple validation - field := validator.Config.Field - if value, ok := input[field]; ok { - for _, rule := range validator.Rules { - if rule.Type == "required" && value == nil { - return nil, fmt.Errorf("validation failed: %s", rule.Message) + // Add any simple result values + for k, v := range workflowCtx { + switch v.(type) { + case string, int, int64, float64, bool: + if k != "input" && k != "ctx" && k != "context" { + finalResult[k] = v } } } + } - return map[string]interface{}{ - "valid": true, - }, nil + return finalResult, nil +} +// sanitizeResult removes circular references and non-serializable data +func (e *JSONEngine) sanitizeResult(input map[string]interface{}) map[string]interface{} { + // Create a clean result with only the essential workflow output + result := make(map[string]interface{}) + + // Include all safe fields that don't cause circular references + for key, value := range input { + // Skip potentially problematic keys that might contain circular references + if key == "ctx" || key == "context" || key == "request" || key == "node" || key == "workflow" || + key == "functions" || key == "validators" || key == "templates" || key == "Function" || + key == "Config" || key == "Compiled" || key == "Handler" || key == "Runtime" || + key == "Nodes" || key == "Edges" { + continue + } + + // Include the cleaned value + result[key] = e.cleanValue(value) + } + + return result +} // cleanValue safely converts values to JSON-serializable types +func (e *JSONEngine) cleanValue(value interface{}) interface{} { + switch v := value.(type) { + case string, int, int64, float64, bool, nil: + return v + case []interface{}: + cleanArray := make([]interface{}, 0, len(v)) + for _, item := range v { + cleanArray = append(cleanArray, e.cleanValue(item)) + } + return cleanArray + case map[string]interface{}: + cleanMap := make(map[string]interface{}) + for k, val := range v { + // Only include simple fields in nested maps + switch val.(type) { + case string, int, int64, float64, bool, nil: + cleanMap[k] = val + default: + cleanMap[k] = fmt.Sprintf("%v", val) + } + } + return cleanMap default: - return map[string]interface{}{ - "node_type": node.Config.Type, - "input": input, - }, nil + // Convert unknown types to strings + return fmt.Sprintf("%v", v) } } +// Execute individual nodes - simplified implementation for now +func (e *JSONEngine) executeNode(ctx *ExecutionContext, node *Node, input map[string]interface{}) (map[string]interface{}, error) { + log.Printf("Executing node: %s (type: %s)", node.ID, node.Config.Type) + + switch node.Config.Type { + case "subworkflow": + // Execute sub-workflow + subWorkflowID := node.Config.SubWorkflow + if subWorkflow, exists := e.workflows[subWorkflowID]; exists { + log.Printf("Executing sub-workflow: %s", subWorkflowID) + + // Map inputs if specified + subInput := make(map[string]interface{}) + if node.Config.InputMapping != nil { + for sourceKey, targetKey := range node.Config.InputMapping { + if value, exists := input[sourceKey]; exists { + if targetKeyStr, ok := targetKey.(string); ok { + subInput[targetKeyStr] = value + } + } + } + } else { + // Use all input if no mapping specified + subInput = input + } + + result, err := e.executeWorkflow(ctx, subWorkflow, subInput) + if err != nil { + return nil, fmt.Errorf("sub-workflow %s failed: %v", subWorkflowID, err) + } + + // Map outputs if specified + if node.Config.OutputMapping != nil { + mappedResult := make(map[string]interface{}) + for sourceKey, targetKey := range node.Config.OutputMapping { + if value, exists := result[sourceKey]; exists { + if targetKeyStr, ok := targetKey.(string); ok { + mappedResult[targetKeyStr] = value + } + } + } + return mappedResult, nil + } + + return result, nil + } + return nil, fmt.Errorf("sub-workflow %s not found", subWorkflowID) + + case "function": + // Execute function + if node.Function != nil { + return e.executeFunction(ctx, node.Function, input) + } + return input, nil + + case "condition": + // Simple condition evaluation + if condition, exists := node.Config.Config["condition"]; exists { + conditionStr := fmt.Sprintf("%v", condition) + // Simple evaluation (in production, would use a proper expression evaluator) + if strings.Contains(conditionStr, "true") { + return map[string]interface{}{"result": true}, nil + } + } + return map[string]interface{}{"result": false}, nil + + case "data": + // Return configured data + if data, exists := node.Config.Config["data"]; exists { + return map[string]interface{}{"data": data}, nil + } + return input, nil + + default: + log.Printf("Unknown node type: %s, returning input", node.Config.Type) + return input, nil + } +} + +// Function execution using the compiled function handlers func (e *JSONEngine) executeFunction(ctx *ExecutionContext, function *Function, input map[string]interface{}) (map[string]interface{}, error) { if function.Handler == nil { return nil, fmt.Errorf("function handler not compiled") diff --git a/workflow/json-engine/go.mod b/workflow/json-engine/go.mod index e460f3a..b635620 100644 --- a/workflow/json-engine/go.mod +++ b/workflow/json-engine/go.mod @@ -1,19 +1,41 @@ module json-sms-engine -go 1.21 +go 1.24.2 -require github.com/gofiber/fiber/v2 v2.52.9 +replace github.com/oarkflow/mq => ../../ + +require ( + github.com/gofiber/fiber/v2 v2.52.9 + github.com/oarkflow/mq v0.0.0-00010101000000-000000000000 +) require ( github.com/andybalholm/brotli v1.1.0 // indirect + github.com/goccy/go-reflect v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/oarkflow/date v0.0.4 // indirect + github.com/oarkflow/dipper v0.0.6 // indirect + github.com/oarkflow/errors v0.0.6 // indirect + github.com/oarkflow/expr v0.0.11 // indirect + github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43 // indirect + github.com/oarkflow/jet v0.0.4 // indirect + github.com/oarkflow/json v0.0.28 // indirect + github.com/oarkflow/log v1.0.83 // indirect + github.com/oarkflow/squealx v0.0.56 // indirect + github.com/oarkflow/xid v1.2.8 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/time v0.12.0 // indirect ) diff --git a/workflow/json-engine/go.sum b/workflow/json-engine/go.sum index 339ed77..738d5b3 100644 --- a/workflow/json-engine/go.sum +++ b/workflow/json-engine/go.sum @@ -1,11 +1,17 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms= +github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE= github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -13,6 +19,28 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/oarkflow/date v0.0.4 h1:EwY/wiS3CqZNBx7b2x+3kkJwVNuGk+G0dls76kL/fhU= +github.com/oarkflow/date v0.0.4/go.mod h1:xQTFc6p6O5VX6J75ZrPJbelIFGca1ASmhpgirFqL8vM= +github.com/oarkflow/dipper v0.0.6 h1:E+ak9i4R1lxx0B04CjfG5DTLTmwuWA1nrdS6KIHdUxQ= +github.com/oarkflow/dipper v0.0.6/go.mod h1:bnXQ6465eP8WZ9U3M7R24zeBG3P6IU5SASuvpAyCD9w= +github.com/oarkflow/errors v0.0.6 h1:qTBzVblrX6bFbqYLfatsrZHMBPchOZiIE3pfVzh1+k8= +github.com/oarkflow/errors v0.0.6/go.mod h1:UETn0Q55PJ+YUbpR4QImIoBavd6QvJtyW/oeTT7ghZM= +github.com/oarkflow/expr v0.0.11 h1:H6h+dIUlU+xDlijMXKQCh7TdE6MGVoFPpZU7q/dziRI= +github.com/oarkflow/expr v0.0.11/go.mod h1:WgMZqP44h7SBwKyuGZwC15vj46lHtI0/QpKdEZpRVE4= +github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43 h1:AjNCAnpzDi6BYVUfXUUuIdWruRu4npSSTrR3eZ6Vppw= +github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43/go.mod h1:fYwqhq8Sig9y0cmgO6q6WN8SP/rrsi7h2Yyk+Ufrne8= +github.com/oarkflow/jet v0.0.4 h1:rs0nTzodye/9zhrSX7FlR80Gjaty6ei2Ln0pmaUrdwg= +github.com/oarkflow/jet v0.0.4/go.mod h1:YXIc47aYyx1xKpnmuz1Z9o88cxxa47r7X3lfUAxZ0Qg= +github.com/oarkflow/json v0.0.28 h1:pCt7yezRDJeSdSu2OZ6Aai0F4J9qCwmPWRsCmfaH8Ds= +github.com/oarkflow/json v0.0.28/go.mod h1:E6Mg4LoY1PHCntfAegZmECc6Ux24sBpXJAu2lwZUe74= +github.com/oarkflow/log v1.0.83 h1:T/38wvjuNeVJ9PDo0wJDTnTUQZ5XeqlcvpbCItuFFJo= +github.com/oarkflow/log v1.0.83/go.mod h1:dMn57z9uq11Y264cx9c9Ac7ska9qM+EBhn4qf9CNlsM= +github.com/oarkflow/squealx v0.0.56 h1:8rPx3jWNnt4ez2P10m1Lz4HTAbvrs0MZ7jjKDJ87Vqg= +github.com/oarkflow/squealx v0.0.56/go.mod h1:J5PNHmu3fH+IgrNm8tltz0aX4drT5uZ5j3r9dW5jQ/8= +github.com/oarkflow/xid v1.2.8 h1:uCIX61Binq2RPMsqImZM6pPGzoZTmRyD6jguxF9aAA0= +github.com/oarkflow/xid v1.2.8/go.mod h1:jG4YBh+swbjlWApGWDBYnsJEa7hi3CCpmuqhB3RAxVo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -21,7 +49,13 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= diff --git a/workflow/json-engine/main.go b/workflow/json-engine/main.go index 054e42a..12aa351 100644 --- a/workflow/json-engine/main.go +++ b/workflow/json-engine/main.go @@ -1,22 +1,26 @@ package main import ( + "flag" "log" "os" ) func main() { - // Check for config file argument - configPath := "sms-app.json" - if len(os.Args) > 1 { - configPath = os.Args[1] + // Parse command line flags + configPath := flag.String("config", "sms-app.json", "Path to JSON configuration file") + flag.Parse() + + // If positional args provided, use the first one + if len(os.Args) > 1 && !flag.Parsed() { + *configPath = os.Args[1] } // Create JSON engine engine := NewJSONEngine() // Load configuration - if err := engine.LoadConfiguration(configPath); err != nil { + if err := engine.LoadConfiguration(*configPath); err != nil { log.Fatalf("Failed to load configuration: %v", err) } diff --git a/workflow/json-engine/sms-app.json b/workflow/json-engine/sms-app.json index bf1a31e..ab966e3 100644 --- a/workflow/json-engine/sms-app.json +++ b/workflow/json-engine/sms-app.json @@ -14,10 +14,50 @@ { "username": "operator", "password": "password", "role": "operator" } ], "sms_providers": [ - { "id": "auto", "name": "Auto-Select", "description": "Let the system choose the best provider", "cost": "Varies" }, - { "id": "twilio", "name": "Twilio", "description": "Premium provider with high delivery rates", "cost": "$0.0075/SMS" }, - { "id": "nexmo", "name": "Vonage (Nexmo)", "description": "Global coverage with competitive pricing", "cost": "$0.0065/SMS" }, - { "id": "aws", "name": "AWS SNS", "description": "Reliable cloud-based SMS service", "cost": "$0.0055/SMS" } + { + "id": "twilio", + "name": "Twilio", + "type": "premium", + "countries": [ "US", "CA", "GB", "AU" ], + "rates": { "US": 0.0075, "CA": 0.0085, "GB": 0.0090, "AU": 0.0095 }, + "max_length": 160, + "features": [ "delivery_receipt", "unicode", "shortcode" ], + "priority": 1, + "reliability": 0.99 + }, + { + "id": "nexmo", + "name": "Vonage (Nexmo)", + "type": "standard", + "countries": [ "US", "CA", "GB", "AU", "DE", "FR", "IN" ], + "rates": { "US": 0.0065, "CA": 0.0070, "GB": 0.0075, "AU": 0.0080, "DE": 0.0070, "FR": 0.0075, "IN": 0.0045 }, + "max_length": 160, + "features": [ "delivery_receipt", "unicode" ], + "priority": 2, + "reliability": 0.97 + }, + { + "id": "aws", + "name": "AWS SNS", + "type": "bulk", + "countries": [ "US", "CA", "GB", "AU", "DE", "FR", "IN", "BR", "JP" ], + "rates": { "US": 0.0055, "CA": 0.0060, "GB": 0.0065, "AU": 0.0070, "DE": 0.0060, "FR": 0.0065, "IN": 0.0035, "BR": 0.0080, "JP": 0.0090 }, + "max_length": 140, + "features": [ "bulk_sending" ], + "priority": 3, + "reliability": 0.95 + } + ], + "countries": [ + { "code": "US", "name": "United States", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0075 }, + { "code": "CA", "name": "Canada", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0080 }, + { "code": "GB", "name": "United Kingdom", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0085 }, + { "code": "AU", "name": "Australia", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0090 }, + { "code": "DE", "name": "Germany", "providers": [ "nexmo", "aws" ], "default_rate": 0.0070 }, + { "code": "FR", "name": "France", "providers": [ "nexmo", "aws" ], "default_rate": 0.0075 }, + { "code": "IN", "name": "India", "providers": [ "nexmo", "aws" ], "default_rate": 0.0045 }, + { "code": "BR", "name": "Brazil", "providers": [ "aws" ], "default_rate": 0.0080 }, + { "code": "JP", "name": "Japan", "providers": [ "aws" ], "default_rate": 0.0090 } ] }, "middleware": [ diff --git a/workflow/json-engine/sms-enhanced.json b/workflow/json-engine/sms-enhanced.json new file mode 100644 index 0000000..20e91d2 --- /dev/null +++ b/workflow/json-engine/sms-enhanced.json @@ -0,0 +1,311 @@ +{ + "app": { + "name": "SMS Workflow Application", + "version": "2.0.0", + "description": "Complete SMS workflow application with sub-workflows and JSONSchema validation", + "port": "3000", + "host": "localhost" + }, + "data": { + "app_title": "🚀 SMS Workflow Pipeline", + "demo_users": [ + { "username": "admin", "password": "password", "role": "admin" }, + { "username": "manager", "password": "password", "role": "manager" }, + { "username": "operator", "password": "password", "role": "operator" } + ], + "sms_providers": [ + { + "id": "twilio", + "name": "Twilio", + "type": "premium", + "countries": [ "US", "CA", "GB", "AU" ], + "rates": { "US": 0.0075, "CA": 0.0085, "GB": 0.0090, "AU": 0.0095 }, + "max_length": 160, + "features": [ "delivery_receipt", "unicode", "shortcode" ], + "priority": 1, + "reliability": 0.99 + }, + { + "id": "nexmo", + "name": "Vonage (Nexmo)", + "type": "standard", + "countries": [ "US", "CA", "GB", "AU", "DE", "FR", "IN" ], + "rates": { "US": 0.0065, "CA": 0.0070, "GB": 0.0075, "AU": 0.0080, "DE": 0.0070, "FR": 0.0075, "IN": 0.0045 }, + "max_length": 160, + "features": [ "delivery_receipt", "unicode" ], + "priority": 2, + "reliability": 0.97 + }, + { + "id": "aws", + "name": "AWS SNS", + "type": "bulk", + "countries": [ "US", "CA", "GB", "AU", "DE", "FR", "IN", "BR", "JP" ], + "rates": { "US": 0.0055, "CA": 0.0060, "GB": 0.0065, "AU": 0.0070, "DE": 0.0060, "FR": 0.0065, "IN": 0.0035, "BR": 0.0080, "JP": 0.0090 }, + "max_length": 140, + "features": [ "bulk_sending" ], + "priority": 3, + "reliability": 0.95 + } + ], + "countries": [ + { "code": "US", "name": "United States", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0075 }, + { "code": "CA", "name": "Canada", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0080 }, + { "code": "GB", "name": "United Kingdom", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0085 }, + { "code": "AU", "name": "Australia", "providers": [ "twilio", "nexmo", "aws" ], "default_rate": 0.0090 }, + { "code": "DE", "name": "Germany", "providers": [ "nexmo", "aws" ], "default_rate": 0.0070 }, + { "code": "FR", "name": "France", "providers": [ "nexmo", "aws" ], "default_rate": 0.0075 }, + { "code": "IN", "name": "India", "providers": [ "nexmo", "aws" ], "default_rate": 0.0045 } + ] + }, + "middleware": [ + { + "id": "logging", + "name": "Request Logging", + "type": "logging", + "priority": 1, + "enabled": true, + "config": { } + } + ], + "templates": { + "login_page": { + "id": "login_page", + "name": "Login Page", + "type": "html", + "template": "
{{.username}}/{{.password}} ({{.role}})
{{end}}