package gormcol import ( "fmt" "reflect" "strings" "gorm.io/gorm/schema" ) // NamingStrategy is the GORM naming strategy used for column/table name resolution. // Defaults to gorm.io/gorm/schema.NamingStrategy. // Set this to NamingStrategy when using within a project that has a database connection. var NamingStrategy schema.Namer = schema.NamingStrategy{} type Tabler interface { TableName() string } // Field represents a GORM column descriptor with table context. // Define package-level variables to get type-safe column references: // // var PsAccess = struct { // IDProfile gormcol.Field // IDAuthorizationRole gormcol.Field // }{ // IDProfile: gormcol.Field{Table: "ps_access", Column: "id_profile"}, // IDAuthorizationRole: gormcol.Field{Table: "ps_access", Column: "id_authorization_role"}, // } type Field struct { Table string Column string } // Column returns the column name from a Field descriptor. // // gormcol.Column(prestadb.PsAccess.IDAuthorizationRole) // "id_authorization_role" func Column(f Field) string { return f.Column } // ColumnOnTable returns "table.column" from a Field descriptor. // // gormcol.ColumnOnTable(prestadb.PsAccess.IDAuthorizationRole) // "ps_access.id_authorization_role" func ColumnOnTable(f Field) string { return f.Table + "." + f.Column } // TableField returns the table name from a Field descriptor. func TableField(f Field) string { return f.Table } // -- Reflection-based utilities (for structs without Field descriptors) -- // Col extracts the GORM column name from the `gorm:"column:..."` struct tag. // // gormcol.Col[prestadb.PsAccess]("IDAuthorizationRole") // "id_authorization_role" func Col[T any](fieldName string) string { var zero T typ := reflect.TypeOf(zero) return columnFromType(typ, fieldName) } // ColOf extracts a GORM column name from a field on a nested struct referenced // by structFieldName. func ColOf[T, S any](structFieldName, fieldName string) string { var zero T typ := reflect.TypeOf(zero) field, ok := typ.FieldByName(structFieldName) if !ok { panic(fmt.Sprintf("gormcol: struct %q has no field %q", typ.Name(), structFieldName)) } return columnFromType(field.Type, fieldName) } // Tbl returns the table name for a GORM model. // Uses TableName() method if the struct implements Tabler, // otherwise falls back to the GORM naming strategy. // // gormcol.Tbl[prestadb.PsAccess]() // "ps_access" func Tbl[T any]() string { var zero T if t, ok := any(zero).(Tabler); ok { return t.TableName() } typ := reflect.TypeOf(zero) return NamingStrategy.TableName(typ.Name()) } // ColOnTbl returns the column prefixed with the table name (reflection-based). // // gormcol.ColOnTbl[prestadb.PsAccess]("IDAuthorizationRole") // "ps_access.id_authorization_role" func ColOnTbl[T any](fieldName string) string { return Tbl[T]() + "." + Col[T](fieldName) } // PK returns the primary key column name for a GORM model. // // gormcol.PK[prestadb.PsAccess]() // "id_authorization_role" func PK[T any]() string { var zero T typ := reflect.TypeOf(zero) for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) gormTag := field.Tag.Get("gorm") if gormTag == "" || gormTag == "-" { continue } for _, part := range strings.Split(gormTag, ";") { if strings.TrimSpace(part) == "primaryKey" { return parseGormColumn(gormTag) } } } return "" } // Columns returns all GORM column names for a struct type. // // gormcol.Columns[prestadb.PsAccess]() // []string{"id_profile", "id_authorization_role"} func Columns[T any]() []string { var zero T typ := reflect.TypeOf(zero) var cols []string for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) if field.PkgPath != "" { continue } col := parseGormColumn(field.Tag.Get("gorm")) if col == "" { col = NamingStrategy.ColumnName(typ.Name(), field.Name) } cols = append(cols, col) } return cols } // ColByJSON finds the GORM column name by matching the struct field's json tag. // // gormcol.ColByJSON[prestadb.PsAccess]("id_authorization_role") // "id_authorization_role" func ColByJSON[T any](jsonName string) string { var zero T typ := reflect.TypeOf(zero) for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) jsonTag := field.Tag.Get("json") if jsonTag == "" { continue } tagName := strings.Split(jsonTag, ",")[0] if tagName == jsonName { return columnFromType(typ, field.Name) } } panic(fmt.Sprintf("gormcol: struct %q has no field with json tag %q", typ.Name(), jsonName)) } func columnFromType(typ reflect.Type, fieldName string) string { field, ok := typ.FieldByName(fieldName) if !ok { panic(fmt.Sprintf("gormcol: struct %q has no field %q", typ.Name(), fieldName)) } gormTag := field.Tag.Get("gorm") if gormTag == "" || gormTag == "-" { return NamingStrategy.ColumnName(typ.Name(), fieldName) } col := parseGormColumn(gormTag) if col != "" { return col } return NamingStrategy.ColumnName(typ.Name(), fieldName) }