This commit is contained in:
sujit
2025-09-18 08:52:38 +05:45
parent f7f011db3d
commit 651c7335bc
8 changed files with 873 additions and 278 deletions

View File

@@ -14,17 +14,38 @@ import (
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/recover"
"github.com/oarkflow/mq/workflow"
) )
// NewJSONEngine creates a new JSON-driven workflow engine // NewJSONEngine creates a new JSON-driven workflow engine
func NewJSONEngine() *JSONEngine { 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{ return &JSONEngine{
templates: make(map[string]*Template), workflowEngine: workflowEngine,
workflows: make(map[string]*Workflow), templates: make(map[string]*Template),
functions: make(map[string]*Function), workflows: make(map[string]*Workflow),
validators: make(map[string]*Validator), functions: make(map[string]*Function),
middleware: make(map[string]*Middleware), validators: make(map[string]*Validator),
data: make(map[string]interface{}), 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 { for id, templateConfig := range e.config.Templates {
log.Printf("Compiling template: %s", id) 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 // Create template
tmpl, err := htmlTemplate.New(id).Parse(templateConfig.Template) tmpl, err := htmlTemplate.New(id).Parse(templateContent)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse template %s: %v", id, err) return fmt.Errorf("failed to parse template %s: %v", id, err)
} }
@@ -289,7 +320,7 @@ func (e *JSONEngine) setupMiddleware() error {
// Apply middleware // Apply middleware
for _, middleware := range middlewares { for _, middleware := range middlewares {
e.app.Use(middleware.Handler) 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 return nil
@@ -463,7 +494,9 @@ func (e *JSONEngine) handleWorkflow(ctx *ExecutionContext, routeConfig RouteConf
// Return result based on response configuration // Return result based on response configuration
if routeConfig.Response.Type == "json" { 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 != "" { } else if routeConfig.Response.Type == "html" && routeConfig.Response.Template != "" {
// Render result using template // Render result using template
template, exists := e.templates[routeConfig.Response.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.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 { 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{} { func (e *JSONEngine) createExpressionFunction(config FunctionConfig) interface{} {
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) { return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
// Simple expression evaluation (would use a proper expression engine in production) // Enhanced expression evaluation with JSON parsing and variable substitution
result := map[string]interface{}{ expression := config.Code
"result": config.Code, // Placeholder
"input": input, // 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) { 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 // Initialize workflow context
workflowCtx := make(map[string]interface{}) workflowCtx := make(map[string]interface{})
@@ -737,8 +800,9 @@ func (e *JSONEngine) executeWorkflow(ctx *ExecutionContext, workflow *Workflow,
workflowCtx[k] = v workflowCtx[k] = v
} }
// Simple sequential execution (in production, would handle parallel execution, conditions, etc.) // Simple sequential execution
result := make(map[string]interface{}) finalResult := make(map[string]interface{})
var lastNodeResult map[string]interface{}
for _, node := range workflow.Nodes { for _, node := range workflow.Nodes {
ctx.Node = node 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) return nil, fmt.Errorf("node %s failed: %v", node.ID, err)
} }
// Merge results // Update workflow context with node results
for k, v := range nodeResult { for k, v := range nodeResult {
result[k] = v
workflowCtx[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) { // If still no result, return the last meaningful result
switch node.Config.Type { if len(finalResult) == 0 {
case "function": // Return only safe, non-circular data
if node.Function != nil { finalResult = map[string]interface{}{
return e.executeFunction(ctx, node.Function, input) "status": "completed",
} "message": workflowCtx["result"],
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
} }
client := &http.Client{Timeout: 30 * time.Second} // Add any simple result values
req, err := http.NewRequest(method, url, nil) for k, v := range workflowCtx {
if err != nil { switch v.(type) {
return nil, err case string, int, int64, float64, bool:
} if k != "input" && k != "ctx" && k != "context" {
finalResult[k] = v
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)
} }
} }
} }
}
return map[string]interface{}{ return finalResult, nil
"valid": true, }
}, 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: default:
return map[string]interface{}{ // Convert unknown types to strings
"node_type": node.Config.Type, return fmt.Sprintf("%v", v)
"input": input,
}, nil
} }
} }
// 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) { func (e *JSONEngine) executeFunction(ctx *ExecutionContext, function *Function, input map[string]interface{}) (map[string]interface{}, error) {
if function.Handler == nil { if function.Handler == nil {
return nil, fmt.Errorf("function handler not compiled") return nil, fmt.Errorf("function handler not compiled")

View File

@@ -1,19 +1,41 @@
module json-sms-engine 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 ( require (
github.com/andybalholm/brotli v1.1.0 // indirect 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/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/klauspost/compress v1.17.9 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // 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/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.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
) )

View File

@@ -1,11 +1,17 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 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 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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= 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-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 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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/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 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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=

View File

@@ -1,22 +1,26 @@
package main package main
import ( import (
"flag"
"log" "log"
"os" "os"
) )
func main() { func main() {
// Check for config file argument // Parse command line flags
configPath := "sms-app.json" configPath := flag.String("config", "sms-app.json", "Path to JSON configuration file")
if len(os.Args) > 1 { flag.Parse()
configPath = os.Args[1]
// If positional args provided, use the first one
if len(os.Args) > 1 && !flag.Parsed() {
*configPath = os.Args[1]
} }
// Create JSON engine // Create JSON engine
engine := NewJSONEngine() engine := NewJSONEngine()
// Load configuration // Load configuration
if err := engine.LoadConfiguration(configPath); err != nil { if err := engine.LoadConfiguration(*configPath); err != nil {
log.Fatalf("Failed to load configuration: %v", err) log.Fatalf("Failed to load configuration: %v", err)
} }

View File

@@ -14,10 +14,50 @@
{ "username": "operator", "password": "password", "role": "operator" } { "username": "operator", "password": "password", "role": "operator" }
], ],
"sms_providers": [ "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": "twilio",
{ "id": "nexmo", "name": "Vonage (Nexmo)", "description": "Global coverage with competitive pricing", "cost": "$0.0065/SMS" }, "name": "Twilio",
{ "id": "aws", "name": "AWS SNS", "description": "Reliable cloud-based SMS service", "cost": "$0.0055/SMS" } "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": [ "middleware": [

View File

@@ -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": "<!DOCTYPE html><html><head><title>SMS Workflow - Login</title><style>body{font-family:Arial;max-width:500px;margin:100px auto;padding:20px}.login-container{padding:40px;border:1px solid #ddd;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1)}.form-group{margin-bottom:20px}label{display:block;margin-bottom:5px;font-weight:bold}input{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;box-sizing:border-box}button{width:100%;background:#007bff;color:white;padding:12px;border:none;border-radius:4px;cursor:pointer;font-size:16px}button:hover{background:#0056b3}.error{background:#f8d7da;border:1px solid #f5c6cb;color:#721c24;padding:10px;border-radius:4px;margin-top:10px}.success{background:#d4edda;border:1px solid #c3e6cb;color:#155724;padding:10px;border-radius:4px;margin-top:10px}.demo-users{background:#e3f2fd;padding:15px;border-radius:4px;margin-bottom:20px}</style></head><body><div class=\"login-container\"><h1>🔐 SMS Workflow Login</h1><div class=\"demo-users\"><h3>Demo Users:</h3>{{range .demo_users}}<p><strong>{{.username}}</strong>/{{.password}} ({{.role}})</p>{{end}}</div><form id=\"loginForm\"><div class=\"form-group\"><label for=\"username\">Username:</label><input type=\"text\" id=\"username\" required></div><div class=\"form-group\"><label for=\"password\">Password:</label><input type=\"password\" id=\"password\" required></div><button type=\"submit\">Login</button></form><div id=\"result\"></div></div><script>document.getElementById('loginForm').addEventListener('submit',async function(e){e.preventDefault();const username=document.getElementById('username').value;const password=document.getElementById('password').value;const resultDiv=document.getElementById('result');try{const response=await fetch('/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username,password})});const result=await response.json();if(result.success){resultDiv.innerHTML='<div class=\"success\">✅ Login successful! Redirecting...</div>';sessionStorage.setItem('authToken',result.token);sessionStorage.setItem('user',JSON.stringify(result.user));setTimeout(()=>{window.location.href='/sms';},1000);}else{resultDiv.innerHTML='<div class=\"error\">❌ '+result.error+'</div>';}}catch(error){resultDiv.innerHTML='<div class=\"error\">❌ '+error.message+'</div>';}});</script></body></html>"
},
"sms_page": {
"id": "sms_page",
"name": "SMS Workflow Page",
"type": "html",
"template": "<!DOCTYPE html><html><head><title>SMS Workflow</title><style>body{font-family:Arial;max-width:800px;margin:0 auto;padding:20px}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:30px;padding:20px;background:#f8f9fa;border-radius:8px}.form-group{margin-bottom:15px}label{display:block;margin-bottom:5px;font-weight:bold}input,textarea,select{width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;box-sizing:border-box}button{background:#007bff;color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer}.result{margin-top:15px;padding:10px;border-radius:4px}.success{background:#d4edda;border:1px solid #c3e6cb;color:#155724}.error{background:#f8d7da;border:1px solid #f5c6cb;color:#721c24}</style></head><body><div class=\"header\"><h1>SMS Workflow</h1><button onclick=\"logout()\">Logout</button></div><form id=\"smsForm\"><div class=\"form-group\"><label>Recipients:</label><input type=\"text\" id=\"recipients\" placeholder=\"+1234567890,+0987654321\" required></div><div class=\"form-group\"><label>Message:</label><textarea id=\"message\" rows=\"4\" placeholder=\"Enter SMS message...\" required></textarea></div><button type=\"submit\">Send SMS</button></form><div id=\"result\"></div><script>let authToken=sessionStorage.getItem('authToken');if(!authToken){window.location.href='/login';}document.getElementById('smsForm').addEventListener('submit',async function(e){e.preventDefault();const recipients=document.getElementById('recipients').value.split(',');const message=document.getElementById('message').value;const resultDiv=document.getElementById('result');try{const response=await fetch('/api/sms/send',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+authToken},body:JSON.stringify({recipients,message})});const result=await response.json();if(result.success){resultDiv.innerHTML='<div class=\"success\">✅ SMS sent successfully! Provider: '+result.provider_used+', Cost: $'+result.cost+'</div>';}else{resultDiv.innerHTML='<div class=\"error\">❌ '+result.error+'</div>';}}catch(error){resultDiv.innerHTML='<div class=\"error\">❌ '+error.message+'</div>';}});function logout(){sessionStorage.removeItem('authToken');window.location.href='/login';}</script></body></html>"
}
},
"functions": {
"authenticate_user": {
"id": "authenticate_user",
"name": "User Authentication",
"type": "expression",
"code": "validate user credentials and return token"
},
"extract_country_code": {
"id": "extract_country_code",
"name": "Extract Country Code",
"type": "expression",
"code": "{ \"country_codes\": [\"US\"], \"extracted\": true }"
},
"analyze_message_requirements": {
"id": "analyze_message_requirements",
"name": "Analyze Message Requirements",
"type": "expression",
"code": "{ \"message_length\": 50, \"requires_unicode\": false, \"message_count\": 1 }"
},
"calculate_provider_costs": {
"id": "calculate_provider_costs",
"name": "Calculate Provider Costs",
"type": "expression",
"code": "{ \"twilio_cost\": 0.0075, \"nexmo_cost\": 0.0065, \"aws_cost\": 0.0055 }"
},
"select_optimal_provider": {
"id": "select_optimal_provider",
"name": "Select Optimal Provider",
"type": "expression",
"code": "{ \"provider\": \"twilio\", \"cost\": 0.0075, \"reason\": \"best reliability\" }"
},
"send_sms": {
"id": "send_sms",
"name": "Send SMS",
"type": "expression",
"code": "{ \"success\": true, \"message_id\": \"msg_12345\", \"provider_used\": \"twilio\", \"status\": \"sent\" }"
},
"log_sms_result": {
"id": "log_sms_result",
"name": "Log SMS Result",
"type": "expression",
"code": "{ \"success\": true, \"provider_used\": \"twilio\", \"cost\": 0.0075, \"message\": \"SMS sent successfully\", \"timestamp\": 1640995200000 }"
}
},
"validators": {
"sms_input": {
"id": "sms_input",
"name": "SMS Input Validator",
"type": "required",
"field": "message",
"rules": [
{ "type": "required", "message": "Message is required" },
{ "type": "length", "value": { "min": 1, "max": 160 }, "message": "Message must be 1-160 characters" }
]
},
"user_permissions": {
"id": "user_permissions",
"name": "User Permissions Validator",
"type": "required",
"field": "role",
"rules": [
{ "type": "required", "message": "User role required" }
]
}
},
"workflows": [
{
"id": "auth_subworkflow",
"name": "Authentication Sub-Workflow",
"description": "Handle user authentication and authorization",
"version": "1.0.0",
"nodes": [
{
"id": "validate_credentials",
"name": "Validate User Credentials",
"type": "function",
"description": "Check username and password",
"function": "authenticate_user"
},
{
"id": "check_permissions",
"name": "Check SMS Permissions",
"type": "validator",
"description": "Validate user has SMS sending permissions",
"config": { "validator": "user_permissions" }
}
],
"edges": [
{ "id": "creds_to_perms", "from": "validate_credentials", "to": "check_permissions" }
],
"variables": { "username": "", "password": "", "user_role": "" },
"options": { "async": false, "timeout": "10s" }
},
{
"id": "provider_selection_subworkflow",
"name": "SMS Provider Selection Sub-Workflow",
"description": "Select optimal SMS provider based on country, cost, and message requirements",
"version": "1.0.0",
"nodes": [
{
"id": "extract_country",
"name": "Extract Country from Phone",
"type": "function",
"description": "Parse country code from phone number",
"function": "extract_country_code"
},
{
"id": "analyze_message",
"name": "Analyze Message Requirements",
"type": "function",
"description": "Analyze message length and content requirements",
"function": "analyze_message_requirements"
},
{
"id": "calculate_costs",
"name": "Calculate Provider Costs",
"type": "function",
"description": "Calculate cost for each provider based on country and message count",
"function": "calculate_provider_costs"
},
{
"id": "select_optimal_provider",
"name": "Select Optimal Provider",
"type": "function",
"description": "Choose provider with best cost/reliability ratio",
"function": "select_optimal_provider"
}
],
"edges": [
{ "id": "extract_to_analyze", "from": "extract_country", "to": "analyze_message" },
{ "id": "analyze_to_calculate", "from": "analyze_message", "to": "calculate_costs" },
{ "id": "calculate_to_select", "from": "calculate_costs", "to": "select_optimal_provider" }
],
"variables": { "recipients": [ ], "message": "", "country_codes": [ ], "selected_provider": "" },
"options": { "async": false, "timeout": "15s" }
},
{
"id": "sms_workflow",
"name": "Main SMS Sending Workflow",
"description": "Complete SMS workflow using authentication and provider selection sub-workflows",
"version": "2.0.0",
"nodes": [
{
"id": "authenticate",
"name": "User Authentication",
"type": "subworkflow",
"description": "Authenticate user using auth sub-workflow",
"sub_workflow": "auth_subworkflow",
"input_mapping": { "username": "username", "password": "password" },
"output_mapping": { "auth_token": "token", "user_info": "user" }
},
{
"id": "validate_input",
"name": "Validate SMS Input",
"type": "validator",
"description": "Validate SMS message and recipients",
"config": { "validator": "sms_input" }
},
{
"id": "select_provider",
"name": "Select SMS Provider",
"type": "subworkflow",
"description": "Select optimal provider using provider selection sub-workflow",
"sub_workflow": "provider_selection_subworkflow",
"input_mapping": { "recipients": "recipients", "message": "message" },
"output_mapping": { "provider": "selected_provider", "cost": "estimated_cost" }
},
{
"id": "send_sms",
"name": "Send SMS",
"type": "function",
"description": "Send SMS via selected provider",
"function": "send_sms"
},
{
"id": "log_result",
"name": "Log SMS Result",
"type": "function",
"description": "Log SMS sending result with cost and provider info",
"function": "log_sms_result"
}
],
"edges": [
{ "id": "auth_to_validate", "from": "authenticate", "to": "validate_input" },
{ "id": "validate_to_select", "from": "validate_input", "to": "select_provider" },
{ "id": "select_to_send", "from": "select_provider", "to": "send_sms" },
{ "id": "send_to_log", "from": "send_sms", "to": "log_result" }
],
"variables": { "username": "", "password": "", "recipients": [ ], "message": "", "provider": "", "cost": 0 },
"options": { "async": false, "timeout": "60s", "retry": { "max_attempts": 3, "delay": "5s", "backoff_type": "exponential" } }
}
],
"routes": [
{
"id": "login_page",
"method": "GET",
"path": "/login",
"description": "Login page",
"handler": { "type": "template", "target": "login_page" },
"response": { "type": "html" }
},
{
"id": "auth_login",
"method": "POST",
"path": "/auth/login",
"description": "User authentication endpoint",
"handler": { "type": "function", "target": "authenticate_user" },
"response": { "type": "json" }
},
{
"id": "sms_page",
"method": "GET",
"path": "/sms",
"description": "SMS workflow interface",
"handler": { "type": "template", "target": "sms_page" },
"response": { "type": "html" }
},
{
"id": "sms_send",
"method": "POST",
"path": "/api/sms/send",
"description": "Execute SMS workflow",
"handler": { "type": "workflow", "target": "sms_workflow" },
"response": { "type": "json" }
}
]
}

Binary file not shown.

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/oarkflow/mq/workflow"
) )
// AppConfiguration represents the complete JSON configuration for an application // AppConfiguration represents the complete JSON configuration for an application
@@ -27,213 +28,250 @@ type AppMetadata struct {
// RouteConfig defines HTTP routes // RouteConfig defines HTTP routes
type RouteConfig struct { type RouteConfig struct {
ID string `json:"id"` Path string `json:"path"`
Method string `json:"method"` Method string `json:"method"`
Path string `json:"path"` Handler HandlerConfig `json:"handler"`
Description string `json:"description"` Middleware []string `json:"middleware,omitempty"`
Middleware []string `json:"middleware"` Template string `json:"template,omitempty"`
Handler HandlerConfig `json:"handler"` Variables map[string]string `json:"variables,omitempty"`
Auth *AuthConfig `json:"auth,omitempty"` Auth *AuthConfig `json:"auth,omitempty"`
Response ResponseConfig `json:"response"` Response *ResponseConfig `json:"response,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty"`
} }
// HandlerConfig defines route handler behavior // ResponseConfig defines response handling
type ResponseConfig struct {
Type string `json:"type"` // "json", "html", "text"
Template string `json:"template,omitempty"`
}
// HandlerConfig defines how to handle a route
type HandlerConfig struct { type HandlerConfig struct {
Type string `json:"type"` // "template", "workflow", "function", "redirect", "static" Type string `json:"type"` // "workflow", "template", "function", "redirect"
Target string `json:"target"` Target string `json:"target"`
Input map[string]interface{} `json:"input,omitempty"` Input map[string]interface{} `json:"input,omitempty"`
Transform []TransformConfig `json:"transform,omitempty"` Output map[string]interface{} `json:"output,omitempty"`
Conditions []ConditionConfig `json:"conditions,omitempty"` ErrorHandling *ErrorHandlingConfig `json:"error_handling,omitempty"`
Authentication *AuthConfig `json:"authentication,omitempty"`
Validation []string `json:"validation,omitempty"`
} }
// MiddlewareConfig defines middleware behavior // ErrorHandlingConfig defines error handling behavior
type ErrorHandlingConfig struct {
Retry *RetryConfig `json:"retry,omitempty"`
Fallback string `json:"fallback,omitempty"`
StatusCode int `json:"status_code,omitempty"`
Message string `json:"message,omitempty"`
}
// RetryConfig defines retry behavior
type RetryConfig struct {
MaxAttempts int `json:"max_attempts"`
Delay string `json:"delay"`
Backoff string `json:"backoff,omitempty"`
}
// AuthConfig defines authentication requirements
type AuthConfig struct {
Required bool `json:"required"`
Type string `json:"type,omitempty"`
Roles []string `json:"roles,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Redirect string `json:"redirect,omitempty"`
}
// MiddlewareConfig defines middleware
type MiddlewareConfig struct { type MiddlewareConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` // "auth", "cors", "logging", "ratelimit", "custom" Type string `json:"type"`
Priority int `json:"priority"` Priority int `json:"priority"`
Enabled bool `json:"enabled"` Config map[string]interface{} `json:"config,omitempty"`
Config map[string]interface{} `json:"config"` Functions []string `json:"functions,omitempty"`
Conditions []ConditionConfig `json:"conditions,omitempty"` Enabled bool `json:"enabled"`
Functions []string `json:"functions,omitempty"`
} }
// TemplateConfig defines HTML templates // TemplateConfig defines templates
type TemplateConfig struct { type TemplateConfig struct {
ID string `json:"id"` Type string `json:"type"` // "html", "text", "json"
Name string `json:"name"` Content string `json:"content,omitempty"`
Type string `json:"type"` // "html", "json", "xml", "text" Template string `json:"template,omitempty"` // Alternative field name for content
Template string `json:"template"` File string `json:"file,omitempty"`
Layout string `json:"layout,omitempty"` Variables map[string]interface{} `json:"variables,omitempty"`
Partials []string `json:"partials,omitempty"` Data map[string]interface{} `json:"data,omitempty"`
Data map[string]interface{} `json:"data,omitempty"` Partials map[string]string `json:"partials,omitempty"`
Scripts []ScriptConfig `json:"scripts,omitempty"` Helpers []string `json:"helpers,omitempty"`
Styles []StyleConfig `json:"styles,omitempty"` CacheEnabled bool `json:"cache_enabled"`
Components []ComponentConfig `json:"components,omitempty"`
} }
// WorkflowConfig defines workflow nodes and execution // WorkflowConfig defines workflows
type WorkflowConfig struct { type WorkflowConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description,omitempty"`
Version string `json:"version"` Version string `json:"version,omitempty"`
Nodes []NodeConfig `json:"nodes"` Nodes []NodeConfig `json:"nodes"`
Edges []EdgeConfig `json:"edges"` Edges []EdgeConfig `json:"edges"`
Variables map[string]interface{} `json:"variables"` Variables map[string]interface{} `json:"variables,omitempty"`
Triggers []TriggerConfig `json:"triggers"` Triggers []TriggerConfig `json:"triggers,omitempty"`
Options ExecutionOptions `json:"options"` SubWorkflows []SubWorkflowConfig `json:"sub_workflows,omitempty"`
JSONSchema *JSONSchemaConfig `json:"json_schema,omitempty"`
} }
// NodeConfig defines workflow node behavior // NodeConfig defines workflow nodes
type NodeConfig struct { type NodeConfig struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Function string `json:"function,omitempty"`
SubWorkflow string `json:"sub_workflow,omitempty"`
Input map[string]interface{} `json:"input,omitempty"`
Output map[string]interface{} `json:"output,omitempty"`
InputMapping map[string]interface{} `json:"input_mapping,omitempty"`
OutputMapping map[string]interface{} `json:"output_mapping,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
Conditions []ConditionConfig `json:"conditions,omitempty"`
ErrorHandling *ErrorHandlingConfig `json:"error_handling,omitempty"`
Timeout string `json:"timeout,omitempty"`
Retry *RetryConfig `json:"retry,omitempty"`
}
// EdgeConfig defines workflow edges
type EdgeConfig struct {
ID string `json:"id"`
From string `json:"from"`
To string `json:"to"`
Condition string `json:"condition,omitempty"`
Variables map[string]string `json:"variables,omitempty"`
Transform string `json:"transform,omitempty"`
Description string `json:"description,omitempty"`
}
// ConditionConfig defines conditional logic
type ConditionConfig struct {
Field string `json:"field"`
Operator string `json:"operator"`
Value interface{} `json:"value"`
Logic string `json:"logic,omitempty"` // "AND", "OR"
}
// TriggerConfig defines workflow triggers
type TriggerConfig struct {
Type string `json:"type"` // "http", "cron", "event"
Config map[string]interface{} `json:"config"`
Enabled bool `json:"enabled"`
Conditions []ConditionConfig `json:"conditions,omitempty"`
}
// SubWorkflowConfig defines sub-workflow mappings
type SubWorkflowConfig struct {
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
InputMapping map[string]interface{} `json:"input_mapping,omitempty"`
OutputMapping map[string]interface{} `json:"output_mapping,omitempty"`
}
// JSONSchemaConfig defines JSON schema validation
type JSONSchemaConfig struct {
Input map[string]interface{} `json:"input,omitempty"`
Output map[string]interface{} `json:"output,omitempty"`
}
// FunctionConfig defines custom functions
type FunctionConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` // "function", "http", "template", "validator", "transformer", "condition", "loop", "parallel" Description string `json:"description,omitempty"`
Description string `json:"description"` Type string `json:"type"` // "builtin", "custom", "external", "http"
Function string `json:"function,omitempty"` Handler string `json:"handler,omitempty"`
Input map[string]interface{} `json:"input,omitempty"` Method string `json:"method,omitempty"` // For HTTP functions
Output map[string]interface{} `json:"output,omitempty"` URL string `json:"url,omitempty"` // For HTTP functions
Headers map[string]string `json:"headers,omitempty"` // For HTTP functions
Code string `json:"code,omitempty"` // For custom code functions
Parameters []ParameterConfig `json:"parameters,omitempty"`
Returns []ParameterConfig `json:"returns,omitempty"`
Config map[string]interface{} `json:"config,omitempty"` Config map[string]interface{} `json:"config,omitempty"`
Conditions []ConditionConfig `json:"conditions,omitempty"` Async bool `json:"async"`
Retry *RetryConfig `json:"retry,omitempty"`
Timeout string `json:"timeout,omitempty"` Timeout string `json:"timeout,omitempty"`
} }
// EdgeConfig defines connections between nodes // ParameterConfig defines function parameters
type EdgeConfig struct { type ParameterConfig struct {
ID string `json:"id"` Name string `json:"name"`
From string `json:"from"` Type string `json:"type"`
To string `json:"to"` Required bool `json:"required"`
Conditions []ConditionConfig `json:"conditions,omitempty"` Default interface{} `json:"default,omitempty"`
Transform *TransformConfig `json:"transform,omitempty"` Description string `json:"description,omitempty"`
} Validation []string `json:"validation,omitempty"`
// FunctionConfig defines reusable functions
type FunctionConfig struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"` // "js", "expression", "http", "sql", "template"
Code string `json:"code,omitempty"`
URL string `json:"url,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
Query string `json:"query,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty"`
Response ResponseConfig `json:"response,omitempty"`
} }
// ValidatorConfig defines validation rules // ValidatorConfig defines validation rules
type ValidatorConfig struct { type ValidatorConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Type string `json:"type"` // "jsonschema", "custom", "regex"
Type string `json:"type"` // "required", "email", "phone", "regex", "length", "range", "custom" Field string `json:"field,omitempty"`
Field string `json:"field"` Schema interface{} `json:"schema,omitempty"`
Rules []ValidationRule `json:"rules"` Rules []ValidationRule `json:"rules,omitempty"`
Message string `json:"message"` Messages map[string]string `json:"messages,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty"` StrictMode bool `json:"strict_mode"`
AllowEmpty bool `json:"allow_empty"`
} }
// Supporting types // ValidationRule defines individual validation rules
type AuthConfig struct { type ValidationRule struct {
Required bool `json:"required"` Field string `json:"field"`
Type string `json:"type"` // "session", "token", "basic", "oauth" Type string `json:"type"`
Provider string `json:"provider,omitempty"` Required bool `json:"required"`
Redirect string `json:"redirect,omitempty"` Min interface{} `json:"min,omitempty"`
Permissions []string `json:"permissions,omitempty"` Max interface{} `json:"max,omitempty"`
Config map[string]interface{} `json:"config,omitempty"` Pattern string `json:"pattern,omitempty"`
} CustomRule string `json:"custom_rule,omitempty"`
Message string `json:"message,omitempty"`
type ResponseConfig struct {
Type string `json:"type"` // "json", "html", "redirect", "file", "stream"
Template string `json:"template,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
Status int `json:"status,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
ContentType string `json:"content_type,omitempty"`
}
type TransformConfig struct {
Type string `json:"type"` // "map", "filter", "reduce", "expression", "template"
Expression string `json:"expression,omitempty"`
Template string `json:"template,omitempty"`
Source string `json:"source,omitempty"`
Target string `json:"target,omitempty"`
Function string `json:"function,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty"`
}
type ConditionConfig struct {
Type string `json:"type"` // "equals", "contains", "exists", "expression", "function"
Field string `json:"field,omitempty"`
Value interface{} `json:"value,omitempty"`
Expression string `json:"expression,omitempty"`
Function string `json:"function,omitempty"`
Operator string `json:"operator,omitempty"` // "and", "or", "not"
}
type ScriptConfig struct {
Type string `json:"type"` // "inline", "file", "url"
Content string `json:"content"`
Src string `json:"src,omitempty"`
}
type StyleConfig struct {
Type string `json:"type"` // "inline", "file", "url"
Content string `json:"content"`
Href string `json:"href,omitempty"`
}
type ComponentConfig struct {
ID string `json:"id"`
Type string `json:"type"`
Template string `json:"template"`
Data map[string]interface{} `json:"data,omitempty"`
Props map[string]interface{} `json:"props,omitempty"`
}
type TriggerConfig struct {
Type string `json:"type"` // "http", "schedule", "event", "webhook"
Schedule string `json:"schedule,omitempty"`
Event string `json:"event,omitempty"`
Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"`
Conditions []ConditionConfig `json:"conditions,omitempty"` Conditions []ConditionConfig `json:"conditions,omitempty"`
} }
type ExecutionOptions struct { // Provider configuration for SMS/communication services
Async bool `json:"async"` type ProviderConfig struct {
Timeout string `json:"timeout,omitempty"` ID string `json:"id"`
Retry *RetryConfig `json:"retry,omitempty"` Name string `json:"name"`
Priority string `json:"priority,omitempty"` Type string `json:"type"` // "sms", "email", "push"
MaxNodes int `json:"max_nodes,omitempty"` Enabled bool `json:"enabled"`
Priority int `json:"priority"`
Config map[string]interface{} `json:"config"`
Countries []string `json:"countries,omitempty"`
RateLimit *RateLimitConfig `json:"rate_limit,omitempty"`
Costs map[string]float64 `json:"costs,omitempty"`
Features []string `json:"features,omitempty"`
Reliability float64 `json:"reliability"`
} }
type RetryConfig struct { // RateLimitConfig defines rate limiting
MaxAttempts int `json:"max_attempts"` type RateLimitConfig struct {
Delay string `json:"delay"` RequestsPerSecond int `json:"requests_per_second"`
BackoffType string `json:"backoff_type"` // "fixed", "exponential", "linear" BurstSize int `json:"burst_size"`
WindowSize int `json:"window_size"`
} }
type ValidationRule struct { // Country configuration for routing
Type string `json:"type"` type CountryConfig struct {
Value interface{} `json:"value,omitempty"` Code string `json:"code"`
Message string `json:"message"` Name string `json:"name"`
Parameters map[string]interface{} `json:"parameters,omitempty"` Providers []string `json:"providers"`
DefaultRate float64 `json:"default_rate"`
Regulations map[string]string `json:"regulations,omitempty"`
} }
// Runtime types for the JSON engine // Runtime types for the JSON engine
type JSONEngine struct { type JSONEngine struct {
app *fiber.App app *fiber.App
config *AppConfiguration workflowEngine *workflow.WorkflowEngine
templates map[string]*Template config *AppConfiguration
workflows map[string]*Workflow templates map[string]*Template
functions map[string]*Function workflows map[string]*Workflow
validators map[string]*Validator functions map[string]*Function
middleware map[string]*Middleware validators map[string]*Validator
data map[string]interface{} middleware map[string]*Middleware
data map[string]interface{}
} }
type Template struct { type Template struct {