mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-12 16:23:00 +03:00
Co-authored-by: Kyle Mendell <ksm@ofkm.us> Co-authored-by: Kyle Mendell <kmendell@ofkm.us> Co-authored-by: Elias Schneider <login@eliasschneider.com>
163 lines
5.1 KiB
Go
163 lines
5.1 KiB
Go
package dto
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"time"
|
|
|
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
|
)
|
|
|
|
// MapStructList maps a list of source structs to a list of destination structs
|
|
func MapStructList[S any, D any](source []S, destination *[]D) error {
|
|
*destination = make([]D, 0, len(source))
|
|
|
|
for _, item := range source {
|
|
var destItem D
|
|
if err := MapStruct(item, &destItem); err != nil {
|
|
return err
|
|
}
|
|
*destination = append(*destination, destItem)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MapStruct maps a source struct to a destination struct
|
|
func MapStruct[S any, D any](source S, destination *D) error {
|
|
// Ensure destination is a non-nil pointer
|
|
destValue := reflect.ValueOf(destination)
|
|
if destValue.Kind() != reflect.Ptr || destValue.IsNil() {
|
|
return errors.New("destination must be a non-nil pointer to a struct")
|
|
}
|
|
|
|
// Ensure source is a struct
|
|
sourceValue := reflect.ValueOf(source)
|
|
if sourceValue.Kind() != reflect.Struct {
|
|
return errors.New("source must be a struct")
|
|
}
|
|
|
|
return mapStructInternal(sourceValue, destValue.Elem())
|
|
}
|
|
|
|
func mapStructInternal(sourceVal reflect.Value, destVal reflect.Value) error {
|
|
for i := 0; i < destVal.NumField(); i++ {
|
|
destField := destVal.Field(i)
|
|
destFieldType := destVal.Type().Field(i)
|
|
|
|
if destFieldType.Anonymous {
|
|
if err := mapStructInternal(sourceVal, destField); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
sourceField := sourceVal.FieldByName(destFieldType.Name)
|
|
|
|
if sourceField.IsValid() && destField.CanSet() {
|
|
if err := mapField(sourceField, destField); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//nolint:gocognit
|
|
func mapField(sourceField reflect.Value, destField reflect.Value) error {
|
|
// Handle pointer to struct in source
|
|
if sourceField.Kind() == reflect.Ptr && !sourceField.IsNil() {
|
|
switch {
|
|
case sourceField.Elem().Kind() == reflect.Struct:
|
|
switch {
|
|
case destField.Kind() == reflect.Struct:
|
|
// Map from pointer to struct -> struct
|
|
return mapStructInternal(sourceField.Elem(), destField)
|
|
case destField.Kind() == reflect.Ptr && destField.CanSet():
|
|
// Map from pointer to struct -> pointer to struct
|
|
if destField.IsNil() {
|
|
destField.Set(reflect.New(destField.Type().Elem()))
|
|
}
|
|
return mapStructInternal(sourceField.Elem(), destField.Elem())
|
|
}
|
|
case destField.Kind() == reflect.Ptr &&
|
|
destField.CanSet() &&
|
|
sourceField.Elem().Type().AssignableTo(destField.Type().Elem()):
|
|
// Handle primitive pointer types (e.g., *string to *string)
|
|
if destField.IsNil() {
|
|
destField.Set(reflect.New(destField.Type().Elem()))
|
|
}
|
|
destField.Elem().Set(sourceField.Elem())
|
|
return nil
|
|
case destField.Kind() != reflect.Ptr &&
|
|
destField.CanSet() &&
|
|
sourceField.Elem().Type().AssignableTo(destField.Type()):
|
|
// Handle *T to T conversion for primitive types
|
|
destField.Set(sourceField.Elem())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Handle pointer to struct in destination
|
|
if destField.Kind() == reflect.Ptr && destField.CanSet() {
|
|
switch {
|
|
case sourceField.Kind() == reflect.Struct:
|
|
// Map from struct -> pointer to struct
|
|
if destField.IsNil() {
|
|
destField.Set(reflect.New(destField.Type().Elem()))
|
|
}
|
|
return mapStructInternal(sourceField, destField.Elem())
|
|
case !sourceField.IsZero() && sourceField.Type().AssignableTo(destField.Type().Elem()):
|
|
// Handle T to *T conversion for primitive types
|
|
if destField.IsNil() {
|
|
destField.Set(reflect.New(destField.Type().Elem()))
|
|
}
|
|
destField.Elem().Set(sourceField)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case sourceField.Type() == destField.Type():
|
|
destField.Set(sourceField)
|
|
case sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice:
|
|
return mapSlice(sourceField, destField)
|
|
case sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct:
|
|
return mapStructInternal(sourceField, destField)
|
|
default:
|
|
return mapSpecialTypes(sourceField, destField)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mapSlice(sourceField reflect.Value, destField reflect.Value) error {
|
|
if sourceField.Type().Elem() == destField.Type().Elem() {
|
|
newSlice := reflect.MakeSlice(destField.Type(), sourceField.Len(), sourceField.Cap())
|
|
for j := 0; j < sourceField.Len(); j++ {
|
|
newSlice.Index(j).Set(sourceField.Index(j))
|
|
}
|
|
destField.Set(newSlice)
|
|
} else if sourceField.Type().Elem().Kind() == reflect.Struct && destField.Type().Elem().Kind() == reflect.Struct {
|
|
newSlice := reflect.MakeSlice(destField.Type(), sourceField.Len(), sourceField.Cap())
|
|
for j := 0; j < sourceField.Len(); j++ {
|
|
sourceElem := sourceField.Index(j)
|
|
destElem := reflect.New(destField.Type().Elem()).Elem()
|
|
if err := mapStructInternal(sourceElem, destElem); err != nil {
|
|
return err
|
|
}
|
|
newSlice.Index(j).Set(destElem)
|
|
}
|
|
destField.Set(newSlice)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mapSpecialTypes(sourceField reflect.Value, destField reflect.Value) error {
|
|
if _, ok := sourceField.Interface().(datatype.DateTime); ok {
|
|
if sourceField.Type() == reflect.TypeOf(datatype.DateTime{}) && destField.Type() == reflect.TypeOf(time.Time{}) {
|
|
dateValue := sourceField.Interface().(datatype.DateTime)
|
|
destField.Set(reflect.ValueOf(dateValue.ToTime()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|