@@ -91,16 +91,17 @@ func (r *Registry) GetMetadataByTable(tableName string) (*Metadata, error) {
9191
9292// Metadata holds all metadata for a model
9393type Metadata struct {
94- Type reflect.Type
95- TableName string
96- PrimaryKey * KeySchema
97- Indexes []IndexSchema
98- Fields map [string ]* FieldMetadata
99- FieldsByDBName map [string ]* FieldMetadata
100- VersionField * FieldMetadata
101- TTLField * FieldMetadata
102- CreatedAtField * FieldMetadata
103- UpdatedAtField * FieldMetadata
94+ Type reflect.Type
95+ TableName string
96+ NamingConvention naming.Convention
97+ PrimaryKey * KeySchema
98+ Indexes []IndexSchema
99+ Fields map [string ]* FieldMetadata
100+ FieldsByDBName map [string ]* FieldMetadata
101+ VersionField * FieldMetadata
102+ TTLField * FieldMetadata
103+ CreatedAtField * FieldMetadata
104+ UpdatedAtField * FieldMetadata
104105}
105106
106107// KeySchema represents a primary key or index key schema
@@ -188,12 +189,16 @@ func parseMetadata(modelType reflect.Type) (*Metadata, error) {
188189 tableName = getTableName (modelType )
189190 }
190191
192+ // Detect naming convention from struct tags
193+ convention := detectNamingConvention (modelType )
194+
191195 metadata := & Metadata {
192- Type : modelType ,
193- TableName : tableName ,
194- Fields : make (map [string ]* FieldMetadata ),
195- FieldsByDBName : make (map [string ]* FieldMetadata ),
196- Indexes : make ([]IndexSchema , 0 ),
196+ Type : modelType ,
197+ TableName : tableName ,
198+ NamingConvention : convention ,
199+ Fields : make (map [string ]* FieldMetadata ),
200+ FieldsByDBName : make (map [string ]* FieldMetadata ),
201+ Indexes : make ([]IndexSchema , 0 ),
197202 }
198203
199204 indexMap := make (map [string ]* IndexSchema )
@@ -244,7 +249,7 @@ func parseFields(modelType reflect.Type, metadata *Metadata, indexMap map[string
244249 }
245250
246251 // Parse regular field
247- fieldMeta , err := parseFieldMetadata (field , currentPath )
252+ fieldMeta , err := parseFieldMetadata (field , currentPath , metadata . NamingConvention )
248253 if err != nil {
249254 return fmt .Errorf ("field validation failed: %w" , err )
250255 }
@@ -338,11 +343,11 @@ func parseFields(modelType reflect.Type, metadata *Metadata, indexMap map[string
338343}
339344
340345// parseFieldMetadata parses metadata for a single field
341- func parseFieldMetadata (field reflect.StructField , indexPath []int ) (* FieldMetadata , error ) {
346+ func parseFieldMetadata (field reflect.StructField , indexPath []int , convention naming. Convention ) (* FieldMetadata , error ) {
342347 meta := & FieldMetadata {
343348 Name : field .Name ,
344349 Type : field .Type ,
345- DBName : naming .DefaultAttrName (field .Name ),
350+ DBName : naming .ConvertAttrName (field .Name , convention ),
346351 Index : indexPath [len (indexPath )- 1 ], // Keep for backward compatibility
347352 IndexPath : indexPath ,
348353 Tags : make (map [string ]string ),
@@ -443,7 +448,7 @@ func parseFieldMetadata(field reflect.StructField, indexPath []int) (*FieldMetad
443448 return nil , err
444449 }
445450
446- if err := naming .ValidateAttrName (meta .DBName ); err != nil {
451+ if err := naming .ValidateAttrName (meta .DBName , convention ); err != nil {
447452 return nil , fmt .Errorf ("%w: %v" , errors .ErrInvalidTag , err )
448453 }
449454
@@ -593,6 +598,38 @@ func splitTags(tag string) []string {
593598 return parts
594599}
595600
601+ // detectNamingConvention scans struct fields for a naming convention tag.
602+ // It looks for a field (typically blank identifier _) with tag `dynamorm:"naming:snake_case"`.
603+ // Returns CamelCase (default) if no naming tag is found.
604+ func detectNamingConvention (modelType reflect.Type ) naming.Convention {
605+ for i := 0 ; i < modelType .NumField (); i ++ {
606+ field := modelType .Field (i )
607+ tag := field .Tag .Get ("dynamorm" )
608+
609+ if tag == "" {
610+ continue
611+ }
612+
613+ // Look for naming:snake_case or naming:camel_case
614+ parts := strings .Split (tag , "," )
615+ for _ , part := range parts {
616+ part = strings .TrimSpace (part )
617+ if strings .HasPrefix (part , "naming:" ) {
618+ convention := strings .TrimPrefix (part , "naming:" )
619+ switch convention {
620+ case "snake_case" :
621+ return naming .SnakeCase
622+ case "camel_case" , "camelCase" :
623+ return naming .CamelCase
624+ }
625+ }
626+ }
627+ }
628+
629+ // Default to CamelCase
630+ return naming .CamelCase
631+ }
632+
596633// isStandaloneTag checks if the string starts with a standalone tag (not an index modifier)
597634func isStandaloneTag (s string ) bool {
598635 // Check for simple tags
0 commit comments