Files
netbird/management/internals/network_map_db/struct_helpers.go
Dmitri Dolguikh 3da27221ac support for GetNetwork in sqlite
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-11 10:43:00 +02:00

158 lines
3.6 KiB
Go

package networkmapdb
import (
"database/sql"
"encoding/json"
"errors"
"reflect"
"strings"
"github.com/rs/xid"
)
var ErrNoRows = errors.New("no rows in result set")
const (
NMAP_STRUCT_TAG = "nmap"
NMAP_SKIP = "skip"
NMAP_MAP_TO = "map_to"
NMAP_JSON = "json"
)
type fieldTag struct {
Key string
Value string
}
func tagFromString(t string) fieldTag {
kv := strings.Split(t, ":")
if len(kv) == 1 {
return fieldTag{Key: strings.TrimSpace(kv[0])}
}
return fieldTag{Key: strings.TrimSpace(kv[0]), Value: strings.TrimSpace(kv[1])}
}
func FromSqlTypesToSharedTypes(src reflect.Value, dst reflect.Value) error {
typ := src.Elem().Type()
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
fieldTags := make(map[string]string)
if v := f.Tag.Get(NMAP_STRUCT_TAG); v != "" {
for _, t := range strings.Split(v, ",") {
kv := tagFromString(t)
fieldTags[kv.Key] = kv.Value
}
}
if _, ok := fieldTags[NMAP_SKIP]; ok {
continue
}
if f.PkgPath != "" { // skip unexported fields
continue
}
dstFieldName := f.Name
if override, ok := fieldTags[NMAP_MAP_TO]; ok {
dstFieldName = override
}
dstField := dst.Elem().FieldByName(dstFieldName)
if !dstField.IsValid() {
return errors.New("unsupported type in destination field: " + dstFieldName)
}
srcField := src.Elem().Field(i)
srcFieldType := srcField.Type().String()
switch srcFieldType {
case "string":
s := srcField.Interface().(string)
dstField.SetString(s)
case "sql.NullString":
s := srcField.Interface().(sql.NullString)
if s.Valid {
dstField.SetString(s.String)
}
if (dstFieldName == "PublicId" || dstFieldName == "PublicID") && s.String == "" {
dstField.SetString(xid.New().String()) // TODO (dmitri) this needs to be removed to support delta updates
}
case "sql.NullTime":
s := srcField.Interface().(sql.NullTime)
if s.Valid {
if dstField.Kind() == reflect.Ptr {
t := reflect.ValueOf(&s.Time).Elem()
dstField.Set(t.Addr())
} else {
dstField.Set(reflect.ValueOf(s.Time))
}
}
case "sql.NullBool":
s := srcField.Interface().(sql.NullBool)
if s.Valid {
dstField.SetBool(s.Bool)
}
case "sql.NullInt64":
s := srcField.Interface().(sql.NullInt64)
if s.Valid {
dstField.SetInt(s.Int64)
}
case "json.RawMessage":
s := srcField.Interface().(json.RawMessage)
if len(s) == 0 {
continue
}
if err := json.Unmarshal(s, dstField.Addr().Interface()); err != nil {
return err
}
case "[]byte", "[]uint8":
s := srcField.Interface().([]byte)
if _, ok := fieldTags[NMAP_JSON]; !ok || len(s) == 0 {
continue
}
if err := json.Unmarshal(s, dstField.Addr().Interface()); err != nil {
return err
}
case "[]string":
if srcField.IsNil() {
continue
}
dstv := reflect.MakeSlice(dstField.Type(), srcField.Len(), srcField.Cap())
reflect.Copy(dstv, srcField)
dstField.Set(dstv)
}
}
return nil
}
func StructFields(s any) []any {
src := reflect.ValueOf(s)
toret := make([]any, 0)
typ := src.Elem().Type()
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
if f.PkgPath != "" { // skip unexported fields
continue
}
srcField := src.Elem().Field(i)
toret = append(toret, srcField.Addr().Interface())
}
return toret
}
func ConvertAllToSharedTypes[T any, T1 any](allsrc []T) ([]T1, error) {
toret := make([]T1, 0, len(allsrc))
for _, src := range allsrc {
var dst T1
err := FromSqlTypesToSharedTypes(
reflect.ValueOf(&src), reflect.ValueOf(&dst))
if err != nil {
return nil, err
}
toret = append(toret, dst)
}
return toret, nil
}