mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-11 19:30:25 +08:00
update
This commit is contained in:
@@ -176,13 +176,13 @@
|
|||||||
Enterprise-grade security
|
Enterprise-grade security
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<form {{form_attributes}}>
|
||||||
<form method="post" action="/process?task_id={{task_id}}&next=true">
|
<div class="form-container p-4 bg-white shadow-md rounded">
|
||||||
<div class="form-row">
|
{{form_groups}}
|
||||||
{{form_fields}}
|
<div class="mt-4 flex flex-col items-center gap-2">
|
||||||
|
{{form_buttons}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit">🚀 Send Message</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -5,13 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/oarkflow/json"
|
"github.com/oarkflow/json"
|
||||||
|
|
||||||
"github.com/oarkflow/mq/dag"
|
"github.com/oarkflow/mq/dag"
|
||||||
|
"github.com/oarkflow/mq/renderer"
|
||||||
"github.com/oarkflow/mq/utils"
|
"github.com/oarkflow/mq/utils"
|
||||||
|
|
||||||
"github.com/oarkflow/jet"
|
"github.com/oarkflow/jet"
|
||||||
@@ -21,27 +21,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var contactFormSchema = map[string]any{}
|
renderer, err := renderer.GetFromFile("schema.json", "")
|
||||||
content, err := os.ReadFile("app/schema.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(content, &contactFormSchema); err != nil {
|
|
||||||
panic(fmt.Errorf("failed to parse JSON schema: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
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")))
|
fmt.Printf("Email notification workflow completed for task %s: %s\n", taskID, string(utils.RemoveRecursiveFromJSON(result.Payload, "html_content")))
|
||||||
}, mq.WithSyncMode(true))
|
}, mq.WithSyncMode(true))
|
||||||
|
|
||||||
// Add workflow nodes
|
// Add workflow nodes
|
||||||
// Note: Page nodes have no timeout by default, allowing users unlimited time for form input
|
// 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: string(contactFormLayout)}, true)
|
flow.AddNode(dag.Page, "Contact Form", "ContactForm", &JSONSchemaFormNode{renderer: renderer}, true)
|
||||||
flow.AddNode(dag.Function, "Validate Contact Data", "ValidateContact", &ValidateContactNode{})
|
flow.AddNode(dag.Function, "Validate Contact Data", "ValidateContact", &ValidateContactNode{})
|
||||||
flow.AddNode(dag.Function, "Check User Type", "CheckUserType", &CheckUserTypeNode{})
|
flow.AddNode(dag.Function, "Check User Type", "CheckUserType", &CheckUserTypeNode{})
|
||||||
flow.AddNode(dag.Function, "Send Welcome Email", "SendWelcomeEmail", &SendWelcomeEmailNode{})
|
flow.AddNode(dag.Function, "Send Welcome Email", "SendWelcomeEmail", &SendWelcomeEmailNode{})
|
||||||
@@ -84,179 +74,52 @@ func main() {
|
|||||||
flow.Start(context.Background(), "0.0.0.0:8084")
|
flow.Start(context.Background(), "0.0.0.0:8084")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigurableFormNode - Page node with JSONSchema-based fields and custom HTML layout
|
// JSONSchemaFormNode - Page node with JSONSchema-based fields and custom HTML layout
|
||||||
// Usage: Pass JSONSchema and HTML layout to the node for dynamic form rendering and validation
|
// Usage: Pass JSONSchema and HTML layout to the node for dynamic form rendering and validation
|
||||||
|
|
||||||
type ConfigurableFormNode struct {
|
type JSONSchemaFormNode struct {
|
||||||
dag.Operation
|
dag.Operation
|
||||||
Schema map[string]any // JSONSchema for fields and requirements
|
renderer *renderer.JSONSchemaRenderer
|
||||||
HTMLLayout string // HTML layout template with placeholders for fields
|
|
||||||
fieldsCache []fieldInfo // Cached field order and definitions
|
|
||||||
cacheInitialized bool // Whether cache is initialized
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fieldInfo caches field metadata for rendering
|
func (c *JSONSchemaFormNode) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
||||||
type fieldInfo struct {
|
if c.renderer == nil {
|
||||||
name string
|
schemaFile, ok := c.Payload.Data["schema_file"].(string)
|
||||||
order int
|
if !ok || schemaFile == "" {
|
||||||
def map[string]any
|
return mq.Result{Error: fmt.Errorf("schema_file is required for JSONSchemaFormNode"), Ctx: ctx}
|
||||||
definedIndex int // fallback to definition order
|
}
|
||||||
}
|
templateFile, _ := c.Payload.Data["template_file"].(string)
|
||||||
|
template, _ := c.Payload.Data["template"].(string)
|
||||||
|
|
||||||
func (c *ConfigurableFormNode) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
renderer, err := renderer.GetFromFile(schemaFile, template, templateFile)
|
||||||
var inputData map[string]any
|
|
||||||
if task.Payload != nil && len(task.Payload) > 0 {
|
|
||||||
if err := json.Unmarshal(task.Payload, &inputData); err == nil {
|
|
||||||
// Validate input against schema requirements
|
|
||||||
validationErrors := validateAgainstSchema(inputData, c.Schema)
|
|
||||||
if len(validationErrors) > 0 {
|
|
||||||
inputData["validation_error"] = validationErrors[0] // Show first error
|
|
||||||
bt, _ := json.Marshal(inputData)
|
|
||||||
return mq.Result{Payload: bt, Ctx: ctx, ConditionStatus: "invalid"}
|
|
||||||
}
|
|
||||||
return mq.Result{Payload: task.Payload, Ctx: ctx}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize cache if not done
|
|
||||||
if !c.cacheInitialized {
|
|
||||||
c.fieldsCache = parseFieldsFromSchema(c.Schema)
|
|
||||||
c.cacheInitialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render form fields from cached field order
|
|
||||||
formFieldsHTML := renderFieldsFromCache(c.fieldsCache)
|
|
||||||
parser := jet.NewWithMemory(jet.WithDelims("{{", "}}"))
|
|
||||||
layout := strings.Replace(c.HTMLLayout, "{{form_fields}}", formFieldsHTML, 1)
|
|
||||||
rs, err := parser.ParseTemplate(layout, map[string]any{
|
|
||||||
"task_id": ctx.Value("task_id"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mq.Result{Error: err, Ctx: ctx}
|
return mq.Result{Error: fmt.Errorf("failed to get renderer from file %s: %v", schemaFile, err), Ctx: ctx}
|
||||||
|
}
|
||||||
|
c.renderer = renderer
|
||||||
|
}
|
||||||
|
var inputData map[string]any
|
||||||
|
if len(task.Payload) > 0 {
|
||||||
|
if err := json.Unmarshal(task.Payload, &inputData); err != nil {
|
||||||
|
return mq.Result{Payload: task.Payload, Error: err, Ctx: ctx, ConditionStatus: "invalid"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templateData := map[string]any{
|
||||||
|
"task_id": ctx.Value("task_id"),
|
||||||
|
}
|
||||||
|
renderedHTML, err := c.renderer.RenderFields(templateData)
|
||||||
|
if err != nil {
|
||||||
|
return mq.Result{Payload: task.Payload, Error: err, Ctx: ctx, ConditionStatus: "invalid"}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, consts.ContentType, consts.TypeHtml)
|
ctx = context.WithValue(ctx, consts.ContentType, consts.TypeHtml)
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"html_content": rs,
|
"html_content": renderedHTML,
|
||||||
"step": "form",
|
"step": "form",
|
||||||
}
|
}
|
||||||
bt, _ := json.Marshal(data)
|
bt, _ := json.Marshal(data)
|
||||||
return mq.Result{Payload: bt, Ctx: ctx}
|
return mq.Result{Payload: bt, Ctx: ctx}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateAgainstSchema checks inputData against JSONSchema requirements
|
|
||||||
func validateAgainstSchema(inputData map[string]any, schema map[string]any) []string {
|
|
||||||
var errors []string
|
|
||||||
if _, ok := schema["properties"].(map[string]any); ok {
|
|
||||||
if required, ok := schema["required"].([]any); ok {
|
|
||||||
for _, field := range required {
|
|
||||||
fname := field.(string)
|
|
||||||
if val, exists := inputData[fname]; !exists || val == "" {
|
|
||||||
errors = append(errors, fname+" is required")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add more validation as needed (type, format, etc.)
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFieldsFromSchema extracts and sorts fields from schema, preserving order
|
|
||||||
func parseFieldsFromSchema(schema map[string]any) []fieldInfo {
|
|
||||||
var fields []fieldInfo
|
|
||||||
if props, ok := schema["properties"].(map[string]any); ok {
|
|
||||||
keyOrder := make([]string, 0, len(props))
|
|
||||||
for k := range props {
|
|
||||||
keyOrder = append(keyOrder, k)
|
|
||||||
}
|
|
||||||
for idx, name := range keyOrder {
|
|
||||||
field := props[name].(map[string]any)
|
|
||||||
order := -1
|
|
||||||
if o, ok := field["order"].(int); ok {
|
|
||||||
order = o
|
|
||||||
} else if o, ok := field["order"].(float64); ok {
|
|
||||||
order = int(o)
|
|
||||||
}
|
|
||||||
fields = append(fields, fieldInfo{name: name, order: order, def: field, definedIndex: idx})
|
|
||||||
}
|
|
||||||
if len(fields) > 1 {
|
|
||||||
sort.SliceStable(fields, func(i, j int) bool {
|
|
||||||
if fields[i].order != -1 && fields[j].order != -1 {
|
|
||||||
return fields[i].order < fields[j].order
|
|
||||||
} else if fields[i].order != -1 {
|
|
||||||
return true
|
|
||||||
} else if fields[j].order != -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return fields[i].definedIndex < fields[j].definedIndex
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderFieldsFromCache generates HTML for form fields from cached field order
|
|
||||||
func renderFieldsFromCache(fields []fieldInfo) string {
|
|
||||||
var html strings.Builder
|
|
||||||
for _, f := range fields {
|
|
||||||
label := f.name
|
|
||||||
if l, ok := f.def["title"].(string); ok {
|
|
||||||
label = l
|
|
||||||
}
|
|
||||||
// UI config
|
|
||||||
ui := map[string]any{}
|
|
||||||
if uiRaw, ok := f.def["ui"].(map[string]any); ok {
|
|
||||||
ui = uiRaw
|
|
||||||
}
|
|
||||||
// Control type
|
|
||||||
controlType := "input"
|
|
||||||
if ct, ok := ui["control"].(string); ok {
|
|
||||||
controlType = ct
|
|
||||||
}
|
|
||||||
// CSS classes
|
|
||||||
classes := "form-group"
|
|
||||||
if cls, ok := ui["class"].(string); ok {
|
|
||||||
classes = cls
|
|
||||||
}
|
|
||||||
// Name attribute
|
|
||||||
nameAttr := f.name
|
|
||||||
if n, ok := ui["name"].(string); ok {
|
|
||||||
nameAttr = n
|
|
||||||
}
|
|
||||||
// Type
|
|
||||||
typeStr := "text"
|
|
||||||
if t, ok := f.def["type"].(string); ok {
|
|
||||||
switch t {
|
|
||||||
case "string":
|
|
||||||
typeStr = "text"
|
|
||||||
case "email":
|
|
||||||
typeStr = "email"
|
|
||||||
case "number":
|
|
||||||
typeStr = "number"
|
|
||||||
case "textarea":
|
|
||||||
typeStr = "textarea"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Render control
|
|
||||||
if controlType == "textarea" || typeStr == "textarea" {
|
|
||||||
html.WriteString(fmt.Sprintf(`<div class="%s"><label for="%s">%s:</label><textarea id="%s" name="%s" placeholder="%s"></textarea></div>`, classes, nameAttr, label, nameAttr, nameAttr, label))
|
|
||||||
} else if controlType == "select" {
|
|
||||||
// Optionally support select with options in ui["options"]
|
|
||||||
optionsHTML := ""
|
|
||||||
if opts, ok := ui["options"].([]any); ok {
|
|
||||||
for _, opt := range opts {
|
|
||||||
optStr := fmt.Sprintf("%v", opt)
|
|
||||||
optionsHTML += fmt.Sprintf(`<option value="%s">%s</option>`, optStr, optStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html.WriteString(fmt.Sprintf(`<div class="%s"><label for="%s">%s:</label><select id="%s" name="%s">%s</select></div>`, classes, nameAttr, label, nameAttr, nameAttr, optionsHTML))
|
|
||||||
} else {
|
|
||||||
html.WriteString(fmt.Sprintf(`<div class="%s"><label for="%s">%s:</label><input type="%s" id="%s" name="%s" placeholder="%s"></div>`, classes, nameAttr, label, typeStr, nameAttr, nameAttr, label))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return html.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateContactNode - Validates contact form data
|
// ValidateContactNode - Validates contact form data
|
||||||
type ValidateContactNode struct {
|
type ValidateContactNode struct {
|
||||||
dag.Operation
|
dag.Operation
|
||||||
|
105
examples/schema.json
Normal file
105
examples/schema.json
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"first_name": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "First Name",
|
||||||
|
"order": 1,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "first_name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Last Name",
|
||||||
|
"order": 2,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "last_name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "email",
|
||||||
|
"title": "Email Address",
|
||||||
|
"order": 3,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "email",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "email"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user_type": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "User Type",
|
||||||
|
"order": 4,
|
||||||
|
"ui": {
|
||||||
|
"element": "select",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "user_type",
|
||||||
|
"options": [ "new", "premium", "standard" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Priority Level",
|
||||||
|
"order": 5,
|
||||||
|
"ui": {
|
||||||
|
"element": "select",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "priority",
|
||||||
|
"options": [ "low", "medium", "high", "urgent" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Subject",
|
||||||
|
"order": 6,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "subject"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "textarea",
|
||||||
|
"title": "Message",
|
||||||
|
"order": 7,
|
||||||
|
"ui": {
|
||||||
|
"element": "textarea",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [ "first_name", "last_name", "email", "user_type", "priority", "subject", "message" ],
|
||||||
|
"form": {
|
||||||
|
"class": "form-horizontal",
|
||||||
|
"action": "/process?task_id={{task_id}}&next=true",
|
||||||
|
"method": "POST",
|
||||||
|
"enctype": "application/x-www-form-urlencoded",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"title": "User Information",
|
||||||
|
"fields": [ "first_name", "last_name", "email" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ticket Details",
|
||||||
|
"fields": [ "user_type", "priority", "subject", "message" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"submit": {
|
||||||
|
"type": "submit",
|
||||||
|
"label": "Submit",
|
||||||
|
"class": "btn btn-primary"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"type": "reset",
|
||||||
|
"label": "Reset",
|
||||||
|
"class": "btn btn-secondary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1022,16 +1022,17 @@ func getPlaceholder(schema *jsonschema.Schema) string {
|
|||||||
func getFieldContent(field FieldInfo) string {
|
func getFieldContent(field FieldInfo) string {
|
||||||
// Check for content in UI first
|
// Check for content in UI first
|
||||||
if field.Schema.UI != nil {
|
if field.Schema.UI != nil {
|
||||||
|
if content, ok := field.Schema.UI["value"].(string); ok {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
if content, ok := field.Schema.UI["defaultValue"].(string); ok {
|
||||||
|
return content
|
||||||
|
}
|
||||||
if content, ok := field.Schema.UI["content"].(string); ok {
|
if content, ok := field.Schema.UI["content"].(string); ok {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use title as fallback for some elements
|
|
||||||
if field.Schema.Title != nil {
|
|
||||||
return *field.Schema.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1202,18 +1203,40 @@ func GetCacheSize() int {
|
|||||||
return len(cache)
|
return len(cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFromBytes(schemaContent []byte, template string) (*RequestSchemaTemplate, error) {
|
func GetFromBytes(schemaContent []byte, template string, templateFiles ...string) (*RequestSchemaTemplate, error) {
|
||||||
|
template = strings.TrimSpace(template)
|
||||||
compiler := jsonschema.NewCompiler()
|
compiler := jsonschema.NewCompiler()
|
||||||
schema, err := compiler.Compile(schemaContent)
|
schema, err := compiler.Compile(schemaContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error compiling schema: %w", err)
|
return nil, fmt.Errorf("error compiling schema: %w", err)
|
||||||
}
|
}
|
||||||
|
var htmlLayout []byte
|
||||||
templatePath := fmt.Sprintf("%s/%s.html", BaseTemplateDir, template)
|
if len(templateFiles) > 0 && templateFiles[0] != "" {
|
||||||
htmlLayout, err := os.ReadFile(templatePath)
|
templateFile := templateFiles[0]
|
||||||
|
if !strings.Contains(templateFile, "/") {
|
||||||
|
template = fmt.Sprintf("%s/%s", BaseTemplateDir, templateFile)
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(templateFile, ".html") {
|
||||||
|
template += ".html"
|
||||||
|
}
|
||||||
|
htmlLayout, err = os.ReadFile(templateFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load template: %w", err)
|
return nil, fmt.Errorf("failed to load template: %w", err)
|
||||||
}
|
}
|
||||||
|
} else if template != "" {
|
||||||
|
htmlLayout = []byte(template)
|
||||||
|
} else {
|
||||||
|
htmlLayout = []byte(`
|
||||||
|
<form {{form_attributes}}>
|
||||||
|
<div>
|
||||||
|
{{form_groups}}
|
||||||
|
<div>
|
||||||
|
{{form_buttons}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
renderer := NewJSONSchemaRenderer(schema, string(htmlLayout))
|
renderer := NewJSONSchemaRenderer(schema, string(htmlLayout))
|
||||||
cachedTemplate := &RequestSchemaTemplate{
|
cachedTemplate := &RequestSchemaTemplate{
|
||||||
@@ -1223,8 +1246,12 @@ func GetFromBytes(schemaContent []byte, template string) (*RequestSchemaTemplate
|
|||||||
return cachedTemplate, nil
|
return cachedTemplate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFromFile(schemaPath, template string) (*JSONSchemaRenderer, error) {
|
func GetFromFile(schemaPath, template string, templateFiles ...string) (*JSONSchemaRenderer, error) {
|
||||||
path := fmt.Sprintf("%s:%s", schemaPath, template)
|
path := schemaPath
|
||||||
|
if len(templateFiles) > 0 {
|
||||||
|
templateFile := templateFiles[0]
|
||||||
|
path += fmt.Sprintf(":%s", templateFile)
|
||||||
|
}
|
||||||
mu.RLock()
|
mu.RLock()
|
||||||
if cached, exists := cache[path]; exists {
|
if cached, exists := cache[path]; exists {
|
||||||
mu.RUnlock()
|
mu.RUnlock()
|
||||||
@@ -1240,7 +1267,7 @@ func GetFromFile(schemaPath, template string) (*JSONSchemaRenderer, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading schema file: %w", err)
|
return nil, fmt.Errorf("error reading schema file: %w", err)
|
||||||
}
|
}
|
||||||
schemaRenderer, err := GetFromBytes(schemaContent, template)
|
schemaRenderer, err := GetFromBytes(schemaContent, template, templateFiles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating renderer from bytes: %w", err)
|
return nil, fmt.Errorf("error creating renderer from bytes: %w", err)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user