2119 lines
65 KiB
Go
2119 lines
65 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// ================== STRUCTURES FOR SEPARATION OF CONCERNS ==================
|
|
// HandlerValidation tracks validation and generation status with thread safety
|
|
type HandlerValidation struct {
|
|
ExistingFunctions map[string]bool
|
|
NewFunctions []string
|
|
UpdatedFiles []string
|
|
CreatedFiles []string
|
|
mu sync.Mutex // For thread-safe access to slices
|
|
}
|
|
|
|
// DirectoryInfo represents directory structure for nested endpoints
|
|
type DirectoryInfo struct {
|
|
Path string
|
|
IsFile bool
|
|
Functions []FunctionInfo
|
|
Children map[string]*DirectoryInfo
|
|
}
|
|
|
|
// FunctionInfo contains information about a function
|
|
type FunctionInfo struct {
|
|
Name string
|
|
Methods []string
|
|
Endpoint string
|
|
Config EndpointConfig
|
|
}
|
|
|
|
// EndpointConfig contains configuration for an endpoint
|
|
type EndpointConfig struct {
|
|
Methods []string `yaml:"methods"`
|
|
GetPath string `yaml:"get_path"`
|
|
PostPath string `yaml:"post_path"`
|
|
PutPath string `yaml:"put_path"`
|
|
PatchPath string `yaml:"patch_path"`
|
|
DeletePath string `yaml:"delete_path"`
|
|
SearchPath string `yaml:"search_path"`
|
|
Model string `yaml:"model"`
|
|
ResponseModel string `yaml:"response_model"`
|
|
RequestModel string `yaml:"request_model"`
|
|
Description string `yaml:"description"`
|
|
Summary string `yaml:"summary"`
|
|
Tags []string `yaml:"tags"`
|
|
RequireAuth bool `yaml:"require_auth"`
|
|
CacheEnabled bool `yaml:"cache_enabled"`
|
|
CacheTTL int `yaml:"cache_ttl"`
|
|
FhirProfiles []string `yaml:"fhir_profiles"`
|
|
SearchParams []string `yaml:"search_params"`
|
|
}
|
|
|
|
// GlobalConfig contains global configuration
|
|
type GlobalConfig struct {
|
|
ModuleName string `yaml:"module_name"`
|
|
OutputDir string `yaml:"output_dir"`
|
|
EnableSwagger bool `yaml:"enable_swagger"`
|
|
EnableLogging bool `yaml:"enable_logging"`
|
|
EnableMetrics bool `yaml:"enable_metrics"`
|
|
EnableAuth bool `yaml:"enable_auth"`
|
|
BaseURL string `yaml:"base_url"`
|
|
Version string `yaml:"version"`
|
|
Environment string `yaml:"environment"`
|
|
FhirVersion string `yaml:"fhir_version"`
|
|
ProfileURL string `yaml:"profile_url"`
|
|
}
|
|
|
|
// ServiceConfig contains complete service configuration
|
|
type ServiceConfig struct {
|
|
Global GlobalConfig `yaml:"global,omitempty"`
|
|
Services map[string]Service `yaml:"services"`
|
|
}
|
|
|
|
// Service represents a single service
|
|
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"`
|
|
FhirResource string `yaml:"fhir_resource,omitempty"`
|
|
Validation ValidationConfig `yaml:"validation,omitempty"`
|
|
Auth AuthConfig `yaml:"authentication,omitempty"`
|
|
Endpoints map[string]EndpointGroup `yaml:"endpoints"`
|
|
Dependencies string `yaml:"dependencies,omitempty"`
|
|
Middleware string `yaml:"middleware,omitempty"`
|
|
}
|
|
|
|
// ValidationConfig contains validation rules
|
|
type ValidationConfig struct {
|
|
EnableFhirValidation bool `yaml:"enable_fhir_validation"`
|
|
RequiredFields []string `yaml:"required_fields"`
|
|
CustomValidators []string `yaml:"custom_validators"`
|
|
}
|
|
|
|
// AuthConfig contains authentication configuration
|
|
type AuthConfig struct {
|
|
Type string `yaml:"type"`
|
|
TokenURL string `yaml:"token_url"`
|
|
Scopes []string `yaml:"scopes"`
|
|
}
|
|
|
|
// EndpointGroup represents a group of related endpoints - MODIFIED to match your YAML
|
|
type EndpointGroup struct {
|
|
Description string `yaml:"description"`
|
|
Basic map[string]interface{} `yaml:"basic"`
|
|
// Generated fields
|
|
HandlerFolder string
|
|
HandlerFile string
|
|
HandlerName string
|
|
Functions map[string]FunctionConfig
|
|
}
|
|
|
|
// FunctionConfig contains individual function configuration
|
|
type FunctionConfig struct {
|
|
Methods []string `yaml:"methods"`
|
|
Path string `yaml:"path"`
|
|
Model string `yaml:"model"`
|
|
RoutesLink string `yaml:"routes_link"`
|
|
// Path for swagger documentation
|
|
GetPath string `yaml:"get_path"`
|
|
PostPath string `yaml:"post_path"`
|
|
PutPath string `yaml:"put_path"`
|
|
PatchPath string `yaml:"patch_path"`
|
|
DeletePath string `yaml:"delete_path"`
|
|
SearchPath string `yaml:"search_path"`
|
|
ResponseModel string `yaml:"response_model"`
|
|
RequestModel string `yaml:"request_model"`
|
|
Description string `yaml:"description"`
|
|
Summary string `yaml:"summary"`
|
|
Tags []string `yaml:"tags"`
|
|
RequireAuth bool `yaml:"require_auth"`
|
|
CacheEnabled bool `yaml:"cache_enabled"`
|
|
CacheTTL int `yaml:"cache_ttl"`
|
|
FhirProfiles []string `yaml:"fhir_profiles"`
|
|
SearchParams []string `yaml:"search_params"`
|
|
}
|
|
|
|
// TemplateData contains data for template generation
|
|
type TemplateData struct {
|
|
ServiceName string
|
|
ServiceLower string
|
|
ServiceUpper string
|
|
Category string
|
|
CategoryParts []string
|
|
Package string
|
|
Description string
|
|
BaseURL string
|
|
Timeout int
|
|
RetryCount int
|
|
FhirResource string
|
|
Endpoints []EndpointData
|
|
Timestamp string
|
|
ModuleName string
|
|
HasValidator bool
|
|
HasLogger bool
|
|
HasSwagger bool
|
|
GlobalConfig GlobalConfig
|
|
ShouldGenerateStruct bool
|
|
ShouldGenerateConstructor bool
|
|
FunctionalArea string
|
|
HandlerName string
|
|
DirectoryDepth int
|
|
}
|
|
|
|
// EndpointData contains endpoint-specific data
|
|
type EndpointData struct {
|
|
Name string
|
|
NameLower string
|
|
NameUpper string
|
|
NameCamel string
|
|
Methods []string
|
|
GetPath string
|
|
PostPath string
|
|
PutPath string
|
|
PatchPath string
|
|
DeletePath string
|
|
SearchPath string
|
|
Model string
|
|
ResponseModel string
|
|
RequestModel string
|
|
DataModel string
|
|
Description string
|
|
Summary string
|
|
Tags []string
|
|
HasGet bool
|
|
HasPost bool
|
|
HasPut bool
|
|
HasPatch bool
|
|
HasDelete bool
|
|
HasSearch bool
|
|
RequireAuth bool
|
|
CacheEnabled bool
|
|
CacheTTL int
|
|
PathParams []string
|
|
ModelPackage string
|
|
FhirResource string
|
|
CategoryPath string
|
|
}
|
|
|
|
// ================== MAIN GENERATION FUNCTIONS ==================
|
|
// generateHandlerWithValidation generates handler with separation of concerns
|
|
func generateHandlerWithValidation(
|
|
ctx context.Context,
|
|
serviceName string,
|
|
svc Service,
|
|
gc GlobalConfig,
|
|
) error {
|
|
validation := &HandlerValidation{
|
|
ExistingFunctions: make(map[string]bool),
|
|
NewFunctions: []string{},
|
|
UpdatedFiles: []string{},
|
|
CreatedFiles: []string{},
|
|
}
|
|
|
|
baseDir := gc.OutputDir
|
|
var wg sync.WaitGroup
|
|
errChan := make(chan error, len(svc.Endpoints))
|
|
|
|
// Debug: Print service information
|
|
fmt.Printf("🔍 Debug: Processing service %s with %d endpoints\n", serviceName, len(svc.Endpoints))
|
|
|
|
// Process each endpoint group concurrently
|
|
for groupName, grp := range svc.Endpoints {
|
|
wg.Add(1)
|
|
go func(groupName string, grp EndpointGroup) {
|
|
defer wg.Done()
|
|
select {
|
|
case <-ctx.Done():
|
|
errChan <- ctx.Err()
|
|
return
|
|
default:
|
|
// Debug: Print endpoint group information
|
|
fmt.Printf("🔍 Debug: Processing endpoint group %s with %d functions\n", groupName, len(grp.Functions))
|
|
|
|
// Create handler folder structure
|
|
categoryParts := strings.Split(svc.Category, "/")
|
|
handlerDirParts := append([]string{baseDir}, categoryParts...)
|
|
handlerDir := filepath.Join(handlerDirParts...)
|
|
if err := os.MkdirAll(handlerDir, 0755); err != nil {
|
|
errChan <- fmt.Errorf("mkdir %s: %w", handlerDir, err)
|
|
return
|
|
}
|
|
|
|
// Generate methods file with clear naming
|
|
methodsFileName := fmt.Sprintf("%s.go", strings.ToLower(svc.Package))
|
|
methodsFilePath := filepath.Join(handlerDir, methodsFileName)
|
|
|
|
// Check if methods file exists
|
|
fileExists := false
|
|
if _, err := os.Stat(methodsFilePath); err == nil {
|
|
fileExists = true
|
|
}
|
|
|
|
if !fileExists {
|
|
// Create new methods file
|
|
err := createMethodsFileFromConfig(
|
|
methodsFilePath, svc, grp, gc, groupName,
|
|
)
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("create methods file %s: %w", methodsFilePath, err)
|
|
return
|
|
}
|
|
validation.mu.Lock()
|
|
validation.CreatedFiles = append(validation.CreatedFiles, methodsFilePath)
|
|
validation.mu.Unlock()
|
|
fmt.Printf("✅ Created methods file: %s\n", methodsFilePath)
|
|
} else {
|
|
// Update existing methods file with new functions only
|
|
err := updateExistingMethodsFile(
|
|
methodsFilePath, svc, grp, gc, groupName, validation,
|
|
)
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("update methods file %s: %w", methodsFilePath, err)
|
|
return
|
|
}
|
|
validation.mu.Lock()
|
|
if len(validation.NewFunctions) > 0 {
|
|
validation.UpdatedFiles = append(validation.UpdatedFiles, methodsFilePath)
|
|
}
|
|
validation.mu.Unlock()
|
|
fmt.Printf("✅ Updated methods file: %s\n", methodsFilePath)
|
|
}
|
|
}
|
|
}(groupName, grp)
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
wg.Wait()
|
|
close(errChan)
|
|
|
|
// Check for errors
|
|
for err := range errChan {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Generate models (sequential for simplicity)
|
|
if err := generateModels(ctx, svc, gc); err != nil {
|
|
return fmt.Errorf("generate models: %w", err)
|
|
}
|
|
|
|
// Generate routes
|
|
if err := generateRoutes(serviceName, svc, gc); err != nil {
|
|
return fmt.Errorf("generate routes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createMethodsFileFromConfig creates new methods file from configuration
|
|
func createMethodsFileFromConfig(
|
|
filePath string,
|
|
svc Service,
|
|
grp EndpointGroup,
|
|
gc GlobalConfig,
|
|
functionalArea string,
|
|
) error {
|
|
// Debug: Print function information
|
|
fmt.Printf("🔍 Debug: Creating methods file with %d functions\n", len(grp.Functions))
|
|
for funcName, funcConfig := range grp.Functions {
|
|
fmt.Printf("🔍 Debug: Function %s has methods: %v\n", funcName, funcConfig.Methods)
|
|
}
|
|
|
|
// Collect all functions for this group
|
|
var allEndpoints []EndpointData
|
|
for fname, fcfg := range grp.Functions {
|
|
td := processFunctionData(svc, grp, fname, fcfg, gc)
|
|
allEndpoints = append(allEndpoints, td.Endpoints...)
|
|
}
|
|
|
|
templateData := TemplateData{
|
|
ServiceName: svc.Name,
|
|
ServiceLower: strings.ToLower(svc.Name),
|
|
ServiceUpper: strings.ToUpper(svc.Name),
|
|
Category: svc.Category,
|
|
CategoryParts: strings.Split(svc.Category, "/"),
|
|
Package: svc.Package,
|
|
Description: svc.Description,
|
|
BaseURL: svc.BaseURL,
|
|
Timeout: svc.Timeout,
|
|
RetryCount: svc.RetryCount,
|
|
FhirResource: svc.FhirResource,
|
|
Endpoints: allEndpoints,
|
|
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
|
ModuleName: gc.ModuleName,
|
|
HasValidator: true,
|
|
HasLogger: gc.EnableLogging,
|
|
HasSwagger: gc.EnableSwagger,
|
|
GlobalConfig: gc,
|
|
ShouldGenerateStruct: false, // NEVER generate struct in methods file
|
|
ShouldGenerateConstructor: false, // NEVER generate constructor in methods file
|
|
FunctionalArea: functionalArea,
|
|
HandlerName: grp.HandlerName,
|
|
DirectoryDepth: len(strings.Split(svc.Category, "/")),
|
|
}
|
|
|
|
return createMethodsFile(filePath, templateData)
|
|
}
|
|
|
|
// updateExistingMethodsFile updates existing methods file with new functions
|
|
func updateExistingMethodsFile(
|
|
filePath string,
|
|
svc Service,
|
|
grp EndpointGroup,
|
|
gc GlobalConfig,
|
|
functionalArea string,
|
|
validation *HandlerValidation,
|
|
) error {
|
|
existingContent, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content := string(existingContent)
|
|
|
|
// Debug: Print function information
|
|
fmt.Printf("🔍 Debug: Updating methods file with %d functions\n", len(grp.Functions))
|
|
for funcName, funcConfig := range grp.Functions {
|
|
fmt.Printf("🔍 Debug: Function %s has methods: %v\n", funcName, funcConfig.Methods)
|
|
}
|
|
|
|
// Check for existing functions and collect new ones
|
|
var newEndpoints []EndpointData
|
|
for fname, fcfg := range grp.Functions {
|
|
functionExists := false
|
|
for _, method := range fcfg.Methods {
|
|
funcName := generateFunctionName(fname, method)
|
|
sig := fmt.Sprintf("func (h *%sHandler) %s", grp.HandlerName, funcName)
|
|
if strings.Contains(content, sig) {
|
|
fmt.Printf("⚠️ Skip existing: %s (%s)\n", fname, funcName)
|
|
functionExists = true
|
|
break
|
|
}
|
|
}
|
|
if functionExists {
|
|
continue
|
|
}
|
|
|
|
td := processFunctionData(svc, grp, fname, fcfg, gc)
|
|
newEndpoints = append(newEndpoints, td.Endpoints...)
|
|
validation.mu.Lock()
|
|
validation.NewFunctions = append(validation.NewFunctions, fname)
|
|
validation.mu.Unlock()
|
|
fmt.Printf("✅ Will add: %s\n", fname)
|
|
}
|
|
|
|
if len(newEndpoints) == 0 {
|
|
fmt.Printf("⏭️ No new functions to add\n")
|
|
return nil
|
|
}
|
|
|
|
// Generate new functions using methods-only template
|
|
templateData := TemplateData{
|
|
ServiceName: svc.Name,
|
|
ServiceLower: strings.ToLower(svc.Name),
|
|
ServiceUpper: strings.ToUpper(svc.Name),
|
|
Category: svc.Category,
|
|
CategoryParts: strings.Split(svc.Category, "/"),
|
|
Package: svc.Package,
|
|
Description: svc.Description,
|
|
BaseURL: svc.BaseURL,
|
|
Timeout: svc.Timeout,
|
|
RetryCount: svc.RetryCount,
|
|
FhirResource: svc.FhirResource,
|
|
Endpoints: newEndpoints,
|
|
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
|
ModuleName: gc.ModuleName,
|
|
HasValidator: true,
|
|
HasLogger: gc.EnableLogging,
|
|
HasSwagger: gc.EnableSwagger,
|
|
GlobalConfig: gc,
|
|
ShouldGenerateStruct: false, // NEVER
|
|
ShouldGenerateConstructor: false, // NEVER
|
|
FunctionalArea: functionalArea,
|
|
HandlerName: grp.HandlerName,
|
|
DirectoryDepth: len(strings.Split(svc.Category, "/")),
|
|
}
|
|
|
|
newFunctions, err := generateNewMethodsOnly(templateData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Merge content
|
|
mergedContent := mergeGoFileContent(content, newFunctions)
|
|
return ioutil.WriteFile(filePath, []byte(mergedContent), 0644)
|
|
}
|
|
|
|
// processFunctionData processes individual function configuration
|
|
func processFunctionData(
|
|
svc Service,
|
|
grp EndpointGroup,
|
|
fname string,
|
|
fcfg FunctionConfig,
|
|
gc GlobalConfig,
|
|
) TemplateData {
|
|
fmt.Printf("🔍 Debug: Processing function %s with methods: %v\n", fname, fcfg.Methods)
|
|
|
|
ed := EndpointData{
|
|
Name: toCamelCase(fname),
|
|
NameLower: strings.ToLower(fname),
|
|
NameUpper: strings.ToUpper(fname),
|
|
NameCamel: toCamelCase(fname),
|
|
Methods: fcfg.Methods,
|
|
GetPath: fcfg.GetPath,
|
|
PostPath: fcfg.PostPath,
|
|
PutPath: fcfg.PutPath,
|
|
PatchPath: fcfg.PatchPath,
|
|
DeletePath: fcfg.DeletePath,
|
|
SearchPath: fcfg.SearchPath,
|
|
Model: fcfg.Model,
|
|
ResponseModel: fcfg.ResponseModel,
|
|
RequestModel: fcfg.Model,
|
|
DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1),
|
|
Description: fcfg.Description,
|
|
Summary: fcfg.Summary,
|
|
Tags: fcfg.Tags,
|
|
RequireAuth: fcfg.RequireAuth,
|
|
CacheEnabled: fcfg.CacheEnabled,
|
|
CacheTTL: fcfg.CacheTTL,
|
|
PathParams: extractPathParams(fcfg.GetPath),
|
|
ModelPackage: svc.Package,
|
|
FhirResource: svc.FhirResource,
|
|
CategoryPath: svc.Category,
|
|
}
|
|
|
|
// Set method flags
|
|
for _, m := range fcfg.Methods {
|
|
switch strings.ToUpper(m) {
|
|
case "GET":
|
|
ed.HasGet = true
|
|
case "POST":
|
|
ed.HasPost = true
|
|
case "PUT":
|
|
ed.HasPut = true
|
|
case "PATCH":
|
|
ed.HasPatch = true
|
|
case "DELETE":
|
|
ed.HasDelete = true
|
|
case "SEARCH":
|
|
ed.HasSearch = true
|
|
}
|
|
}
|
|
|
|
fmt.Printf("🔍 Debug: Function %s flags - Get:%t, Post:%t, Put:%t, Patch:%t, Delete:%t, Search:%t\n",
|
|
fname, ed.HasGet, ed.HasPost, ed.HasPut, ed.HasPatch, ed.HasDelete, ed.HasSearch)
|
|
|
|
return TemplateData{
|
|
ServiceName: svc.Name,
|
|
ServiceLower: strings.ToLower(svc.Name),
|
|
ServiceUpper: strings.ToUpper(svc.Name),
|
|
Category: svc.Category,
|
|
CategoryParts: strings.Split(svc.Category, "/"),
|
|
Package: svc.Package,
|
|
Description: svc.Description,
|
|
BaseURL: svc.BaseURL,
|
|
Timeout: svc.Timeout,
|
|
RetryCount: svc.RetryCount,
|
|
FhirResource: svc.FhirResource,
|
|
Endpoints: []EndpointData{ed},
|
|
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
|
ModuleName: gc.ModuleName,
|
|
HasValidator: true,
|
|
HasLogger: gc.EnableLogging,
|
|
HasSwagger: gc.EnableSwagger,
|
|
GlobalConfig: gc,
|
|
HandlerName: grp.HandlerName,
|
|
DirectoryDepth: len(strings.Split(svc.Category, "/")),
|
|
}
|
|
}
|
|
|
|
// generateModels generates model files for the service
|
|
func generateModels(ctx context.Context, svc Service, gc GlobalConfig) error {
|
|
// Create model directory structure
|
|
categoryParts := strings.Split(svc.Category, "/")
|
|
modelDirParts := append([]string{"internal", "models"}, categoryParts...)
|
|
modelDir := filepath.Join(modelDirParts...)
|
|
if err := os.MkdirAll(modelDir, 0755); err != nil {
|
|
return fmt.Errorf("mkdir %s: %w", modelDir, err)
|
|
}
|
|
|
|
modelFilePath := filepath.Join(modelDir, strings.ToLower(svc.Package)+".go")
|
|
|
|
// Check if model file exists
|
|
if _, err := os.Stat(modelFilePath); err == nil {
|
|
fmt.Printf("⏭️ Model file already exists: %s\n", modelFilePath)
|
|
return nil
|
|
}
|
|
|
|
// Check if context was cancelled
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
|
|
// Generate model content
|
|
templateData := TemplateData{
|
|
ServiceName: svc.Name,
|
|
ServiceLower: strings.ToLower(svc.Name),
|
|
ServiceUpper: strings.ToUpper(svc.Name),
|
|
Category: svc.Category,
|
|
CategoryParts: categoryParts,
|
|
Package: svc.Package,
|
|
FhirResource: svc.FhirResource,
|
|
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
|
ModuleName: gc.ModuleName,
|
|
DirectoryDepth: len(categoryParts),
|
|
}
|
|
|
|
tmpl := template.New("model")
|
|
tmpl, err := tmpl.Parse(modelTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
file, err := os.Create(modelFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
err = tmpl.Execute(file, templateData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✅ Created model file: %s\n", modelFilePath)
|
|
return nil
|
|
}
|
|
|
|
// generateRoutes generates routes for the service
|
|
func generateRoutes(serviceName string, svc Service, gc GlobalConfig) error {
|
|
routesFilePath := filepath.Join(gc.OutputDir, "routes.go")
|
|
if _, err := os.Stat(routesFilePath); os.IsNotExist(err) {
|
|
// Create routes file if it doesn't exist
|
|
if err := os.MkdirAll(filepath.Dir(routesFilePath), 0755); err != nil {
|
|
return fmt.Errorf("mkdir %s: %w", filepath.Dir(routesFilePath), err)
|
|
}
|
|
|
|
// Create a basic routes file
|
|
routesContent := fmt.Sprintf(`package handlers
|
|
import (
|
|
"%s/internal/config"
|
|
"%s/pkg/logger"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
// SetupRoutes sets up all routes for the application
|
|
func SetupRoutes(r *gin.Engine, cfg *config.Config) error {
|
|
// Setup routes will be added here
|
|
return nil
|
|
}
|
|
`, gc.ModuleName, gc.ModuleName)
|
|
|
|
if err := ioutil.WriteFile(routesFilePath, []byte(routesContent), 0644); err != nil {
|
|
return fmt.Errorf("create routes file: %w", err)
|
|
}
|
|
fmt.Printf("✅ Created routes file: %s\n", routesFilePath)
|
|
}
|
|
|
|
routesContent, err := ioutil.ReadFile(routesFilePath)
|
|
if err != nil {
|
|
return fmt.Errorf("read routes file: %w", err)
|
|
}
|
|
routesContentStr := string(routesContent)
|
|
|
|
// Check if routes are already registered
|
|
if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", svc.Name)) {
|
|
fmt.Printf("⚠️ Routes for %s already registered\n", svc.Name)
|
|
return nil
|
|
}
|
|
|
|
var imports []string
|
|
var allRoutes []string
|
|
|
|
// Track handler folders that have been imported
|
|
processedFolders := make(map[string]bool)
|
|
|
|
for groupName, grp := range svc.Endpoints {
|
|
// Import based on handler folder
|
|
if !processedFolders[grp.HandlerFolder] {
|
|
importLine := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"",
|
|
grp.HandlerFolder, gc.ModuleName, grp.HandlerFolder)
|
|
imports = append(imports, importLine)
|
|
processedFolders[grp.HandlerFolder] = true
|
|
fmt.Printf("✅ Added import: %sHandlers\n", grp.HandlerFolder)
|
|
}
|
|
|
|
var routesCode strings.Builder
|
|
|
|
// Use groupName for comment and identifier
|
|
routesCode.WriteString(fmt.Sprintf("\n\t// %s (%s) routes\n", grp.Description, groupName))
|
|
|
|
// Handler instantiation using HandlerName from config
|
|
routesCode.WriteString(fmt.Sprintf("\t%sHandler := %sHandlers.New%sHandler(%sHandlers.%sHandlerConfig{\n",
|
|
strings.ToLower(grp.HandlerName),
|
|
grp.HandlerFolder,
|
|
grp.HandlerName,
|
|
grp.HandlerFolder,
|
|
grp.HandlerName))
|
|
routesCode.WriteString("\t\tConfig: cfg,\n")
|
|
routesCode.WriteString("\t\tLogger: logger.Default(),\n")
|
|
routesCode.WriteString("\t\tValidator: validator.New(),\n")
|
|
routesCode.WriteString("\t})\n")
|
|
|
|
// Use groupName for route group path
|
|
routesCode.WriteString(fmt.Sprintf("\t%sGroup := r.Group(\"/%s\")\n",
|
|
strings.ToLower(grp.HandlerName), groupName))
|
|
|
|
// Process functions
|
|
for fname, fcfg := range grp.Functions {
|
|
td := processFunctionData(svc, grp, fname, fcfg, gc)
|
|
for _, endpoint := range td.Endpoints {
|
|
handlerVar := strings.ToLower(grp.HandlerName) + "Handler"
|
|
groupVar := strings.ToLower(grp.HandlerName) + "Group"
|
|
|
|
// Loop through methods and use specific routes
|
|
for _, method := range fcfg.Methods {
|
|
var cleanPath string
|
|
|
|
// Choose path based on method
|
|
switch strings.ToUpper(method) {
|
|
case "GET":
|
|
cleanPath = fcfg.GetPath
|
|
case "POST":
|
|
cleanPath = fcfg.PostPath
|
|
case "PUT":
|
|
cleanPath = fcfg.PutPath
|
|
case "PATCH":
|
|
cleanPath = fcfg.PatchPath
|
|
case "DELETE":
|
|
cleanPath = fcfg.DeletePath
|
|
case "SEARCH":
|
|
cleanPath = fcfg.SearchPath
|
|
default:
|
|
fmt.Printf("⚠️ Unsupported HTTP method: %s for function %s\n", method, fname)
|
|
continue
|
|
}
|
|
|
|
// Fallback to path if specific route is empty
|
|
if cleanPath == "" {
|
|
cleanPath = fcfg.Path
|
|
}
|
|
|
|
// Clean path - remove groupName prefix if exists
|
|
if strings.HasPrefix(cleanPath, "/"+groupName) {
|
|
cleanPath = strings.TrimPrefix(cleanPath, "/"+groupName)
|
|
}
|
|
if cleanPath == "" {
|
|
cleanPath = "/"
|
|
}
|
|
|
|
// Generate route based on method
|
|
switch strings.ToUpper(method) {
|
|
case "GET":
|
|
routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Get%s)\n",
|
|
groupVar, cleanPath, handlerVar, endpoint.Name))
|
|
case "POST":
|
|
routesCode.WriteString(fmt.Sprintf("\t%s.POST(\"%s\", %s.Create%s)\n",
|
|
groupVar, cleanPath, handlerVar, endpoint.Name))
|
|
case "PUT":
|
|
routesCode.WriteString(fmt.Sprintf("\t%s.PUT(\"%s\", %s.Update%s)\n",
|
|
groupVar, cleanPath, handlerVar, endpoint.Name))
|
|
case "PATCH":
|
|
routesCode.WriteString(fmt.Sprintf("\t%s.PATCH(\"%s\", %s.Patch%s)\n",
|
|
groupVar, cleanPath, handlerVar, endpoint.Name))
|
|
case "DELETE":
|
|
routesCode.WriteString(fmt.Sprintf("\t%s.DELETE(\"%s\", %s.Delete%s)\n",
|
|
groupVar, cleanPath, handlerVar, endpoint.Name))
|
|
case "SEARCH":
|
|
routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Search%s)\n",
|
|
groupVar, cleanPath, handlerVar, endpoint.Name))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
allRoutes = append(allRoutes, routesCode.String())
|
|
}
|
|
|
|
// Insert imports after existing imports
|
|
if len(imports) > 0 {
|
|
importSection := strings.Join(imports, "\n") + "\n"
|
|
// Find position after import section
|
|
importMarker := "import ("
|
|
if strings.Contains(routesContentStr, importMarker) {
|
|
importIndex := strings.Index(routesContentStr, importMarker)
|
|
closingParenIndex := strings.Index(routesContentStr[importIndex:], "\n)") + importIndex
|
|
if closingParenIndex > importIndex {
|
|
routesContentStr = routesContentStr[:closingParenIndex+2] + "\n" + importSection + routesContentStr[closingParenIndex+2:]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find and insert routes
|
|
setupRoutesMarker := "func SetupRoutes"
|
|
if !strings.Contains(routesContentStr, setupRoutesMarker) {
|
|
return fmt.Errorf("SetupRoutes function not found in routes.go")
|
|
}
|
|
|
|
funcStart := strings.Index(routesContentStr, setupRoutesMarker)
|
|
funcBodyStart := strings.Index(routesContentStr[funcStart:], "{") + funcStart + 1
|
|
newRoutesContent := routesContentStr[:funcBodyStart+1] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[funcBodyStart+1:]
|
|
|
|
err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("write updated routes file: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✅ Updated routes file with %s routes\n", svc.Name)
|
|
return nil
|
|
}
|
|
|
|
// ================== HELPER FUNCTIONS ==================
|
|
// generateFunctionName generates function name based on endpoint and method
|
|
func generateFunctionName(endpointName, method string) string {
|
|
switch strings.ToUpper(method) {
|
|
case "GET":
|
|
return fmt.Sprintf("Get%s", strings.Title(endpointName))
|
|
case "POST":
|
|
return fmt.Sprintf("Create%s", strings.Title(endpointName))
|
|
case "PUT":
|
|
return fmt.Sprintf("Update%s", strings.Title(endpointName))
|
|
case "DELETE":
|
|
return fmt.Sprintf("Delete%s", strings.Title(endpointName))
|
|
case "PATCH":
|
|
return fmt.Sprintf("Patch%s", strings.Title(endpointName))
|
|
case "SEARCH":
|
|
return fmt.Sprintf("Search%s", strings.Title(endpointName))
|
|
default:
|
|
return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName))
|
|
}
|
|
}
|
|
|
|
// extractPathParams extracts path parameters from URL path
|
|
func extractPathParams(path string) []string {
|
|
var params []string
|
|
for _, part := range strings.Split(path, "/") {
|
|
if strings.HasPrefix(part, ":") {
|
|
params = append(params, strings.TrimPrefix(part, ":"))
|
|
}
|
|
}
|
|
return params
|
|
}
|
|
|
|
// toCamelCase converts string to CamelCase
|
|
func toCamelCase(s string) string {
|
|
parts := strings.FieldsFunc(s, func(r rune) bool {
|
|
return r == '_' || r == '-' || r == ' '
|
|
})
|
|
for i, p := range parts {
|
|
parts[i] = strings.Title(strings.ToLower(p))
|
|
}
|
|
return strings.Join(parts, "")
|
|
}
|
|
|
|
// createMethodsFile creates methods file using template
|
|
func createMethodsFile(filePath string, templateData TemplateData) error {
|
|
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Debug: Print template data
|
|
fmt.Printf("🔍 Debug: Creating methods file with %d endpoints\n", len(templateData.Endpoints))
|
|
for _, endpoint := range templateData.Endpoints {
|
|
fmt.Printf("🔍 Debug: Endpoint %s has methods: %v\n", endpoint.Name, endpoint.Methods)
|
|
}
|
|
|
|
// Create template with basic functions
|
|
tmpl := template.New("methods").Funcs(template.FuncMap{
|
|
"title": strings.Title,
|
|
"upper": strings.ToUpper,
|
|
"lower": strings.ToLower,
|
|
"index": func(slice []string, i int) string {
|
|
if i >= 0 && i < len(slice) {
|
|
return slice[i]
|
|
}
|
|
return ""
|
|
},
|
|
"add": func(a, b int) int {
|
|
return a + b
|
|
},
|
|
"sub": func(a, b int) int {
|
|
return a - b
|
|
},
|
|
})
|
|
|
|
tmpl, err := tmpl.Parse(handlerTemplate)
|
|
if err != nil {
|
|
return fmt.Errorf("parse template: %w", err)
|
|
}
|
|
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
return tmpl.Execute(file, templateData)
|
|
}
|
|
|
|
// generateNewMethodsOnly generates only new methods for existing files
|
|
func generateNewMethodsOnly(templateData TemplateData) (string, error) {
|
|
// Debug: Print template data
|
|
fmt.Printf("🔍 Debug: Generating new methods with %d endpoints\n", len(templateData.Endpoints))
|
|
for _, endpoint := range templateData.Endpoints {
|
|
fmt.Printf("🔍 Debug: Endpoint %s has methods: %v\n", endpoint.Name, endpoint.Methods)
|
|
}
|
|
|
|
// Create template with basic functions only
|
|
tmpl := template.New("newMethods").Funcs(template.FuncMap{
|
|
"title": strings.Title,
|
|
"upper": strings.ToUpper,
|
|
"lower": strings.ToLower,
|
|
"index": func(slice []string, i int) string {
|
|
if i >= 0 && i < len(slice) {
|
|
return slice[i]
|
|
}
|
|
return ""
|
|
},
|
|
})
|
|
|
|
tmpl, err := tmpl.Parse(methodsOnlyTemplate)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var result strings.Builder
|
|
err = tmpl.Execute(&result, templateData)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return result.String(), nil
|
|
}
|
|
|
|
// mergeGoFileContent merges new functions into existing file
|
|
func mergeGoFileContent(existingContent, newFunctions string) string {
|
|
// Find last closing brace
|
|
re := regexp.MustCompile(`}\s*$`)
|
|
lastBraceIndex := re.FindStringIndex(existingContent)
|
|
if lastBraceIndex == nil {
|
|
return existingContent + "\n" + newFunctions
|
|
}
|
|
|
|
before := existingContent[:lastBraceIndex[0]]
|
|
after := existingContent[lastBraceIndex[0]:]
|
|
|
|
return before + "\n" + newFunctions + "\n" + after
|
|
}
|
|
|
|
// ================== MAIN FUNCTION ==================
|
|
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]
|
|
}
|
|
|
|
config, err := loadConfig(configFile)
|
|
if err != nil {
|
|
fmt.Printf("Error loading config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("🚀 Starting Handler Generation with Validation...")
|
|
fmt.Printf("📁 Config file: %s\n", configFile)
|
|
if targetService != "" {
|
|
fmt.Printf("🎯 Target service: %s\n", targetService)
|
|
}
|
|
|
|
// Create context with cancellation for graceful shutdown
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Worker pool for concurrent processing
|
|
type result struct {
|
|
serviceName string
|
|
err error
|
|
}
|
|
results := make(chan result)
|
|
jobs := make(chan string, len(config.Services))
|
|
|
|
// Start worker pool
|
|
var wg sync.WaitGroup
|
|
numWorkers := 4 // Adjust based on your system
|
|
for i := 0; i < numWorkers; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for serviceName := range jobs {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case results <- result{
|
|
serviceName: serviceName,
|
|
err: generateHandlerWithValidation(ctx, serviceName, config.Services[serviceName], config.Global),
|
|
}:
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Send jobs to workers
|
|
go func() {
|
|
for serviceName := range config.Services {
|
|
if targetService != "" && serviceName != targetService {
|
|
continue
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
break
|
|
case jobs <- serviceName:
|
|
}
|
|
}
|
|
close(jobs)
|
|
}()
|
|
|
|
// Collect results
|
|
go func() {
|
|
wg.Wait()
|
|
close(results)
|
|
}()
|
|
|
|
// Process results
|
|
generated := 0
|
|
errors := 0
|
|
for res := range results {
|
|
if res.err != nil {
|
|
fmt.Printf("❌ Error generating handler for %s: %v\n", res.serviceName, res.err)
|
|
errors++
|
|
continue
|
|
}
|
|
generated++
|
|
}
|
|
|
|
// Summary
|
|
fmt.Println("\n📊 Generation Summary:")
|
|
fmt.Printf("✅ Successfully processed: %d services\n", generated)
|
|
if errors > 0 {
|
|
fmt.Printf("❌ Failed: %d services\n", errors)
|
|
}
|
|
if generated > 0 {
|
|
fmt.Println("🎉 Generation completed successfully!")
|
|
}
|
|
}
|
|
|
|
// loadConfig loads configuration from YAML file
|
|
func loadConfig(filename string) (*ServiceConfig, error) {
|
|
data, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var config ServiceConfig
|
|
err = yaml.Unmarshal(data, &config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set defaults
|
|
if config.Global.ModuleName == "" {
|
|
config.Global.ModuleName = "api-service"
|
|
}
|
|
if config.Global.OutputDir == "" {
|
|
config.Global.OutputDir = "internal/handlers"
|
|
}
|
|
|
|
// Debug: Print loaded configuration
|
|
fmt.Printf("🔍 Debug: Loaded %d services\n", len(config.Services))
|
|
|
|
// Process endpoint groups to add missing handler information
|
|
for serviceName, service := range config.Services {
|
|
fmt.Printf("🔍 Debug: Processing service %s with %d endpoints\n", serviceName, len(service.Endpoints))
|
|
|
|
for endpointName, endpointGroup := range service.Endpoints {
|
|
fmt.Printf("🔍 Debug: Processing endpoint %s with %d basic configs\n", endpointName, len(endpointGroup.Basic))
|
|
|
|
// Generate handler information based on service and endpoint names
|
|
endpointGroup.HandlerFolder = strings.ToLower(service.Package)
|
|
endpointGroup.HandlerFile = fmt.Sprintf("%s.go", strings.ToLower(service.Package))
|
|
endpointGroup.HandlerName = toCamelCase(serviceName)
|
|
|
|
// Convert basic map to functions map
|
|
endpointGroup.Functions = make(map[string]FunctionConfig)
|
|
|
|
// Debug: Print the structure of basic configs
|
|
fmt.Printf("🔍 Debug: Processing basic config for endpoint %s\n", endpointName)
|
|
|
|
// Treat the entire Basic map as a single function configuration
|
|
funcConfig := FunctionConfig{}
|
|
|
|
// Process each field in the basic config
|
|
for key, value := range endpointGroup.Basic {
|
|
keyStr := fmt.Sprintf("%v", key)
|
|
processYAMLField(keyStr, value, &funcConfig)
|
|
}
|
|
|
|
fmt.Printf("🔍 Debug: Final function config for %s: methods=%v, get_path=%s\n",
|
|
endpointName, funcConfig.Methods, funcConfig.GetPath)
|
|
|
|
endpointGroup.Functions["basic"] = funcConfig
|
|
|
|
// Update the service endpoints
|
|
service.Endpoints[endpointName] = endpointGroup
|
|
}
|
|
config.Services[serviceName] = service
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// processYAMLField processes a single YAML field and updates the function config
|
|
func processYAMLField(key string, value interface{}, funcConfig *FunctionConfig) {
|
|
fmt.Printf("🔍 Debug: Processing key %s with value %v (type: %T)\n", key, value, value)
|
|
|
|
switch key {
|
|
case "methods":
|
|
if methods, ok := value.([]interface{}); ok {
|
|
for _, method := range methods {
|
|
if methodStr, ok := method.(string); ok {
|
|
funcConfig.Methods = append(funcConfig.Methods, methodStr)
|
|
fmt.Printf("🔍 Debug: Added method %s\n", methodStr)
|
|
}
|
|
}
|
|
}
|
|
case "get_path":
|
|
if path, ok := value.(string); ok {
|
|
funcConfig.GetPath = path
|
|
fmt.Printf("🔍 Debug: Set get_path to %s\n", path)
|
|
}
|
|
case "post_path":
|
|
if path, ok := value.(string); ok {
|
|
funcConfig.PostPath = path
|
|
fmt.Printf("🔍 Debug: Set post_path to %s\n", path)
|
|
}
|
|
case "put_path":
|
|
if path, ok := value.(string); ok {
|
|
funcConfig.PutPath = path
|
|
fmt.Printf("🔍 Debug: Set put_path to %s\n", path)
|
|
}
|
|
case "patch_path":
|
|
if path, ok := value.(string); ok {
|
|
funcConfig.PatchPath = path
|
|
fmt.Printf("🔍 Debug: Set patch_path to %s\n", path)
|
|
}
|
|
case "delete_path":
|
|
if path, ok := value.(string); ok {
|
|
funcConfig.DeletePath = path
|
|
fmt.Printf("🔍 Debug: Set delete_path to %s\n", path)
|
|
}
|
|
case "search_path":
|
|
if path, ok := value.(string); ok {
|
|
funcConfig.SearchPath = path
|
|
fmt.Printf("🔍 Debug: Set search_path to %s\n", path)
|
|
}
|
|
case "model":
|
|
if model, ok := value.(string); ok {
|
|
funcConfig.Model = model
|
|
fmt.Printf("🔍 Debug: Set model to %s\n", model)
|
|
}
|
|
case "response_model":
|
|
if model, ok := value.(string); ok {
|
|
funcConfig.ResponseModel = model
|
|
fmt.Printf("🔍 Debug: Set response_model to %s\n", model)
|
|
}
|
|
case "description":
|
|
if desc, ok := value.(string); ok {
|
|
funcConfig.Description = desc
|
|
fmt.Printf("🔍 Debug: Set description to %s\n", desc)
|
|
}
|
|
case "summary":
|
|
if summary, ok := value.(string); ok {
|
|
funcConfig.Summary = summary
|
|
fmt.Printf("🔍 Debug: Set summary to %s\n", summary)
|
|
}
|
|
case "tags":
|
|
if tags, ok := value.([]interface{}); ok {
|
|
for _, tag := range tags {
|
|
if tagStr, ok := tag.(string); ok {
|
|
funcConfig.Tags = append(funcConfig.Tags, tagStr)
|
|
fmt.Printf("🔍 Debug: Added tag %s\n", tagStr)
|
|
}
|
|
}
|
|
}
|
|
case "require_auth":
|
|
if auth, ok := value.(bool); ok {
|
|
funcConfig.RequireAuth = auth
|
|
fmt.Printf("🔍 Debug: Set require_auth to %t\n", auth)
|
|
}
|
|
case "cache_enabled":
|
|
if cache, ok := value.(bool); ok {
|
|
funcConfig.CacheEnabled = cache
|
|
fmt.Printf("🔍 Debug: Set cache_enabled to %t\n", cache)
|
|
}
|
|
case "cache_ttl":
|
|
if ttl, ok := value.(int); ok {
|
|
funcConfig.CacheTTL = ttl
|
|
fmt.Printf("🔍 Debug: Set cache_ttl to %d\n", ttl)
|
|
}
|
|
case "fhir_profiles":
|
|
if profiles, ok := value.([]interface{}); ok {
|
|
for _, profile := range profiles {
|
|
if profileStr, ok := profile.(string); ok {
|
|
funcConfig.FhirProfiles = append(funcConfig.FhirProfiles, profileStr)
|
|
fmt.Printf("🔍 Debug: Added fhir_profile %s\n", profileStr)
|
|
}
|
|
}
|
|
}
|
|
case "search_params":
|
|
if params, ok := value.([]interface{}); ok {
|
|
for _, param := range params {
|
|
if paramStr, ok := param.(string); ok {
|
|
funcConfig.SearchParams = append(funcConfig.SearchParams, paramStr)
|
|
fmt.Printf("🔍 Debug: Added search_param %s\n", paramStr)
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
fmt.Printf("⚠️ Debug: Unknown key %s\n", key)
|
|
}
|
|
}
|
|
|
|
// printUsage prints usage information
|
|
func printUsage() {
|
|
fmt.Println("Satu Sehat FHIR Handler Generator")
|
|
fmt.Println()
|
|
fmt.Println("Usage:")
|
|
fmt.Println(" go run generate-satusehat-handler.go <config-file> [service-name]")
|
|
fmt.Println()
|
|
fmt.Println("Examples:")
|
|
fmt.Println(" go run generate-satusehat-handler.go services-config-satusehat.yaml")
|
|
fmt.Println(" go run generate-satusehat-handler.go services-config-satusehat.yaml patient")
|
|
}
|
|
|
|
// ================== TEMPLATES ==================
|
|
const handlerTemplate = `// Package {{.Package}} handles {{.HandlerName}} services
|
|
// Generated on: {{.Timestamp}}
|
|
package {{.Package}}
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"{{.ModuleName}}/internal/config"
|
|
"{{.ModuleName}}/internal/models/{{.Package}}"
|
|
"{{.ModuleName}}/pkg/logger"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// {{.HandlerName}} handles {{.HandlerName}} services
|
|
type {{.HandlerName}} struct {
|
|
service interface{}
|
|
validator *validator.Validate
|
|
logger logger.Logger
|
|
config *config.Config
|
|
}
|
|
|
|
// {{.HandlerName}}Config contains configuration for {{.HandlerName}}
|
|
type {{.HandlerName}}Config struct {
|
|
Config *config.Config
|
|
Logger logger.Logger
|
|
Validator *validator.Validate
|
|
}
|
|
|
|
// New{{.HandlerName}} creates a new {{.HandlerName}}
|
|
func New{{.HandlerName}}(cfg {{.HandlerName}}Config) *{{.HandlerName}} {
|
|
return &{{.HandlerName}}{
|
|
service: nil, // Initialize with appropriate service
|
|
validator: cfg.Validator,
|
|
logger: cfg.Logger,
|
|
config: cfg.Config,
|
|
}
|
|
}
|
|
|
|
{{range .Endpoints}}
|
|
{{if .HasPost}}
|
|
// Create{{.Name}} creates a new resource
|
|
// @Summary Create a new resource
|
|
// @Description Create a new resource in the system
|
|
// @Tags {{index .Tags 0}}
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Authorization header string true "Bearer token"
|
|
// @Param request body models.{{.Name}}CreateRequest true "Resource creation request"
|
|
// @Success 201 {object} models.{{.Name}}Response "Resource created successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 401 {object} models.ErrorResponse "Unauthorized"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router {{.PostPath}} [post]
|
|
func (h *{{$.HandlerName}}) Create{{.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 Create{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.PostPath}}",
|
|
})
|
|
{{end}}
|
|
|
|
// Bind and validate request body
|
|
var req models.{{.Name}}CreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Failed to bind request body", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Invalid request body: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Validate request structure
|
|
if err := h.validator.Struct(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Request validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Validation failed: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.CreateResource(ctx, req)
|
|
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}Response{
|
|
Status: "success",
|
|
Message: "{{.Name}} created successfully",
|
|
RequestID: requestID,
|
|
Data: nil, // Replace with actual data
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasGet}}
|
|
// Get{{.Name}} retrieves a specific resource by ID
|
|
// @Summary Get a specific resource by ID
|
|
// @Description Retrieve a specific resource from the system
|
|
// @Tags {{index .Tags 0}}
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Authorization header string true "Bearer token"
|
|
// @Param id path string true "Resource ID"
|
|
// @Success 200 {object} models.{{.Name}}Response "Resource retrieved successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 401 {object} models.ErrorResponse "Unauthorized"
|
|
// @Failure 404 {object} models.ErrorResponse "Resource not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router {{.GetPath}} [get]
|
|
func (h *{{$.HandlerName}}) 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)
|
|
}
|
|
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Missing required parameter id", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Missing required parameter id",
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.GetPath}}",
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.GetResource(ctx, id)
|
|
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}Response{
|
|
Status: "success",
|
|
Message: "{{.Name}} retrieved successfully",
|
|
RequestID: requestID,
|
|
Data: nil, // Replace with actual data
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully retrieved {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasPut}}
|
|
// Update{{.Name}} updates an existing resource
|
|
// @Summary Update an existing resource
|
|
// @Description Update an existing resource in the system
|
|
// @Tags {{index .Tags 0}}
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Authorization header string true "Bearer token"
|
|
// @Param id path string true "Resource ID"
|
|
// @Param request body models.{{.Name}}UpdateRequest true "Resource update request"
|
|
// @Success 200 {object} models.{{.Name}}Response "Resource updated successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 401 {object} models.ErrorResponse "Unauthorized"
|
|
// @Failure 404 {object} models.ErrorResponse "Resource not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router {{.PutPath}} [put]
|
|
func (h *{{$.HandlerName}}) Update{{.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)
|
|
}
|
|
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Missing required parameter id", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Missing required parameter id",
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.PutPath}}",
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
// Bind and validate request body
|
|
var req models.{{.Name}}UpdateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Failed to bind request body", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Invalid request body: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Validate request structure
|
|
if err := h.validator.Struct(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Request validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Validation failed: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.UpdateResource(ctx, id, req)
|
|
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}Response{
|
|
Status: "success",
|
|
Message: "{{.Name}} updated successfully",
|
|
RequestID: requestID,
|
|
Data: nil, // Replace with actual data
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasDelete}}
|
|
// Delete{{.Name}} deletes a resource
|
|
// @Summary Delete a resource
|
|
// @Description Delete a resource from the system
|
|
// @Tags {{index .Tags 0}}
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Authorization header string true "Bearer token"
|
|
// @Param id path string true "Resource ID"
|
|
// @Success 200 {object} models.SuccessResponse "Resource deleted successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 401 {object} models.ErrorResponse "Unauthorized"
|
|
// @Failure 404 {object} models.ErrorResponse "Resource not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router {{.DeletePath}} [delete]
|
|
func (h *{{$.HandlerName}}) Delete{{.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)
|
|
}
|
|
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Missing required parameter id", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Missing required parameter id",
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.DeletePath}}",
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
// TODO: Implement service call
|
|
// Example: err := h.service.DeleteResource(ctx, id)
|
|
|
|
// For now, return a mock response
|
|
response := models.SuccessResponse{
|
|
Status: "success",
|
|
Message: "{{.Name}} deleted successfully",
|
|
RequestID: requestID,
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasSearch}}
|
|
// Search{{.Name}} searches for resources
|
|
// @Summary Search for resources
|
|
// @Description Search for resources in the system
|
|
// @Tags {{index .Tags 0}}
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Authorization header string true "Bearer token"
|
|
// @Param query query string false "Search query"
|
|
// @Param limit query int false "Limit results" default(10)
|
|
// @Param offset query int false "Offset results" default(0)
|
|
// @Success 200 {object} models.{{.Name}}SearchResponse "Search results"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 401 {object} models.ErrorResponse "Unauthorized"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router {{.SearchPath}} [get]
|
|
func (h *{{$.HandlerName}}) Search{{.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)
|
|
}
|
|
|
|
// Get query parameters
|
|
query := c.Query("query")
|
|
limit := 10
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
offset := 0
|
|
if o := c.Query("offset"); o != "" {
|
|
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
|
offset = parsed
|
|
}
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Search{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.SearchPath}}",
|
|
"query": query,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
{{end}}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.SearchResources(ctx, query, limit, offset)
|
|
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}SearchResponse{
|
|
Status: "success",
|
|
Message: "Search completed successfully",
|
|
RequestID: requestID,
|
|
Data: models.SearchData{
|
|
Results: []interface{}{}, // Replace with actual results
|
|
Total: 0,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
},
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully completed search", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"query": query,
|
|
"results": len(response.Data.Results),
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{end}}`
|
|
|
|
const methodsOnlyTemplate = `{{range .Endpoints}}
|
|
{{if .HasPost}}
|
|
// Create{{.Name}} creates a new resource
|
|
func (h *{{$.HandlerName}}) Create{{.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 Create{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.PostPath}}",
|
|
})
|
|
{{end}}
|
|
|
|
// Bind and validate request body
|
|
var req models.{{.Name}}CreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Failed to bind request body", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Invalid request body: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Validate request structure
|
|
if err := h.validator.Struct(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Request validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Validation failed: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.CreateResource(ctx, req)
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}Response{
|
|
Status: "success",
|
|
Message: "{{.Name}} created successfully",
|
|
RequestID: requestID,
|
|
Data: nil, // Replace with actual data
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasGet}}
|
|
// Get{{.Name}} retrieves a specific resource by ID
|
|
func (h *{{$.HandlerName}}) 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)
|
|
}
|
|
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Missing required parameter id", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Missing required parameter id",
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.GetPath}}",
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.GetResource(ctx, id)
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}Response{
|
|
Status: "success",
|
|
Message: "{{.Name}} retrieved successfully",
|
|
RequestID: requestID,
|
|
Data: nil, // Replace with actual data
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully retrieved {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasPut}}
|
|
// Update{{.Name}} updates an existing resource
|
|
func (h *{{$.HandlerName}}) Update{{.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)
|
|
}
|
|
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Missing required parameter id", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Missing required parameter id",
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.PutPath}}",
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
// Bind and validate request body
|
|
var req models.{{.Name}}UpdateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Failed to bind request body", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Invalid request body: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Validate request structure
|
|
if err := h.validator.Struct(&req); err != nil {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Request validation failed", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Validation failed: " + err.Error(),
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.UpdateResource(ctx, id, req)
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}Response{
|
|
Status: "success",
|
|
Message: "{{.Name}} updated successfully",
|
|
RequestID: requestID,
|
|
Data: nil, // Replace with actual data
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasDelete}}
|
|
// Delete{{.Name}} deletes a resource
|
|
func (h *{{$.HandlerName}}) Delete{{.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)
|
|
}
|
|
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
{{if $.HasLogger}}
|
|
h.logger.Error("Missing required parameter id", map[string]interface{}{
|
|
"request_id": requestID,
|
|
})
|
|
{{end}}
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Status: "error",
|
|
Message: "Missing required parameter id",
|
|
RequestID: requestID,
|
|
})
|
|
return
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.DeletePath}}",
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
// TODO: Implement service call
|
|
// Example: err := h.service.DeleteResource(ctx, id)
|
|
// For now, return a mock response
|
|
response := models.SuccessResponse{
|
|
Status: "success",
|
|
Message: "{{.Name}} deleted successfully",
|
|
RequestID: requestID,
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"id": id,
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{if .HasSearch}}
|
|
// Search{{.Name}} searches for resources
|
|
func (h *{{$.HandlerName}}) Search{{.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)
|
|
}
|
|
|
|
// Get query parameters
|
|
query := c.Query("query")
|
|
limit := 10
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
offset := 0
|
|
if o := c.Query("offset"); o != "" {
|
|
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
|
offset = parsed
|
|
}
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Processing Search{{.Name}} request", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"endpoint": "{{.SearchPath}}",
|
|
"query": query,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
{{end}}
|
|
|
|
// TODO: Implement service call
|
|
// Example: resp, err := h.service.SearchResources(ctx, query, limit, offset)
|
|
// For now, return a mock response
|
|
response := models.{{.Name}}SearchResponse{
|
|
Status: "success",
|
|
Message: "Search completed successfully",
|
|
RequestID: requestID,
|
|
Data: models.SearchData{
|
|
Results: []interface{}{}, // Replace with actual results
|
|
Total: 0,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
},
|
|
}
|
|
|
|
{{if $.HasLogger}}
|
|
h.logger.Info("Successfully completed search", map[string]interface{}{
|
|
"request_id": requestID,
|
|
"query": query,
|
|
"results": len(response.Data.Results),
|
|
})
|
|
{{end}}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
{{end}}
|
|
{{end}}`
|
|
|
|
const modelTemplate = `package models
|
|
|
|
import (
|
|
"time"
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
// BaseRequest contains common fields for all requests
|
|
type BaseRequest struct {
|
|
RequestID string ` + "`json:\"request_id,omitempty\"`" + `
|
|
Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + `
|
|
}
|
|
|
|
// BaseResponse contains common fields for all responses
|
|
type BaseResponse struct {
|
|
Status string ` + "`json:\"status\"`" + `
|
|
Message string ` + "`json:\"message\"`" + `
|
|
Data interface{} ` + "`json:\"data,omitempty\"`" + `
|
|
RequestID string ` + "`json:\"request_id,omitempty\"`" + `
|
|
}
|
|
|
|
// ErrorResponse represents an error response
|
|
type ErrorResponse struct {
|
|
Status string ` + "`json:\"status\"`" + `
|
|
Message string ` + "`json:\"message\"`" + `
|
|
RequestID string ` + "`json:\"request_id,omitempty\"`" + `
|
|
}
|
|
|
|
// SuccessResponse represents a success response
|
|
type SuccessResponse struct {
|
|
Status string ` + "`json:\"status\"`" + `
|
|
Message string ` + "`json:\"message\"`" + `
|
|
RequestID string ` + "`json:\"request_id,omitempty\"`" + `
|
|
}
|
|
|
|
// SearchData contains search results
|
|
type SearchData struct {
|
|
Results []interface{} ` + "`json:\"results\"`" + `
|
|
Total int ` + "`json:\"total\"`" + `
|
|
Limit int ` + "`json:\"limit\"`" + `
|
|
Offset int ` + "`json:\"offset\"`" + `
|
|
}
|
|
|
|
// {{.ServiceName}} Models
|
|
// Generated at: {{.Timestamp}}
|
|
|
|
// {{.ServiceName}}CreateRequest represents a request to create a {{.ServiceName}}
|
|
type {{.ServiceName}}CreateRequest struct {
|
|
BaseRequest
|
|
// Add fields specific to {{.ServiceName}} creation
|
|
Name string ` + "`json:\"name\" validate:\"required\"`" + `
|
|
Description string ` + "`json:\"description\" validate:\"omitempty\"`" + `
|
|
}
|
|
|
|
// {{.ServiceName}}UpdateRequest represents a request to update a {{.ServiceName}}
|
|
type {{.ServiceName}}UpdateRequest struct {
|
|
BaseRequest
|
|
ID string ` + "`json:\"id\" validate:\"required,uuid\"`" + `
|
|
Name string ` + "`json:\"name\" validate:\"required\"`" + `
|
|
Description string ` + "`json:\"description\" validate:\"omitempty\"`" + `
|
|
}
|
|
|
|
// {{.ServiceName}}Response represents a response for {{.ServiceName}} operations
|
|
type {{.ServiceName}}Response struct {
|
|
BaseResponse
|
|
}
|
|
|
|
// {{.ServiceName}}SearchResponse represents a response for {{.ServiceName}} search
|
|
type {{.ServiceName}}SearchResponse struct {
|
|
BaseResponse
|
|
Data SearchData ` + "`json:\"data\"`" + `
|
|
}
|
|
|
|
// {{.ServiceName}}Data represents the data structure for {{.ServiceName}}
|
|
type {{.ServiceName}}Data struct {
|
|
ID string ` + "`json:\"id\"`" + `
|
|
Name string ` + "`json:\"name\"`" + `
|
|
Description string ` + "`json:\"description,omitempty\"`" + `
|
|
CreatedAt time.Time ` + "`json:\"created_at\"`" + `
|
|
UpdatedAt time.Time ` + "`json:\"updated_at,omitempty\"`" + `
|
|
}
|
|
|
|
// Validate validates the {{.ServiceName}}CreateRequest
|
|
func (r *{{.ServiceName}}CreateRequest) Validate() error {
|
|
validate := validator.New()
|
|
return validate.Struct(r)
|
|
}
|
|
|
|
// Validate validates the {{.ServiceName}}UpdateRequest
|
|
func (r *{{.ServiceName}}UpdateRequest) Validate() error {
|
|
validate := validator.New()
|
|
return validate.Struct(r)
|
|
}
|
|
|
|
// New{{.ServiceName}}Data creates a new {{.ServiceName}}Data with default values
|
|
func New{{.ServiceName}}Data(name, description string) *{{.ServiceName}}Data {
|
|
now := time.Now()
|
|
return &{{.ServiceName}}Data{
|
|
ID: "", // Will be set by the database
|
|
Name: name,
|
|
Description: description,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
}`
|