package bun import ( "sort" "strings" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // TemplateData represents the data passed to the template for code generation type TemplateData struct { PackageName string Imports []string Models []*ModelData Config *MethodConfig } // ModelData represents a single model/struct in the template type ModelData struct { Name string TableName string // schema.table format SchemaName string TableNameOnly string // just table name without schema Comment string Fields []*FieldData Config *MethodConfig PrimaryKeyField string // Name of the primary key field PrimaryKeyIsSQL bool // Whether PK uses SQL type (needs .Int64() call) IDColumnName string // Name of the ID column in database Prefix string // 3-letter prefix } // FieldData represents a single field in a struct type FieldData struct { Name string // Go field name (PascalCase) Type string // Go type BunTag string // Complete bun tag JSONTag string // JSON tag Comment string // Field comment } // MethodConfig controls which helper methods to generate type MethodConfig struct { GenerateTableName bool GenerateSchemaName bool GenerateTableNameOnly bool GenerateGetID bool GenerateGetIDStr bool GenerateSetID bool GenerateUpdateID bool GenerateGetIDName bool GenerateGetPrefix bool } // DefaultMethodConfig returns a MethodConfig with all methods enabled func DefaultMethodConfig() *MethodConfig { return &MethodConfig{ GenerateTableName: true, GenerateSchemaName: true, GenerateTableNameOnly: true, GenerateGetID: true, GenerateGetIDStr: true, GenerateSetID: true, GenerateUpdateID: true, GenerateGetIDName: true, GenerateGetPrefix: true, } } // NewTemplateData creates a new TemplateData with the given package name and config func NewTemplateData(packageName string, config *MethodConfig) *TemplateData { if config == nil { config = DefaultMethodConfig() } return &TemplateData{ PackageName: packageName, Imports: make([]string, 0), Models: make([]*ModelData, 0), Config: config, } } // AddModel adds a model to the template data func (td *TemplateData) AddModel(model *ModelData) { model.Config = td.Config td.Models = append(td.Models, model) } // AddImport adds an import to the template data (deduplicates automatically) func (td *TemplateData) AddImport(importPath string) { // Check if already exists for _, imp := range td.Imports { if imp == importPath { return } } td.Imports = append(td.Imports, importPath) } // FinalizeImports sorts and organizes imports func (td *TemplateData) FinalizeImports() { // Sort imports alphabetically sort.Strings(td.Imports) } // NewModelData creates a new ModelData from a models.Table func NewModelData(table *models.Table, schema string, typeMapper *TypeMapper) *ModelData { tableName := table.Name if schema != "" { tableName = schema + "." + table.Name } // Generate model name: singularize and convert to PascalCase singularTable := Singularize(table.Name) modelName := SnakeCaseToPascalCase(singularTable) // Add "Model" prefix if not already present if !hasModelPrefix(modelName) { modelName = "Model" + modelName } model := &ModelData{ Name: modelName, TableName: tableName, SchemaName: schema, TableNameOnly: table.Name, Comment: formatComment(table.Description, table.Comment), Fields: make([]*FieldData, 0), Prefix: GeneratePrefix(table.Name), } // Find primary key for _, col := range table.Columns { if col.IsPrimaryKey { model.PrimaryKeyField = SnakeCaseToPascalCase(col.Name) model.IDColumnName = col.Name // Check if PK type is a SQL type (contains resolvespec_common or sql_types) goType := typeMapper.SQLTypeToGoType(col.Type, col.NotNull) model.PrimaryKeyIsSQL = strings.Contains(goType, "resolvespec_common") || strings.Contains(goType, "sql_types") break } } // Convert columns to fields (sorted by sequence or name) columns := sortColumns(table.Columns) for _, col := range columns { field := columnToField(col, table, typeMapper) model.Fields = append(model.Fields, field) } return model } // columnToField converts a models.Column to FieldData func columnToField(col *models.Column, table *models.Table, typeMapper *TypeMapper) *FieldData { fieldName := SnakeCaseToPascalCase(col.Name) goType := typeMapper.SQLTypeToGoType(col.Type, col.NotNull) bunTag := typeMapper.BuildBunTag(col, table) jsonTag := col.Name // Use column name for JSON tag return &FieldData{ Name: fieldName, Type: goType, BunTag: bunTag, JSONTag: jsonTag, Comment: formatComment(col.Description, col.Comment), } } // AddRelationshipField adds a relationship field to the model func (md *ModelData) AddRelationshipField(field *FieldData) { md.Fields = append(md.Fields, field) } // formatComment combines description and comment into a single comment string func formatComment(description, comment string) string { if description != "" && comment != "" { return description + " - " + comment } if description != "" { return description } return comment } // hasModelPrefix checks if a name already has "Model" prefix func hasModelPrefix(name string) bool { return len(name) >= 5 && name[:5] == "Model" } // sortColumns sorts columns by sequence, then by name func sortColumns(columns map[string]*models.Column) []*models.Column { result := make([]*models.Column, 0, len(columns)) for _, col := range columns { result = append(result, col) } sort.Slice(result, func(i, j int) bool { // Sort by sequence if both have it if result[i].Sequence > 0 && result[j].Sequence > 0 { return result[i].Sequence < result[j].Sequence } // Put primary keys first if result[i].IsPrimaryKey != result[j].IsPrimaryKey { return result[i].IsPrimaryKey } // Otherwise sort alphabetically return result[i].Name < result[j].Name }) return result } // LoadMethodConfigFromMetadata loads method configuration from metadata map func LoadMethodConfigFromMetadata(metadata map[string]interface{}) *MethodConfig { config := DefaultMethodConfig() if metadata == nil { return config } // Load each setting from metadata if present if val, ok := metadata["generate_table_name"].(bool); ok { config.GenerateTableName = val } if val, ok := metadata["generate_schema_name"].(bool); ok { config.GenerateSchemaName = val } if val, ok := metadata["generate_table_name_only"].(bool); ok { config.GenerateTableNameOnly = val } if val, ok := metadata["generate_get_id"].(bool); ok { config.GenerateGetID = val } if val, ok := metadata["generate_get_id_str"].(bool); ok { config.GenerateGetIDStr = val } if val, ok := metadata["generate_set_id"].(bool); ok { config.GenerateSetID = val } if val, ok := metadata["generate_update_id"].(bool); ok { config.GenerateUpdateID = val } if val, ok := metadata["generate_get_id_name"].(bool); ok { config.GenerateGetIDName = val } if val, ok := metadata["generate_get_prefix"].(bool); ok { config.GenerateGetPrefix = val } return config }