diff --git a/examples/app/server.go b/examples/app/server.go
index e204230..c2dd27f 100644
--- a/examples/app/server.go
+++ b/examples/app/server.go
@@ -5,6 +5,8 @@ import (
"fmt"
"net/http"
"os"
+
+ "github.com/oarkflow/mq/renderer"
)
func main() {
@@ -18,6 +20,7 @@ func main() {
fmt.Printf("Error parsing schema: %v\n", err)
return
}
+
http.Handle("/form.css", http.FileServer(http.Dir("templates")))
http.HandleFunc("/render", func(w http.ResponseWriter, r *http.Request) {
templateName := r.URL.Query().Get("template")
@@ -32,7 +35,7 @@ func main() {
return
}
- renderer := NewJSONSchemaRenderer(schema, string(htmlLayout))
+ renderer := renderer.NewJSONSchemaRenderer(schema, string(htmlLayout))
// Set template data for dynamic interpolation
templateData := map[string]interface{}{
diff --git a/examples/email/contact-form.html b/examples/email/contact-form.html
new file mode 100644
index 0000000..d74180d
--- /dev/null
+++ b/examples/email/contact-form.html
@@ -0,0 +1,190 @@
+
+
+
+
+ Contact Us - Email Notification System
+
+
+
+
+
+
+
+
diff --git a/examples/email/error.html b/examples/email/error.html
new file mode 100644
index 0000000..9935457
--- /dev/null
+++ b/examples/email/error.html
@@ -0,0 +1,134 @@
+
+
+
+
+ Email Error
+
+
+
+
+
+
❌
+
Email Processing Error
+
+
+ {{error_message}}
+
+
+ {{if error_field}}
+
+ 🎯 Error Field: {{error_field}}
+ ⚡ Action Required: Please correct the highlighted field and try again.
+ 💡 Tip: Make sure all required fields are properly filled out.
+
+ {{end}}
+
+ {{if retry_suggested}}
+
+ ⚠️ Temporary Issue: This appears to be a temporary system issue.
+ Please try sending your message again in a few moments.
+ 🔄 Auto-Retry: Our system will automatically retry failed deliveries.
+
+ {{end}}
+
+
+
+
+ 🔄 DAG Error Handler | Email Notification Workflow Failed
+ Our advanced routing system ensures reliable message delivery.
+
+
+
+
+
diff --git a/examples/email/success.html b/examples/email/success.html
new file mode 100644
index 0000000..f26e89d
--- /dev/null
+++ b/examples/email/success.html
@@ -0,0 +1,210 @@
+
+
+
+
+ Message Sent Successfully
+
+
+
+
+
+
✅
+
Message Sent Successfully!
+
+
{{email_status}}
+
+
+
+
👤 Recipient
+
{{full_name}}
+
+
+
📧 Email Address
+
{{email}}
+
+
+
🆔 Email ID
+
{{email_id}}
+
+
+
⏰ Sent At
+
{{sent_at}}
+
+
+
📨 Email Type
+
{{email_type}}
+
+
+
👥 User Type
+
{{user_type}}
+
+
+
🚨 Priority
+
{{priority}}
+
+
+
🚚 Delivery
+
{{delivery_estimate}}
+
+
+
+
+
📋 Subject:
+
+ {{subject}}
+
+
💬 Message ({{message_length}} chars):
+
+ "{{message}}"
+
+
+
+
+
+
+ 🔄 Workflow Details:
+ Gateway: {{gateway}} | Template: {{email_template}} | Processed: {{processed_at}}
+ This message was processed through our advanced DAG workflow system with conditional routing.
+
+
+
+
+
diff --git a/examples/email_notification_dag.go b/examples/email_notification_dag.go
index 72cff23..7a6dfb4 100644
--- a/examples/email_notification_dag.go
+++ b/examples/email_notification_dag.go
@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
+ "os"
"regexp"
"sort"
"strings"
@@ -20,145 +21,19 @@ import (
)
func main() {
- contactFormSchema := map[string]any{
- "properties": map[string]any{
- "first_name": map[string]any{
- "type": "string",
- "title": "👤 First Name",
- "order": 1,
- "ui": map[string]any{
- "control": "input",
- "class": "form-group",
- "name": "first_name",
- },
- },
- "last_name": map[string]any{
- "type": "string",
- "title": "👤 Last Name",
- "order": 2,
- "ui": map[string]any{
- "control": "input",
- "class": "form-group",
- "name": "last_name",
- },
- },
- "email": map[string]any{
- "type": "email",
- "title": "📧 Email Address",
- "order": 3,
- "ui": map[string]any{
- "control": "input",
- "class": "form-group",
- "name": "email",
- },
- },
- "user_type": map[string]any{
- "type": "string",
- "title": "👥 User Type",
- "order": 4,
- "ui": map[string]any{
- "control": "select",
- "class": "form-group",
- "name": "user_type",
- "options": []any{"new", "premium", "standard"},
- },
- },
- "priority": map[string]any{
- "type": "string",
- "title": "🚨 Priority Level",
- "order": 5,
- "ui": map[string]any{
- "control": "select",
- "class": "form-group",
- "name": "priority",
- "options": []any{"low", "medium", "high", "urgent"},
- },
- },
- "subject": map[string]any{
- "type": "string",
- "title": "📋 Subject",
- "order": 6,
- "ui": map[string]any{
- "control": "input",
- "class": "form-group",
- "name": "subject",
- },
- },
- "message": map[string]any{
- "type": "textarea",
- "title": "💬 Message",
- "order": 7,
- "ui": map[string]any{
- "control": "textarea",
- "class": "form-group",
- "name": "message",
- },
- },
- },
- "required": []any{"first_name", "last_name", "email", "user_type", "priority", "subject", "message"},
+ var contactFormSchema = map[string]any{}
+ content, err := os.ReadFile("app/schema.json")
+ if err != nil {
+ panic(err)
+ }
+ if err := json.Unmarshal(content, &contactFormSchema); err != nil {
+ panic(fmt.Errorf("failed to parse JSON schema: %w", err))
}
- contactFormLayout := `
-
-
-
- Contact Us - Email Notification System
-
-
-
-
-
-`
+ contactFormLayout, err := os.ReadFile("email/contact-form.html")
+ if err != nil {
+ panic(err)
+ }
flow := dag.NewDAG("Email Notification System", "email-notification", func(taskID string, result mq.Result) {
fmt.Printf("Email notification workflow completed for task %s: %s\n", taskID, string(utils.RemoveRecursiveFromJSON(result.Payload, "html_content")))
@@ -166,7 +41,7 @@ func main() {
// Add workflow nodes
// Note: Page nodes have no timeout by default, allowing users unlimited time for form input
- flow.AddNode(dag.Page, "Contact Form", "ContactForm", &ConfigurableFormNode{Schema: contactFormSchema, HTMLLayout: contactFormLayout}, true)
+ flow.AddNode(dag.Page, "Contact Form", "ContactForm", &ConfigurableFormNode{Schema: contactFormSchema, HTMLLayout: string(contactFormLayout)}, true)
flow.AddNode(dag.Function, "Validate Contact Data", "ValidateContact", &ValidateContactNode{})
flow.AddNode(dag.Function, "Check User Type", "CheckUserType", &CheckUserTypeNode{})
flow.AddNode(dag.Function, "Send Welcome Email", "SendWelcomeEmail", &SendWelcomeEmailNode{})
@@ -631,190 +506,13 @@ func (s *SuccessPageNode) ProcessTask(ctx context.Context, task *mq.Task) mq.Res
return mq.Result{Error: err, Ctx: ctx}
}
- htmlTemplate := `
-
-
-
- Message Sent Successfully
-
-
-
-
-
✅
-
Message Sent Successfully!
-
-
{{email_status}}
-
-
-
-
👤 Recipient
-
{{full_name}}
-
-
-
📧 Email Address
-
{{email}}
-
-
-
🆔 Email ID
-
{{email_id}}
-
-
-
⏰ Sent At
-
{{sent_at}}
-
-
-
📨 Email Type
-
{{email_type}}
-
-
-
👥 User Type
-
{{user_type}}
-
-
-
🚨 Priority
-
{{priority}}
-
-
-
🚚 Delivery
-
{{delivery_estimate}}
-
-
-
-
-
📋 Subject:
-
- {{subject}}
-
-
💬 Message ({{message_length}} chars):
-
- "{{message}}"
-
-
-
-
-
-
- 🔄 Workflow Details:
- Gateway: {{gateway}} | Template: {{email_template}} | Processed: {{processed_at}}
- This message was processed through our advanced DAG workflow system with conditional routing.
-
-
-
-`
+ htmlTemplate, err := os.ReadFile("email/success.html")
+ if err != nil {
+ return mq.Result{Error: fmt.Errorf("failed to read success template: %v", err)}
+ }
parser := jet.NewWithMemory(jet.WithDelims("{{", "}}"))
- rs, err := parser.ParseTemplate(htmlTemplate, inputData)
+ rs, err := parser.ParseTemplate(string(htmlTemplate), inputData)
if err != nil {
return mq.Result{Error: err, Ctx: ctx}
}
@@ -853,123 +551,10 @@ func (e *EmailErrorPageNode) ProcessTask(ctx context.Context, task *mq.Task) mq.
errorMessage = "An unknown error occurred"
}
- htmlTemplate := `
-
-
-
- Email Error
-
-
-
-
-
❌
-
Email Processing Error
-
-
- {{error_message}}
-
-
- {{if error_field}}
-
- 🎯 Error Field: {{error_field}}
- ⚡ Action Required: Please correct the highlighted field and try again.
- 💡 Tip: Make sure all required fields are properly filled out.
-
- {{end}}
-
- {{if retry_suggested}}
-
- ⚠️ Temporary Issue: This appears to be a temporary system issue.
- Please try sending your message again in a few moments.
- 🔄 Auto-Retry: Our system will automatically retry failed deliveries.
-
- {{end}}
-
-
-
-
- 🔄 DAG Error Handler | Email Notification Workflow Failed
- Our advanced routing system ensures reliable message delivery.
-
-
-
-`
+ htmlTemplate, err := os.ReadFile("email/error.html")
+ if err != nil {
+ return mq.Result{Error: fmt.Errorf("failed to read error template: %v", err)}
+ }
parser := jet.NewWithMemory(jet.WithDelims("{{", "}}"))
templateData := map[string]any{
@@ -978,7 +563,7 @@ func (e *EmailErrorPageNode) ProcessTask(ctx context.Context, task *mq.Task) mq.
"retry_suggested": inputData["retry_suggested"],
}
- rs, err := parser.ParseTemplate(htmlTemplate, templateData)
+ rs, err := parser.ParseTemplate(string(htmlTemplate), templateData)
if err != nil {
return mq.Result{Error: err, Ctx: ctx}
}
diff --git a/examples/app/jsonschema_renderer.go b/renderer/schema.go
similarity index 86%
rename from examples/app/jsonschema_renderer.go
rename to renderer/schema.go
index 3972acf..d1f07dc 100644
--- a/examples/app/jsonschema_renderer.go
+++ b/renderer/schema.go
@@ -1,4 +1,4 @@
-package main
+package renderer
import (
"bytes"
@@ -11,7 +11,7 @@ import (
type FieldInfo struct {
Name string
Order int
- Definition map[string]interface{}
+ Definition map[string]any
}
// GroupInfo represents metadata for a group extracted from JSONSchema
@@ -29,23 +29,23 @@ type GroupTitle struct {
// JSONSchemaRenderer is responsible for rendering HTML fields based on JSONSchema
type JSONSchemaRenderer struct {
- Schema map[string]interface{}
+ Schema map[string]any
HTMLLayout string
- TemplateData map[string]interface{} // Data for template interpolation
- cachedHTML string // Cached rendered HTML
+ TemplateData map[string]any // Data for template interpolation
+ cachedHTML string // Cached rendered HTML
}
// NewJSONSchemaRenderer creates a new instance of JSONSchemaRenderer
-func NewJSONSchemaRenderer(schema map[string]interface{}, htmlLayout string) *JSONSchemaRenderer {
+func NewJSONSchemaRenderer(schema map[string]any, htmlLayout string) *JSONSchemaRenderer {
return &JSONSchemaRenderer{
Schema: schema,
HTMLLayout: htmlLayout,
- TemplateData: make(map[string]interface{}),
+ TemplateData: make(map[string]any),
}
}
// SetTemplateData sets the data used for template interpolation
-func (r *JSONSchemaRenderer) SetTemplateData(data map[string]interface{}) {
+func (r *JSONSchemaRenderer) SetTemplateData(data map[string]any) {
r.TemplateData = data
}
@@ -93,7 +93,7 @@ func (r *JSONSchemaRenderer) RenderFields() (string, error) {
groupHTML.WriteString(renderGroup(group))
}
- formConfig := r.Schema["form"].(map[string]interface{})
+ formConfig := r.Schema["form"].(map[string]any)
formClass, _ := formConfig["class"].(string)
formAction, _ := formConfig["action"].(string)
formMethod, _ := formConfig["method"].(string)
@@ -128,20 +128,20 @@ func (r *JSONSchemaRenderer) RenderFields() (string, error) {
}
// parseGroupsFromSchema extracts and sorts groups and fields from schema
-func parseGroupsFromSchema(schema map[string]interface{}) []GroupInfo {
- formConfig, ok := schema["form"].(map[string]interface{})
+func parseGroupsFromSchema(schema map[string]any) []GroupInfo {
+ formConfig, ok := schema["form"].(map[string]any)
if !ok {
return nil
}
- properties, ok := schema["properties"].(map[string]interface{})
+ properties, ok := schema["properties"].(map[string]any)
if !ok {
return nil
}
// Get required fields from schema root
var requiredFields map[string]bool = make(map[string]bool)
- if reqFields, ok := schema["required"].([]interface{}); ok {
+ if reqFields, ok := schema["required"].([]any); ok {
for _, field := range reqFields {
if fieldName, ok := field.(string); ok {
requiredFields[fieldName] = true
@@ -150,12 +150,12 @@ func parseGroupsFromSchema(schema map[string]interface{}) []GroupInfo {
}
var groups []GroupInfo
- for _, group := range formConfig["groups"].([]interface{}) {
- groupMap := group.(map[string]interface{})
+ for _, group := range formConfig["groups"].([]any) {
+ groupMap := group.(map[string]any)
// Parse group title
var groupTitle GroupTitle
- if titleMap, ok := groupMap["title"].(map[string]interface{}); ok {
+ if titleMap, ok := groupMap["title"].(map[string]any); ok {
if text, ok := titleMap["text"].(string); ok {
groupTitle.Text = text
}
@@ -168,15 +168,15 @@ func parseGroupsFromSchema(schema map[string]interface{}) []GroupInfo {
groupClass, _ := groupMap["class"].(string)
var fields []FieldInfo
- for _, fieldName := range groupMap["fields"].([]interface{}) {
- fieldDef := properties[fieldName.(string)].(map[string]interface{})
+ for _, fieldName := range groupMap["fields"].([]any) {
+ fieldDef := properties[fieldName.(string)].(map[string]any)
order := 0
if ord, exists := fieldDef["order"].(int); exists {
order = ord
}
// Add required field info to field definition
- fieldDefCopy := make(map[string]interface{})
+ fieldDefCopy := make(map[string]any)
for k, v := range fieldDef {
fieldDefCopy[k] = v
}
@@ -243,7 +243,7 @@ var fieldTemplates = map[string]string{
}
func renderField(field FieldInfo) string {
- ui, ok := field.Definition["ui"].(map[string]interface{})
+ ui, ok := field.Definition["ui"].(map[string]any)
if !ok {
return ""
}
@@ -279,7 +279,7 @@ func renderField(field FieldInfo) string {
}
}
- data := map[string]interface{}{
+ data := map[string]any{
"Class": class,
"Name": name,
"Title": template.HTML(titleHTML),
@@ -302,7 +302,7 @@ func renderField(field FieldInfo) string {
tmpl.Execute(&buf, data)
return buf.String()
case "select":
- options, _ := ui["options"].([]interface{})
+ options, _ := ui["options"].([]any)
var optionsHTML bytes.Buffer
for _, option := range options {
optionsHTML.WriteString(fmt.Sprintf(`%v `, option, option))
@@ -338,10 +338,10 @@ func renderField(field FieldInfo) string {
// Template for buttons
var buttonTemplate = `{{.Label}} `
-func renderButtons(formConfig map[string]interface{}) string {
+func renderButtons(formConfig map[string]any) string {
var buttonsHTML bytes.Buffer
- if submitConfig, ok := formConfig["submit"].(map[string]interface{}); ok {
- data := map[string]interface{}{
+ if submitConfig, ok := formConfig["submit"].(map[string]any); ok {
+ data := map[string]any{
"Type": submitConfig["type"],
"Class": submitConfig["class"],
"Label": submitConfig["label"],
@@ -349,8 +349,8 @@ func renderButtons(formConfig map[string]interface{}) string {
tmpl := template.Must(template.New("button").Parse(buttonTemplate))
tmpl.Execute(&buttonsHTML, data)
}
- if resetConfig, ok := formConfig["reset"].(map[string]interface{}); ok {
- data := map[string]interface{}{
+ if resetConfig, ok := formConfig["reset"].(map[string]any); ok {
+ data := map[string]any{
"Type": resetConfig["type"],
"Class": resetConfig["class"],
"Label": resetConfig["label"],