package template import ( "bytes" "fmt" htmltemplate "html/template" "strings" texttemplate "text/template" "gateway/internal/model/notification/domain/enum" domtpl "gateway/internal/model/notification/domain/template" ) // Renderer renders notification templates from a registry. type Renderer struct { registry domtpl.Registry fallbacks []string } // NewRenderer builds a Renderer. Implements domain/template.Renderer. func NewRenderer(registry domtpl.Registry, fallbacks ...string) *Renderer { if registry == nil { registry = DefaultRegistry() } return &Renderer{ registry: registry, fallbacks: fallbacks, } } // Render produces subject/body/sms text for the given kind and locale. func (r *Renderer) Render(kind enum.NotifyKind, locale string, data map[string]any) (*domtpl.Rendered, error) { spec, err := r.registry.Lookup(kind, locale, r.fallbacks...) if err != nil { return nil, err } if err := validateRequiredVars(spec.RequiredVars, data); err != nil { return nil, err } subjectTpl, bodyTpl, smsTpl, err := resolveSpec(spec) if err != nil { return nil, err } out := &domtpl.Rendered{} if subjectTpl != "" { subject, err := executeTextTemplate("subject", subjectTpl, data) if err != nil { return nil, fmt.Errorf("template: render email subject: %w", err) } out.Subject = subject } if bodyTpl != "" { body, err := executeHTMLTemplate("body", bodyTpl, data) if err != nil { return nil, fmt.Errorf("template: render email body: %w", err) } out.Body = body } if smsTpl != "" { sms, err := executeTextTemplate("sms", smsTpl, data) if err != nil { return nil, fmt.Errorf("template: render sms: %w", err) } out.SMSText = sms } if out.Subject == "" && out.Body == "" && out.SMSText == "" { return nil, fmt.Errorf("template: kind %q has no renderable fields for locale %q", kind, locale) } return out, nil } func validateRequiredVars(required []string, data map[string]any) error { for _, key := range required { if _, ok := data[key]; !ok { return fmt.Errorf("template: missing required variable %q", key) } } return nil } func executeTextTemplate(name, tpl string, data map[string]any) (string, error) { t, err := texttemplate.New(name).Parse(tpl) if err != nil { return "", err } var buf bytes.Buffer if err := t.Execute(&buf, data); err != nil { return "", err } return strings.TrimSpace(buf.String()), nil } func executeHTMLTemplate(name, tpl string, data map[string]any) (string, error) { t, err := htmltemplate.New(name).Parse(tpl) if err != nil { return "", err } var buf bytes.Buffer if err := t.Execute(&buf, data); err != nil { return "", err } return strings.TrimSpace(buf.String()), nil }