538 lines
15 KiB
Go
538 lines
15 KiB
Go
package copyx
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
ErrType = errors.New("copyx: incompatible types")
|
|
ErrNil = errors.New("copyx: nil cannot fill a non-nullable target")
|
|
ErrField = errors.New("copyx: unmatched field")
|
|
ErrUnsupported = errors.New("copyx: unsupported value; provide an explicit data converter")
|
|
ErrSharedReference = errors.New("copyx: deep copy retains a mutable source reference")
|
|
ErrLimit = errors.New("copyx: traversal limit exceeded")
|
|
)
|
|
|
|
// Error describes a field/index path without recording the source value.
|
|
type Error struct {
|
|
Path string
|
|
Cause error
|
|
}
|
|
|
|
func (e *Error) Error() string { return e.Path + ": " + e.Cause.Error() }
|
|
func (e *Error) Unwrap() error { return e.Cause }
|
|
func at(path string, err error) error { return &Error{Path: path, Cause: err} }
|
|
|
|
func Copy(target, source any, options ...Option) error {
|
|
c, err := New(options...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Copy(target, source)
|
|
}
|
|
|
|
// Clone always makes a deep copy. Nil and empty collections stay distinct.
|
|
func Clone[T any](source T, options ...Option) (T, error) {
|
|
var target T
|
|
options = append(append([]Option(nil), options...), WithDeepCopy())
|
|
err := Copy(&target, source, options...)
|
|
return target, err
|
|
}
|
|
|
|
// Copy stages the complete result and only assigns target on success. User
|
|
// converter side effects (including mutation of source) cannot be rolled back.
|
|
// Caller must synchronize concurrent access to source and target themselves.
|
|
func (c *Copier) Copy(target, source any) error {
|
|
// Alias indexes contain addresses only. Keep the original graph alive until
|
|
// validation and commit finish so the GC cannot recycle any indexed address.
|
|
defer func() { runtime.KeepAlive(source) }()
|
|
if c == nil {
|
|
return errors.New("copyx: nil Copier")
|
|
}
|
|
dst := reflect.ValueOf(target)
|
|
if !dst.IsValid() || dst.Kind() != reflect.Pointer || dst.IsNil() {
|
|
return errors.New("copyx: target must be a non-nil pointer")
|
|
}
|
|
src := reflect.ValueOf(source)
|
|
s := state{options: &c.options, seen: map[visit]reflect.Value{}}
|
|
if c.options.deep {
|
|
var err error
|
|
s.graph, err = sourceGraph(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if src.IsValid() && src.Kind() == reflect.Pointer && !src.IsNil() && dst.Elem().Kind() == reflect.Struct {
|
|
id := identity(src)
|
|
s.valueRoot = &id
|
|
}
|
|
}
|
|
out, err := s.walk(src, dst.Elem(), dst.Elem().Type(), "$", 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if s.graph != nil {
|
|
if err = s.graph.audit(out); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
dst.Elem().Set(out)
|
|
return nil
|
|
}
|
|
|
|
type visit struct {
|
|
reference
|
|
target reflect.Type
|
|
}
|
|
type state struct {
|
|
options *settings
|
|
seen map[visit]reflect.Value
|
|
graph *graph
|
|
nodes int
|
|
allocated uint64
|
|
valueRoot *reference
|
|
}
|
|
|
|
func (s *state) reserve(count int, size uintptr, path string) error {
|
|
if count < 0 || (count > 0 && uint64(size) > maxCopyBytes/uint64(count)) {
|
|
return at(path, ErrLimit)
|
|
}
|
|
s.allocated += uint64(count) * uint64(size)
|
|
if s.allocated > maxCopyBytes {
|
|
return at(path, ErrLimit)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func nullable(k reflect.Kind) bool {
|
|
switch k {
|
|
case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface, reflect.Func, reflect.Chan:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
func baseValue(base reflect.Value, t reflect.Type) reflect.Value {
|
|
if base.IsValid() && base.Type() == t {
|
|
return base
|
|
}
|
|
return reflect.Zero(t)
|
|
}
|
|
func (s *state) walk(src, base reflect.Value, t reflect.Type, path string, depth int) (reflect.Value, error) {
|
|
s.nodes++
|
|
if depth > maxDepth || s.nodes > maxNodes {
|
|
return reflect.Value{}, at(path, ErrLimit)
|
|
}
|
|
base = baseValue(base, t)
|
|
if !shallowSafe(t) {
|
|
return reflect.Value{}, at(path, ErrUnsupported)
|
|
}
|
|
if src.IsValid() && !src.CanInterface() {
|
|
return reflect.Value{}, at(path, ErrUnsupported)
|
|
}
|
|
// Exact converters also receive typed nils and zeros. No method calls,
|
|
// Scanner/Valuer invocation or implicit numeric conversion is attempted.
|
|
if src.IsValid() {
|
|
if convert, ok := s.options.converters[pair{src.Type(), t}]; ok {
|
|
out, err := convert(src)
|
|
if err != nil {
|
|
return reflect.Value{}, at(path, err)
|
|
}
|
|
if s.graph != nil {
|
|
if err = s.graph.audit(out); err != nil {
|
|
return reflect.Value{}, at(path, err)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
}
|
|
if !src.IsValid() || (nullable(src.Kind()) && src.IsNil()) {
|
|
if src.IsValid() && !s.nilCompatible(src.Type(), t, map[pair]bool{}) {
|
|
return reflect.Value{}, at(path, ErrType)
|
|
}
|
|
if s.options.nilPolicy == NilSkip {
|
|
return base, nil
|
|
}
|
|
if nullable(t.Kind()) || s.options.nilPolicy == NilZero {
|
|
return reflect.Zero(t), nil
|
|
}
|
|
return reflect.Value{}, at(path, ErrNil)
|
|
}
|
|
if s.options.skipZero && src.IsZero() {
|
|
if !s.nilCompatible(src.Type(), t, map[pair]bool{}) {
|
|
return reflect.Value{}, at(path, ErrType)
|
|
}
|
|
return base, nil
|
|
}
|
|
if src.Type() == t && len(s.options.converters) == 0 && len(s.options.fields) == 0 && !s.options.skipZero && s.options.nilPolicy != NilSkip {
|
|
if !s.options.deep || simpleValue(t) {
|
|
return src, nil
|
|
}
|
|
}
|
|
if src.Kind() == reflect.Interface {
|
|
return s.walk(src.Elem(), base, t, path, depth+1)
|
|
}
|
|
if t.Kind() == reflect.Interface {
|
|
if !src.Type().Implements(t) {
|
|
return reflect.Value{}, at(path, ErrType)
|
|
}
|
|
out, err := s.walk(src, reflect.Value{}, src.Type(), path, depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
v := reflect.New(t).Elem()
|
|
v.Set(out)
|
|
return v, nil
|
|
}
|
|
if s.options.deep && forbidden(t) {
|
|
return reflect.Value{}, at(path, ErrUnsupported)
|
|
}
|
|
if src.Kind() == reflect.Pointer && t.Kind() != reflect.Pointer {
|
|
return s.walk(src.Elem(), base, t, path, depth+1)
|
|
}
|
|
if t.Kind() == reflect.Pointer {
|
|
if !s.options.deep && src.Type() == t {
|
|
return src, nil
|
|
}
|
|
if src.Kind() == reflect.Pointer {
|
|
key := visit{identity(src), t}
|
|
if s.valueRoot != nil && key.reference == *s.valueRoot {
|
|
return reflect.Value{}, at(path, fmt.Errorf("%w: cyclic root must retain its pointer; use Clone(source) or Copy(&targetPointer, source)", ErrUnsupported))
|
|
}
|
|
if prior, ok := s.seen[key]; ok {
|
|
return prior, nil
|
|
}
|
|
}
|
|
if err := s.reserve(1, t.Elem().Size(), path); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out := reflect.New(t.Elem())
|
|
if out.Type() != t {
|
|
out = out.Convert(t)
|
|
}
|
|
if src.Kind() == reflect.Pointer {
|
|
key := visit{identity(src), t}
|
|
s.seen[key] = out
|
|
src = src.Elem()
|
|
}
|
|
var childBase reflect.Value
|
|
if !base.IsNil() {
|
|
childBase = base.Elem()
|
|
}
|
|
value, err := s.walk(src, childBase, t.Elem(), path, depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Elem().Set(value)
|
|
return out, nil
|
|
}
|
|
if src.Kind() != t.Kind() {
|
|
return reflect.Value{}, at(path, fmt.Errorf("%w: %s -> %s", ErrType, src.Type(), t))
|
|
}
|
|
if t == timeType && src.Type() == t {
|
|
return src, nil
|
|
}
|
|
switch t.Kind() {
|
|
case reflect.Struct:
|
|
return s.structure(src, base, t, path, depth)
|
|
case reflect.Slice:
|
|
if !s.options.deep && src.Type() == t {
|
|
return src, nil
|
|
}
|
|
key := visit{identity(src), t}
|
|
if prior, ok := s.seen[key]; ok {
|
|
return prior, nil
|
|
}
|
|
if err := s.reserve(src.Len(), t.Elem().Size(), path); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out := reflect.MakeSlice(t, src.Len(), src.Len())
|
|
s.seen[key] = out
|
|
if src.Type() == t && len(s.options.converters) == 0 && len(s.options.fields) == 0 && simpleValue(t.Elem()) {
|
|
reflect.Copy(out, src)
|
|
return out, nil
|
|
}
|
|
for i := 0; i < src.Len(); i++ {
|
|
v, err := s.walk(src.Index(i), reflect.Value{}, t.Elem(), fmt.Sprintf("%s[%d]", path, i), depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Index(i).Set(v)
|
|
}
|
|
return out, nil
|
|
case reflect.Array:
|
|
if src.Len() != t.Len() {
|
|
return reflect.Value{}, at(path, ErrType)
|
|
}
|
|
if err := s.reserve(1, t.Size(), path); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out := reflect.New(t).Elem()
|
|
for i := 0; i < src.Len(); i++ {
|
|
v, err := s.walk(src.Index(i), base.Index(i), t.Elem(), fmt.Sprintf("%s[%d]", path, i), depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Index(i).Set(v)
|
|
}
|
|
return out, nil
|
|
case reflect.Map:
|
|
if !s.options.deep && src.Type() == t {
|
|
return src, nil
|
|
}
|
|
key := visit{identity(src), t}
|
|
if prior, ok := s.seen[key]; ok {
|
|
return prior, nil
|
|
}
|
|
if err := s.reserve(src.Len(), t.Key().Size()+t.Elem().Size()+32, path); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out := reflect.MakeMapWithSize(t, src.Len())
|
|
s.seen[key] = out
|
|
it := src.MapRange()
|
|
for it.Next() {
|
|
k, err := s.walk(it.Key(), reflect.Value{}, t.Key(), path+"[key]", depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if !k.Comparable() {
|
|
return reflect.Value{}, at(path+"[key]", ErrType)
|
|
}
|
|
if out.MapIndex(k).IsValid() {
|
|
return reflect.Value{}, at(path+"[key]", errors.New("copyx: key conversion collision"))
|
|
}
|
|
v, err := s.walk(it.Value(), reflect.Value{}, t.Elem(), path+"[value]", depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.SetMapIndex(k, v)
|
|
}
|
|
return out, nil
|
|
case reflect.Func, reflect.Chan, reflect.UnsafePointer:
|
|
if s.options.deep {
|
|
return reflect.Value{}, at(path, ErrUnsupported)
|
|
}
|
|
}
|
|
if src.Type() != t {
|
|
return reflect.Value{}, at(path, fmt.Errorf("%w: %s -> %s", ErrType, src.Type(), t))
|
|
}
|
|
return src, nil
|
|
}
|
|
|
|
var fieldTypes sync.Map
|
|
|
|
func exportedFields(t reflect.Type) map[string]reflect.StructField {
|
|
if known, ok := fieldTypes.Load(t); ok {
|
|
return known.(map[string]reflect.StructField)
|
|
}
|
|
out := map[string]reflect.StructField{}
|
|
for _, f := range reflect.VisibleFields(t) {
|
|
if f.PkgPath != "" {
|
|
continue
|
|
}
|
|
kind := f.Type.Kind()
|
|
if kind == reflect.Pointer {
|
|
kind = f.Type.Elem().Kind()
|
|
}
|
|
if f.Anonymous && kind == reflect.Struct {
|
|
continue
|
|
}
|
|
out[f.Name] = f
|
|
}
|
|
actual, _ := fieldTypes.LoadOrStore(t, out)
|
|
return actual.(map[string]reflect.StructField)
|
|
}
|
|
|
|
func (s *state) nilCompatible(from, to reflect.Type, seen map[pair]bool) bool {
|
|
key := pair{from, to}
|
|
if seen[key] {
|
|
return true
|
|
}
|
|
seen[key] = true
|
|
if from == to {
|
|
return true
|
|
}
|
|
if _, ok := s.options.converters[key]; ok {
|
|
return true
|
|
}
|
|
if to.Kind() == reflect.Interface {
|
|
return from.Implements(to)
|
|
}
|
|
if from.Kind() == reflect.Pointer {
|
|
return s.nilCompatible(from.Elem(), to, seen)
|
|
}
|
|
if to.Kind() == reflect.Pointer {
|
|
return s.nilCompatible(from, to.Elem(), seen)
|
|
}
|
|
if from.Kind() != to.Kind() {
|
|
return false
|
|
}
|
|
switch from.Kind() {
|
|
case reflect.Struct:
|
|
// Nil must not hide a structurally incompatible type pair. There are no
|
|
// field values to visit, but coverage and conversion types still apply.
|
|
for i := 0; i < to.NumField(); i++ {
|
|
if to.Field(i).PkgPath != "" {
|
|
return false
|
|
}
|
|
}
|
|
sources, targets := exportedFields(from), exportedFields(to)
|
|
rules := s.options.fields[key]
|
|
if rules == nil {
|
|
rules = &fields{}
|
|
}
|
|
reverse, used := map[string]string{}, map[string]bool{}
|
|
for a, b := range rules.names {
|
|
reverse[b] = a
|
|
}
|
|
for name, target := range targets {
|
|
if rules.ignoreTarget[name] {
|
|
continue
|
|
}
|
|
srcName := name
|
|
if renamed, ok := reverse[name]; ok {
|
|
srcName = renamed
|
|
}
|
|
source, ok := sources[srcName]
|
|
if !ok || rules.ignoreSource[srcName] || !s.nilCompatible(source.Type, target.Type, seen) {
|
|
return false
|
|
}
|
|
used[srcName] = true
|
|
}
|
|
if s.options.strict {
|
|
for name := range sources {
|
|
if !used[name] && !rules.ignoreSource[name] {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Slice:
|
|
return s.nilCompatible(from.Elem(), to.Elem(), seen)
|
|
case reflect.Map:
|
|
return s.nilCompatible(from.Key(), to.Key(), seen) && s.nilCompatible(from.Elem(), to.Elem(), seen)
|
|
case reflect.Array:
|
|
return from.Len() == to.Len() && s.nilCompatible(from.Elem(), to.Elem(), seen)
|
|
}
|
|
return false
|
|
}
|
|
func fieldValue(v reflect.Value, index []int) (reflect.Value, bool) {
|
|
for _, i := range index {
|
|
if v.Kind() == reflect.Pointer {
|
|
if v.IsNil() {
|
|
return reflect.Value{}, false
|
|
}
|
|
v = v.Elem()
|
|
}
|
|
v = v.Field(i)
|
|
}
|
|
return v, true
|
|
}
|
|
func setField(v reflect.Value, index []int, value reflect.Value) error {
|
|
for j, i := range index {
|
|
field := v.Field(i)
|
|
if !field.CanSet() {
|
|
return ErrUnsupported
|
|
}
|
|
if j == len(index)-1 {
|
|
field.Set(value)
|
|
return nil
|
|
}
|
|
if field.Kind() == reflect.Pointer {
|
|
// Detach embedded destination pointers before editing promoted fields.
|
|
fresh := reflect.New(field.Type().Elem())
|
|
if !field.IsNil() {
|
|
fresh.Elem().Set(field.Elem())
|
|
}
|
|
field.Set(fresh)
|
|
v = fresh.Elem()
|
|
} else {
|
|
v = field
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (s *state) structure(src, base reflect.Value, t reflect.Type, path string, depth int) (reflect.Value, error) {
|
|
if err := s.reserve(1, t.Size(), path); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out := reflect.New(t).Elem()
|
|
out.Set(base)
|
|
if src.Type() == t {
|
|
// Private scalar state may be assigned as part of its whole value; mutable
|
|
// private state requires a converter, never unsafe writes or silent loss.
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if f.PkgPath != "" && s.options.deep && privateMutable(f.Type) {
|
|
return reflect.Value{}, at(path+"."+f.Name, ErrUnsupported)
|
|
}
|
|
}
|
|
out.Set(src)
|
|
} else {
|
|
for i := 0; i < t.NumField(); i++ {
|
|
if t.Field(i).PkgPath != "" {
|
|
return reflect.Value{}, at(path, ErrUnsupported)
|
|
}
|
|
}
|
|
}
|
|
from, to := exportedFields(src.Type()), exportedFields(t)
|
|
rules := s.options.fields[pair{src.Type(), t}]
|
|
if rules == nil {
|
|
rules = &fields{map[string]string{}, map[string]bool{}, map[string]bool{}}
|
|
}
|
|
reverse := map[string]string{}
|
|
for a, b := range rules.names {
|
|
reverse[b] = a
|
|
}
|
|
names := make([]string, 0, len(to))
|
|
for n := range to {
|
|
names = append(names, n)
|
|
}
|
|
sort.Strings(names)
|
|
used := map[string]bool{}
|
|
for _, name := range names {
|
|
target := to[name]
|
|
old, _ := fieldValue(base, target.Index)
|
|
if rules.ignoreTarget[name] {
|
|
if !old.IsValid() {
|
|
old = reflect.Zero(target.Type)
|
|
}
|
|
if err := setField(out, target.Index, old); err != nil {
|
|
return reflect.Value{}, at(path+"."+name, err)
|
|
}
|
|
continue
|
|
}
|
|
srcName := name
|
|
if renamed, ok := reverse[name]; ok {
|
|
srcName = renamed
|
|
}
|
|
source, ok := from[srcName]
|
|
if !ok || rules.ignoreSource[srcName] {
|
|
return reflect.Value{}, at(path+"."+name, ErrField)
|
|
}
|
|
used[srcName] = true
|
|
v, ok := fieldValue(src, source.Index)
|
|
if !ok {
|
|
return reflect.Value{}, at(path+"."+name, ErrNil)
|
|
}
|
|
value, err := s.walk(v, old, target.Type, path+"."+name, depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if err = setField(out, target.Index, value); err != nil {
|
|
return reflect.Value{}, at(path+"."+name, err)
|
|
}
|
|
}
|
|
if s.options.strict {
|
|
for name := range from {
|
|
if !used[name] && !rules.ignoreSource[name] {
|
|
return reflect.Value{}, at(path+"."+name, ErrField)
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|