package main import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "text/template" "time" "gopkg.in/yaml.v2" ) // ServiceConfig represents the main configuration structure type ServiceConfig struct { Services map[string]Service `yaml:"services"` Global GlobalConfig `yaml:"global,omitempty"` } // GlobalConfig contains global configuration type GlobalConfig struct { ModuleName string `yaml:"module_name"` OutputDir string `yaml:"output_dir"` PackagePrefix string `yaml:"package_prefix"` EnableSwagger bool `yaml:"enable_swagger"` EnableLogging bool `yaml:"enable_logging"` EnableMetrics bool `yaml:"enable_metrics"` } // Service represents individual service configuration type Service struct { Name string `yaml:"name"` Category string `yaml:"category"` Package string `yaml:"package"` Description string `yaml:"description"` BaseURL string `yaml:"base_url"` Timeout int `yaml:"timeout"` RetryCount int `yaml:"retry_count"` Endpoints map[string]Endpoint `yaml:"endpoints"` Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string } // Endpoint represents endpoint configuration type Endpoint struct { Methods []string `yaml:"methods"` GetPath string `yaml:"get_path,omitempty"` PostPath string `yaml:"post_path,omitempty"` PutPath string `yaml:"put_path,omitempty"` DeletePath string `yaml:"delete_path,omitempty"` PatchPath string `yaml:"patch_path,omitempty"` Model string `yaml:"model"` ResponseModel string `yaml:"response_model"` Description string `yaml:"description"` Summary string `yaml:"summary"` Tags []string `yaml:"tags"` RequireAuth bool `yaml:"require_auth"` RateLimit int `yaml:"rate_limit,omitempty"` CacheEnabled bool `yaml:"cache_enabled"` CacheTTL int `yaml:"cache_ttl,omitempty"` CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` // ADDED: Missing field } // TemplateData holds data for generating handlers type TemplateData struct { ServiceName string ServiceLower string ServiceUpper string Category string Package string Description string BaseURL string Timeout int RetryCount int Endpoints []EndpointData Timestamp string ModuleName string HasValidator bool HasLogger bool HasMetrics bool HasSwagger bool HasAuth bool HasCache bool Dependencies []string // FIXED: Changed to []string Middleware []string // FIXED: Changed to []string GlobalConfig GlobalConfig } // EndpointData represents processed endpoint data type EndpointData struct { Name string NameLower string NameUpper string NameCamel string Methods []string GetPath string PostPath string PutPath string DeletePath string PatchPath string Model string ResponseModel string Description string Summary string Tags []string HasGet bool HasPost bool HasPut bool HasDelete bool HasPatch bool RequireAuth bool RateLimit int CacheEnabled bool CacheTTL int PathParams []string QueryParams []string RequiredFields []string OptionalFields []string CustomHeaders map[string]string } // Template remains the same as before... const handlerTemplate = ` // Code generated by generate-dynamic-handler.go; DO NOT EDIT. // Generated at: {{.Timestamp}} // Service: {{.ServiceName}} ({{.Category}}) // Description: {{.Description}} package handlers import ( "context" "net/http" "time" "{{.ModuleName}}/internal/config" "{{.ModuleName}}/internal/models/reference" "{{.ModuleName}}/pkg/logger" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "github.com/google/uuid" ) // {{.ServiceName}}Service defines {{.ServiceName}} service interface type {{.ServiceName}}Service interface { {{range .Endpoints}} {{if .HasGet}} Get{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) (*reference.{{.Name}}Data, error) {{end}} {{if .HasPost}} Create{{.Name}}(ctx context.Context, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) {{end}} {{if .HasPut}} Update{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) {{end}} {{if .HasDelete}} Delete{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) error {{end}} {{end}} } // {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services type {{.ServiceName}}Handler struct { service {{.ServiceName}}Service validator *validator.Validate logger logger.Logger config config.BpjsConfig } // {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler type {{.ServiceName}}HandlerConfig struct { BpjsConfig config.BpjsConfig Logger logger.Logger Validator *validator.Validate } // New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig, service {{.ServiceName}}Service) *{{.ServiceName}}Handler { return &{{.ServiceName}}Handler{ service: service, validator: cfg.Validator, logger: cfg.Logger, config: cfg.BpjsConfig, } } {{range .Endpoints}} {{if .HasGet}} // Get{{.NameUpper}} retrieves {{.Name}} data {{if $.HasSwagger}} // @Summary Get {{.Name}} data // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} {{range .PathParams}} // @Param {{.}} path string true "{{.}}" {{end}} // @Success 200 {object} reference.{{.ResponseModel}} // @Failure 400 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse // @Router {{.GetPath}} [get] {{end}} func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() // Generate request ID if not present requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() c.Header("X-Request-ID", requestID) } {{if $.HasLogger}} h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ "request_id": requestID, "endpoint": "{{.GetPath}}", {{range .PathParams}} "{{.}}": c.Param("{{.}}"), {{end}} }) {{end}} // Extract path parameters {{range .PathParams}} {{.}} := c.Param("{{.}}") if {{.}} == "" { {{if $.HasLogger}} h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, reference.ErrorResponse{ Status: "error", Message: "Missing required parameter {{.}}", RequestID: requestID, }) return } {{end}} // Call service method var response reference.{{.ResponseModel}} {{if .PathParams}} result, err := h.service.Get{{.Name}}(ctx{{range .PathParams}}, {{.}}{{end}}) {{else}} result, err := h.service.Get{{.Name}}(ctx) {{end}} if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID response.Data = result c.JSON(http.StatusOK, response) } {{end}} {{if .HasPost}} // Create{{.NameUpper}} creates new {{.Name}} {{if $.HasSwagger}} // @Summary Create {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} // @Param request body reference.{{.Model}} true "{{.Name}} data" // @Success 201 {object} reference.{{.ResponseModel}} // @Failure 400 {object} reference.ErrorResponse // @Failure 500 {object} reference.ErrorResponse // @Router {{.PostPath}} [post] {{end}} func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) defer cancel() requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() c.Header("X-Request-ID", requestID) } {{if $.HasLogger}} h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ "request_id": requestID, }) {{end}} var req reference.{{.Model}} if err := c.ShouldBindJSON(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Invalid request body", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, reference.ErrorResponse{ Status: "error", Message: "Invalid request body: " + err.Error(), RequestID: requestID, }) return } // Validate request if err := h.validator.Struct(&req); err != nil { {{if $.HasLogger}} h.logger.Error("Validation failed", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusBadRequest, reference.ErrorResponse{ Status: "error", Message: "Validation failed: " + err.Error(), RequestID: requestID, }) return } // Call service method var response reference.{{.ResponseModel}} result, err := h.service.Create{{.Name}}(ctx, &req) if err != nil { {{if $.HasLogger}} h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ "error": err.Error(), "request_id": requestID, }) {{end}} c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ Status: "error", Message: "Internal server error", RequestID: requestID, }) return } // Ensure response has proper fields response.Status = "success" response.RequestID = requestID response.Data = result c.JSON(http.StatusCreated, response) } {{end}} {{end}} ` func main() { if len(os.Args) < 2 { printUsage() os.Exit(1) } configFile := os.Args[1] var targetService string if len(os.Args) > 2 { targetService = os.Args[2] } // Load configuration config, err := loadConfig(configFile) if err != nil { fmt.Printf("āŒ Error loading config: %v\n", err) os.Exit(1) } fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation...") fmt.Printf("šŸ“ Config file: %s\n", configFile) if targetService != "" { fmt.Printf("šŸŽÆ Target service: %s\n", targetService) } generated := 0 errors := 0 // Generate handlers for serviceName, service := range config.Services { if targetService != "" && serviceName != targetService { continue } fmt.Printf("šŸ”§ Generating handler for service: %s (%s)\n", service.Name, service.Category) err := generateHandler(serviceName, service, config.Global) if err != nil { fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) errors++ continue } fmt.Printf("āœ… Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) generated++ } // Summary fmt.Println("\nšŸ“Š Generation Summary:") fmt.Printf(" āœ… Successfully generated: %d handlers\n", generated) if errors > 0 { fmt.Printf(" āŒ Failed: %d handlers\n", errors) } if generated > 0 { fmt.Println("šŸŽ‰ Generation completed successfully!") } } func printUsage() { fmt.Println("BPJS Dynamic Handler Generator") fmt.Println() fmt.Println("Usage:") fmt.Println(" go run generate-dynamic-handler.go [service-name]") fmt.Println() fmt.Println("Examples:") fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") } func loadConfig(filename string) (*ServiceConfig, error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } var config ServiceConfig err = yaml.Unmarshal(data, &config) if err != nil { return nil, fmt.Errorf("failed to parse YAML config: %w", err) } // Set default values if config.Global.ModuleName == "" { config.Global.ModuleName = "api-service" } if config.Global.OutputDir == "" { config.Global.OutputDir = "internal/handlers" } return &config, nil } func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { // Prepare template data templateData := TemplateData{ ServiceName: service.Name, ServiceLower: strings.ToLower(service.Name), ServiceUpper: strings.ToUpper(service.Name), Category: service.Category, Package: service.Package, Description: service.Description, BaseURL: service.BaseURL, Timeout: getOrDefault(service.Timeout, 30), RetryCount: getOrDefault(service.RetryCount, 3), Timestamp: time.Now().Format("2006-01-02 15:04:05"), ModuleName: globalConfig.ModuleName, HasValidator: true, HasLogger: globalConfig.EnableLogging, HasMetrics: globalConfig.EnableMetrics, HasSwagger: globalConfig.EnableSwagger, Dependencies: service.Dependencies, // Now []string Middleware: service.Middleware, // Now []string GlobalConfig: globalConfig, } // Check for advanced features for _, endpoint := range service.Endpoints { if endpoint.RequireAuth { templateData.HasAuth = true } if endpoint.CacheEnabled { templateData.HasCache = true } } // Process endpoints for endpointName, endpoint := range service.Endpoints { endpointData := processEndpoint(endpointName, endpoint) templateData.Endpoints = append(templateData.Endpoints, endpointData) } // Create output directory outputDir := globalConfig.OutputDir if outputDir == "" { outputDir = "internal/handlers" } if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } // Generate handler file filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) // Create template with custom functions tmpl := template.New("handler").Funcs(template.FuncMap{ "contains": strings.Contains, "join": strings.Join, "title": strings.Title, "trimPrefix": strings.TrimPrefix, }) tmpl, err := tmpl.Parse(handlerTemplate) if err != nil { return fmt.Errorf("failed to parse template: %w", err) } file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close() err = tmpl.Execute(file, templateData) if err != nil { return fmt.Errorf("failed to execute template: %w", err) } return nil } func processEndpoint(name string, endpoint Endpoint) EndpointData { data := EndpointData{ Name: strings.Title(name), NameLower: strings.ToLower(name), NameUpper: strings.ToUpper(name), NameCamel: toCamelCase(name), Methods: endpoint.Methods, GetPath: endpoint.GetPath, PostPath: endpoint.PostPath, PutPath: endpoint.PutPath, DeletePath: endpoint.DeletePath, PatchPath: endpoint.PatchPath, Model: endpoint.Model, ResponseModel: endpoint.ResponseModel, Description: endpoint.Description, Summary: endpoint.Summary, Tags: endpoint.Tags, RequireAuth: endpoint.RequireAuth, RateLimit: endpoint.RateLimit, CacheEnabled: endpoint.CacheEnabled, CacheTTL: getOrDefault(endpoint.CacheTTL, 300), CustomHeaders: endpoint.CustomHeaders, } // Set method flags and extract path parameters for _, method := range endpoint.Methods { switch strings.ToUpper(method) { case "GET": data.HasGet = true data.PathParams = extractPathParams(endpoint.GetPath) case "POST": data.HasPost = true case "PUT": data.HasPut = true data.PathParams = extractPathParams(endpoint.PutPath) case "DELETE": data.HasDelete = true data.PathParams = extractPathParams(endpoint.DeletePath) case "PATCH": data.HasPatch = true data.PathParams = extractPathParams(endpoint.PatchPath) } } return data } func extractPathParams(path string) []string { if path == "" { return nil } var params []string parts := strings.Split(path, "/") for _, part := range parts { if strings.HasPrefix(part, ":") { params = append(params, strings.TrimPrefix(part, ":")) } } return params } func toCamelCase(str string) string { words := strings.FieldsFunc(str, func(c rune) bool { return c == '_' || c == '-' || c == ' ' }) if len(words) == 0 { return str } result := strings.ToLower(words[0]) for _, word := range words[1:] { result += strings.Title(strings.ToLower(word)) } return result } func getOrDefault(value, defaultValue int) int { if value == 0 { return defaultValue } return value }