e071084ebe
govulncheck / govulncheck (push) Waiting to run
Harness (E2E) / Harnesses (mock LLM) (push) Waiting to run
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Run Tests / Unit Tests (push) Waiting to run
Run Tests / Etcd Integration Tests (push) Waiting to run
163 lines
3.7 KiB
Go
163 lines
3.7 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// Schema describes a model's storage layout, derived from struct tags.
|
|
type Schema struct {
|
|
// Table name in the database.
|
|
Table string
|
|
// Key is the name of the primary key field.
|
|
Key string
|
|
// Fields maps Go field names to their column metadata.
|
|
Fields []Field
|
|
}
|
|
|
|
// Field describes a single field in the schema.
|
|
type Field struct {
|
|
// Name is the Go struct field name.
|
|
Name string
|
|
// Column is the database column name (from json tag or lowercased name).
|
|
Column string
|
|
// Type is the Go reflect type.
|
|
Type reflect.Type
|
|
// IsKey indicates this is the primary key field.
|
|
IsKey bool
|
|
// Index indicates this field should be indexed.
|
|
Index bool
|
|
}
|
|
|
|
// BuildSchema extracts a Schema from a struct type using reflection.
|
|
func BuildSchema(v interface{}, opts ...RegisterOption) *Schema {
|
|
t := reflect.TypeOf(v)
|
|
if t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
|
|
schema := &Schema{
|
|
Table: strings.ToLower(t.Name()) + "s",
|
|
}
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if !f.IsExported() {
|
|
continue
|
|
}
|
|
|
|
field := Field{
|
|
Name: f.Name,
|
|
Type: f.Type,
|
|
}
|
|
|
|
// Column name: use json tag if present, else lowercase field name
|
|
if tag := f.Tag.Get("json"); tag != "" {
|
|
parts := strings.Split(tag, ",")
|
|
if parts[0] != "" && parts[0] != "-" {
|
|
field.Column = parts[0]
|
|
}
|
|
}
|
|
if field.Column == "" {
|
|
field.Column = strings.ToLower(f.Name)
|
|
}
|
|
|
|
// Check model tag
|
|
if tag := f.Tag.Get("model"); tag != "" {
|
|
for _, opt := range strings.Split(tag, ",") {
|
|
switch opt {
|
|
case "key":
|
|
field.IsKey = true
|
|
schema.Key = field.Column
|
|
case "index":
|
|
field.Index = true
|
|
}
|
|
}
|
|
}
|
|
|
|
schema.Fields = append(schema.Fields, field)
|
|
}
|
|
|
|
if schema.Key == "" {
|
|
// Default to "id" if no key tag found
|
|
for i := range schema.Fields {
|
|
if schema.Fields[i].Column == "id" {
|
|
schema.Fields[i].IsKey = true
|
|
schema.Key = "id"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, o := range opts {
|
|
o(schema)
|
|
}
|
|
|
|
return schema
|
|
}
|
|
|
|
// StructToMap converts a struct pointer to a map of column name → value.
|
|
func StructToMap(schema *Schema, v interface{}) map[string]any {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() == reflect.Pointer {
|
|
rv = rv.Elem()
|
|
}
|
|
fields := make(map[string]any, len(schema.Fields))
|
|
for _, f := range schema.Fields {
|
|
fv := rv.FieldByName(f.Name)
|
|
if fv.IsValid() {
|
|
fields[f.Column] = fv.Interface()
|
|
}
|
|
}
|
|
return fields
|
|
}
|
|
|
|
// MapToStruct fills a struct pointer from a map of column name → value.
|
|
func MapToStruct(schema *Schema, fields map[string]any, v interface{}) {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() == reflect.Pointer {
|
|
rv = rv.Elem()
|
|
}
|
|
for _, f := range schema.Fields {
|
|
val, ok := fields[f.Column]
|
|
if !ok {
|
|
continue
|
|
}
|
|
fv := rv.FieldByName(f.Name)
|
|
if !fv.IsValid() || !fv.CanSet() {
|
|
continue
|
|
}
|
|
rval := reflect.ValueOf(val)
|
|
if rval.Type().AssignableTo(fv.Type()) {
|
|
fv.Set(rval)
|
|
} else if rval.Type().ConvertibleTo(fv.Type()) {
|
|
fv.Set(rval.Convert(fv.Type()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewFromSchema creates a new zero-value struct pointer for the given schema's original type.
|
|
func NewFromSchema(schema *Schema, rtype reflect.Type) interface{} {
|
|
return reflect.New(rtype).Interface()
|
|
}
|
|
|
|
// KeyValue extracts the key value from a struct using the schema.
|
|
func KeyValue(schema *Schema, v interface{}) string {
|
|
fields := StructToMap(schema, v)
|
|
key, ok := fields[schema.Key]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return fmt.Sprint(key)
|
|
}
|
|
|
|
// ResolveType returns the struct reflect.Type from a value (handles pointers and slices).
|
|
func ResolveType(v interface{}) reflect.Type {
|
|
t := reflect.TypeOf(v)
|
|
for t.Kind() == reflect.Pointer || t.Kind() == reflect.Slice {
|
|
t = t.Elem()
|
|
}
|
|
return t
|
|
}
|