photoprism/internal/entity/entity_values.go
Keith Martin 9a144abfc3 Run go fix ./... and keep mechanical modernization updates.
- Replace `interface{}` with `any` in signatures and local types
- Apply formatter/style cleanups from go1.26 tooling
- Keep `omitempty` behavior-preserving simplifications suggested by fix
- No functional feature changes intended

Validation:
- make test-sqlite test-mariadb test-postgres  (Go 1.26.0)
- GOTOOLCHAIN=go1.24.10 make test-sqlite test-mariadb test-postgres
2026-02-21 21:29:34 +10:00

135 lines
3.2 KiB
Go

package entity
import (
"fmt"
"reflect"
"slices"
)
// Values is a shorthand alias for map[string]interface{}.
type Values = map[string]any
// Map is retained for backward compatibility.
// TODO: Remove when no longer needed.
type Map = Values
// ModelValues extracts exported struct fields into a Values map, optionally omitting selected names.
func ModelValues(m any, omit ...string) (result Values, omitted []any, err error) {
return ModelValuesStructOption(m, true, omit...)
}
// ModelValuesStructOption extracts Values from an entity model, with the option to includeAll fields like before.
// When using this for entity Updates includeAll MUST be false, so that GormV2 is forced to behave like GormV1.
// There are two white lists which need to be maintained if new data types are used, or pointers to existing types are used.
func ModelValuesStructOption(m any, includeAll bool, omit ...string) (result Values, omitted []any, err error) {
mustOmit := func(name string) bool {
return slices.Contains(omit, name)
}
r := reflect.ValueOf(m)
if r.Kind() != reflect.Pointer {
return result, omitted, fmt.Errorf("model interface expected")
}
values := r.Elem()
if kind := values.Kind(); kind != reflect.Struct {
return result, omitted, fmt.Errorf("model expected")
}
t := values.Type()
num := t.NumField()
omitted = make([]any, 0, len(omit))
result = make(Values, num)
// Add exported fields to result.
for i := range num {
field := t.Field(i)
// Skip non-exported fields.
if !field.IsExported() {
continue
}
fieldName := field.Name
// Skip timestamps.
if fieldName == "" || fieldName == "UpdatedAt" || fieldName == "CreatedAt" {
continue
}
v := values.Field(i)
// log.Debugf("field %v is %v with name %v or string %v and is exported %v", fieldName, v.Kind(), v.Type().Name(), v.Type().String(), field.IsExported())
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer:
if v.IsZero() {
continue
}
if !includeAll {
v.SetZero()
}
continue
case reflect.Slice:
if v.IsZero() {
continue
}
whitelist := false
switch v.Type().String() {
case "json.RawMessage":
whitelist = true
}
if !whitelist && !includeAll {
v.SetZero()
continue
}
case reflect.Struct:
if v.IsZero() {
continue
}
whitelist := false
switch v.Type().String() {
case "sql.NullTime", "time.Time", "time.Duration", "json.RawMessage", "otp.Key":
whitelist = true
}
if !whitelist && !includeAll {
v.SetZero()
continue
}
case reflect.Pointer:
whitelist := false
switch v.Type().String() {
case "*time.Time", "*time.Duration", "*bool", "*uint", "*uint64", "*uint32", "*int", "*int64", "*int32", "*string", "*float32", "*float64", "*otp.Key", "*sql.NullTime", "*json.RawMessage":
whitelist = true
}
if !whitelist && !includeAll {
v.SetZero()
continue
}
}
// Skip read-only fields.
if !v.CanSet() {
continue
}
// Skip omitted.
if mustOmit(fieldName) {
if !v.IsZero() {
omitted = append(omitted, v.Interface())
}
continue
}
// Add value to result.
result[fieldName] = v.Interface()
}
if len(result) == 0 {
return result, omitted, fmt.Errorf("no values")
}
return result, omitted, nil
}