package nigiri import ( "reflect" "strings" ) // Constraint types type ConstraintType string const ( ConstraintUnique ConstraintType = "unique" ConstraintForeign ConstraintType = "fkey" ConstraintRequired ConstraintType = "required" ConstraintIndex ConstraintType = "index" ) type FieldConstraint struct { Type ConstraintType Field string Target string // for foreign keys: "table.field" IndexName string // for custom index names } type SchemaInfo struct { Fields map[string]reflect.Type Constraints map[string][]FieldConstraint Indices map[string]string // field -> index name } // ParseSchema extracts constraints from struct tags func ParseSchema[T any]() *SchemaInfo { var zero T t := reflect.TypeOf(zero) if t.Kind() == reflect.Ptr { t = t.Elem() } schema := &SchemaInfo{ Fields: make(map[string]reflect.Type), Constraints: make(map[string][]FieldConstraint), Indices: make(map[string]string), } for i := 0; i < t.NumField(); i++ { field := t.Field(i) fieldName := field.Name schema.Fields[fieldName] = field.Type dbTag := field.Tag.Get("db") if dbTag == "" { continue } constraints := parseDBTag(fieldName, dbTag) if len(constraints) > 0 { schema.Constraints[fieldName] = constraints } // Auto-create indices for unique and indexed fields for _, constraint := range constraints { if constraint.Type == ConstraintUnique || constraint.Type == ConstraintIndex { indexName := constraint.IndexName if indexName == "" { indexName = fieldName + "_idx" } schema.Indices[fieldName] = indexName } } } return schema } func parseDBTag(fieldName, tag string) []FieldConstraint { var constraints []FieldConstraint parts := strings.Split(tag, ",") for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } switch { case part == "unique": constraints = append(constraints, FieldConstraint{ Type: ConstraintUnique, Field: fieldName, }) case part == "required": constraints = append(constraints, FieldConstraint{ Type: ConstraintRequired, Field: fieldName, }) case part == "index": constraints = append(constraints, FieldConstraint{ Type: ConstraintIndex, Field: fieldName, }) case strings.HasPrefix(part, "index:"): indexName := strings.TrimPrefix(part, "index:") constraints = append(constraints, FieldConstraint{ Type: ConstraintIndex, Field: fieldName, IndexName: indexName, }) case strings.HasPrefix(part, "fkey:"): target := strings.TrimPrefix(part, "fkey:") constraints = append(constraints, FieldConstraint{ Type: ConstraintForeign, Field: fieldName, Target: target, }) } } return constraints }