Files
atomctl/pkg/ast/route/renderer.go
2025-12-17 23:42:40 +08:00

266 lines
7.2 KiB
Go

package route
import (
"bytes"
"text/template"
"github.com/Masterminds/sprig/v3"
log "github.com/sirupsen/logrus"
)
// TemplateRenderer defines the interface for template rendering operations
type TemplateRenderer interface {
Render(data RenderData) ([]byte, error)
Validate() error
GetTemplateInfo() TemplateInfo
}
// TemplateInfo provides metadata about the template
type TemplateInfo struct {
Name string
Version string
Functions []string
Options []string
Size int
}
// RouteRenderer implements TemplateRenderer for route generation
type RouteRenderer struct {
template *template.Template
manualTemplate *template.Template
info TemplateInfo
logger *log.Entry
}
// NewRouteRenderer creates a new RouteRenderer instance with proper initialization
func NewRouteRenderer() *RouteRenderer {
renderer := &RouteRenderer{
logger: log.WithField("module", "route-renderer"),
info: TemplateInfo{
Name: "router",
Version: "1.0.0",
Functions: []string{
"sprig",
"template",
"custom",
},
Options: []string{
"missingkey=error",
},
},
}
// Initialize template with error handling
if err := renderer.initializeTemplate(); err != nil {
renderer.logger.WithError(err).Error("Failed to initialize template")
return nil
}
if err := renderer.initializeManualTemplate(); err != nil {
renderer.logger.WithError(err).Error("Failed to initialize manual template")
return nil
}
renderer.info.Size = len(routeTpl)
renderer.logger.WithFields(log.Fields{
"template_size": renderer.info.Size,
"version": renderer.info.Version,
}).Info("Route renderer initialized successfully")
return renderer
}
func (r *RouteRenderer) initializeManualTemplate() error {
// Create template with sprig functions and custom options
tmpl := template.New(r.info.Name + "manual").
Funcs(sprig.FuncMap()).
Option("missingkey=error")
// Parse the template
parsedTmpl, err := tmpl.Parse(routeManualTpl)
if err != nil {
return WrapError(err, "failed to parse route template")
}
r.manualTemplate = parsedTmpl
return nil
}
// initializeTemplate sets up the template with proper functions and options
func (r *RouteRenderer) initializeTemplate() error {
// Create template with sprig functions and custom options
tmpl := template.New(r.info.Name).
Funcs(sprig.FuncMap()).
Option("missingkey=error")
// Parse the template
parsedTmpl, err := tmpl.Parse(routeTpl)
if err != nil {
return WrapError(err, "failed to parse route template")
}
r.template = parsedTmpl
return nil
}
// Render renders the template with the provided data
func (r *RouteRenderer) RenderManual(data RenderData) ([]byte, error) {
// Validate input data
if err := r.validateRenderData(data); err != nil {
return nil, err
}
// Create buffer for rendering
var buf bytes.Buffer
buf.Grow(estimatedBufferSize(data)) // Pre-allocate buffer for better performance
// Execute template with error handling
if err := r.manualTemplate.Execute(&buf, data); err != nil {
r.logger.WithError(err).WithFields(log.Fields{
"package_name": data.PackageName,
"routes_count": len(data.Routes),
}).Error("Template execution failed")
return nil, WrapError(err, "template execution failed for package: %s", data.PackageName)
}
// Validate rendered content
result := buf.Bytes()
if len(result) == 0 {
return nil, NewRouteError(ErrTemplateFailed, "rendered content is empty for package: %s", data.PackageName)
}
r.logger.WithFields(log.Fields{
"package_name": data.PackageName,
"routes_count": len(data.Routes),
"content_length": len(result),
}).Debug("Template rendered successfully")
return result, nil
}
// Render renders the template with the provided data
func (r *RouteRenderer) Render(data RenderData) ([]byte, error) {
// Validate input data
if err := r.validateRenderData(data); err != nil {
return nil, err
}
// Create buffer for rendering
var buf bytes.Buffer
buf.Grow(estimatedBufferSize(data)) // Pre-allocate buffer for better performance
// Execute template with error handling
if err := r.template.Execute(&buf, data); err != nil {
r.logger.WithError(err).WithFields(log.Fields{
"package_name": data.PackageName,
"routes_count": len(data.Routes),
}).Error("Template execution failed")
return nil, WrapError(err, "template execution failed for package: %s", data.PackageName)
}
// Validate rendered content
result := buf.Bytes()
if len(result) == 0 {
return nil, NewRouteError(ErrTemplateFailed, "rendered content is empty for package: %s", data.PackageName)
}
r.logger.WithFields(log.Fields{
"package_name": data.PackageName,
"routes_count": len(data.Routes),
"content_length": len(result),
}).Debug("Template rendered successfully")
return result, nil
}
// Validate checks if the renderer is properly configured
func (r *RouteRenderer) Validate() error {
if r.template == nil {
return NewRouteError(ErrTemplateFailed, "template is not initialized")
}
if r.info.Name == "" {
return NewRouteError(ErrTemplateFailed, "template name is not set")
}
return nil
}
// GetTemplateInfo returns metadata about the template
func (r *RouteRenderer) GetTemplateInfo() TemplateInfo {
return r.info
}
// validateRenderData validates the input data before rendering
func (r *RouteRenderer) validateRenderData(data RenderData) error {
if data.PackageName == "" {
return NewRouteError(ErrInvalidInput, "package name cannot be empty")
}
if len(data.Routes) == 0 {
return NewRouteError(ErrNoRoutes, "no routes to render for package: %s", data.PackageName)
}
// Validate that all routes have required fields
for controllerName, routes := range data.Routes {
if controllerName == "" {
return NewRouteError(ErrInvalidInput, "controller name cannot be empty")
}
for i, route := range routes {
if route.Method == "" {
return NewRouteError(
ErrInvalidInput,
"route method cannot be empty for controller %s, route %d",
controllerName,
i,
)
}
if route.Route == "" {
return NewRouteError(
ErrInvalidInput,
"route path cannot be empty for controller %s, route %d",
controllerName,
i,
)
}
}
}
return nil
}
// estimatedBufferSize calculates the estimated buffer size needed for rendering
func estimatedBufferSize(data RenderData) int {
// Base size for package structure
baseSize := 1024 // ~1KB for base package structure
// Add size based on routes
routesSize := len(data.Routes) * 256 // ~256 bytes per route
// Add size based on imports
importsSize := len(data.Imports) * 64 // ~64 bytes per import
// Add size based on controllers
controllersSize := len(data.Controllers) * 128 // ~128 bytes per controller
return baseSize + routesSize + importsSize + controllersSize
}
// renderTemplate is the legacy function for backward compatibility
// Use NewRouteRenderer().Render() for new code
func renderTemplate(data RenderData) ([]byte, error) {
renderer := NewRouteRenderer()
if renderer == nil {
return nil, NewRouteError(ErrTemplateFailed, "failed to create route renderer")
}
return renderer.Render(data)
}
func renderManualTemplate(data RenderData) ([]byte, error) {
renderer := NewRouteRenderer()
if renderer == nil {
return nil, NewRouteError(ErrTemplateFailed, "failed to create route renderer")
}
return renderer.RenderManual(data)
}