package wmo import ( "gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/reflectext" "reflect" "strings" ) func (c *Coll[TData]) EnsureInitializedReflection(v TData) { if !c.isInterfaceDataType { return // only dynamically load dataTypeMap on interface TData } rval := reflect.ValueOf(v) for rval.Type().Kind() == reflect.Pointer { rval = rval.Elem() } if _, ok := c.implDataTypeMap[rval.Type()]; ok { return // already loaded } m := make(map[string]fullTypeRef) c.initFields("", rval.Type(), m, make([]int, 0), make([]reflect.Type, 0)) c.implDataTypeMap[rval.Type()] = m } func (c *Coll[TData]) init() { example := *new(TData) datatype := reflect.TypeOf(&example).Elem() if datatype.Kind() == reflect.Interface { c.isInterfaceDataType = true c.dataTypeMap = make(map[string]fullTypeRef) c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) } else { c.isInterfaceDataType = false c.dataTypeMap = make(map[string]fullTypeRef) c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) v := reflect.ValueOf(example) c.initFields("", v.Type(), c.dataTypeMap, make([]int, 0), make([]reflect.Type, 0)) } } func (c *Coll[TData]) initFields(prefix string, rtyp reflect.Type, m map[string]fullTypeRef, idxarr []int, typesInPath []reflect.Type) { for i := 0; i < rtyp.NumField(); i++ { rsfield := rtyp.Field(i) if !rsfield.IsExported() { continue } bsontags := make([]string, 0) bsonkey, found := rsfield.Tag.Lookup("bson") if !found { continue } if strings.Contains(bsonkey, ",") { bsontags = strings.Split(bsonkey[strings.Index(bsonkey, ",")+1:], ",") bsonkey = bsonkey[:strings.Index(bsonkey, ",")] } if bsonkey == "-" { continue } if bsonkey == "" { bsonkey = rsfield.Name } fullKey := prefix + bsonkey newIdxArr := langext.ArrCopy(idxarr) newIdxArr = append(newIdxArr, i) if langext.InArray("inline", bsontags) && rsfield.Type.Kind() == reflect.Struct { // pass-through field c.initFields(prefix, rsfield.Type, m, newIdxArr, typesInPath) } else { if rsfield.Type.Kind() == reflect.Pointer { m[fullKey] = fullTypeRef{ IsPointer: true, RealType: rsfield.Type, Kind: rsfield.Type.Elem().Kind(), Type: rsfield.Type.Elem(), UnderlyingType: reflectext.Underlying(rsfield.Type.Elem()), Name: rsfield.Name, Index: newIdxArr, } } else { m[fullKey] = fullTypeRef{ IsPointer: false, RealType: rsfield.Type, Kind: rsfield.Type.Kind(), Type: rsfield.Type, UnderlyingType: reflectext.Underlying(rsfield.Type), Name: rsfield.Name, Index: newIdxArr, } } if rsfield.Type.Kind() == reflect.Struct { c.initFields(fullKey+".", rsfield.Type, m, newIdxArr, typesInPath) } if rsfield.Type.Kind() == reflect.Pointer && rsfield.Type.Elem().Kind() == reflect.Struct { innerType := rsfield.Type.Elem() // check if there is recursion recursion := false for _, typ := range typesInPath { recursion = recursion || (typ == innerType) } if !recursion { // Store all seen types before that deref a pointer to prevent endless recursion newTypesInPath := make([]reflect.Type, len(typesInPath)) copy(newTypesInPath, typesInPath) newTypesInPath = append(newTypesInPath, rtyp) c.initFields(fullKey+".", innerType, m, newIdxArr, newTypesInPath) } } } } } func (c *Coll[TData]) getTokenValueAsMongoType(value string, fieldName string) (any, error) { fref, err := c.getFieldType(fieldName) if err != nil { return nil, exerr.Wrap(err, "failed to get-field-type").Str("fieldName", fieldName).Build() } pss := reflectext.PrimitiveStringSerializer{} return pss.ValueFromString(value, fref.RealType) } func (c *Coll[TData]) getFieldValueAsTokenString(entity TData, fieldName string) (string, error) { realValue, err := c.getFieldValue(entity, fieldName) if err != nil { return "", exerr.Wrap(err, "failed to get-field-value").Str("fieldName", fieldName).Build() } pss := reflectext.PrimitiveStringSerializer{} return pss.ValueToString(realValue) } func (c *Coll[TData]) getFieldType(fieldName string) (fullTypeRef, error) { if c.isInterfaceDataType { for _, m := range c.implDataTypeMap { if r, ok := m[fieldName]; ok { return r, nil } } return fullTypeRef{}, exerr.New(exerr.TypeMongoReflection, "unknown field: '"+fieldName+"' (in any impl)").Str("fieldName", fieldName).Build() } else { if r, ok := c.dataTypeMap[fieldName]; ok { return r, nil } else { return fullTypeRef{}, exerr.New(exerr.TypeMongoReflection, "unknown field: '"+fieldName+"'").Str("fieldName", fieldName).Build() } } } func (c *Coll[TData]) getFieldValue(data TData, fieldName string) (any, error) { if c.isInterfaceDataType { rval := reflect.ValueOf(data) for rval.Type().Kind() == reflect.Pointer { rval = rval.Elem() } if m, ok := c.implDataTypeMap[rval.Type()]; ok { if fref, ok := m[fieldName]; ok { rval := reflect.ValueOf(data) return rval.FieldByIndex(fref.Index).Interface(), nil } else { return nil, exerr.New(exerr.TypeMongoReflection, "unknown bson field '"+fieldName+"' in type '"+rval.Type().String()+"'").Str("fieldName", fieldName).Type("rval", rval).Build() } } else { return nil, exerr.New(exerr.TypeMongoReflection, "unknown TData type: '"+rval.Type().String()+"'").Type("rval", rval).Build() } } else { if fref, ok := c.dataTypeMap[fieldName]; ok { rval := reflect.ValueOf(data) return rval.FieldByIndex(fref.Index).Interface(), nil } else { return nil, exerr.New(exerr.TypeMongoReflection, "unknown bson field '"+fieldName+"'").Str("fieldName", fieldName).Build() } } }