chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user