tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/assembly.go (about)

     1  package engine
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"reflect"
     7  	"sync"
     8  )
     9  
    10  // Unit is a reference to a struct.
    11  type Unit interface{}
    12  
    13  func unitFrom(v interface{}) Unit {
    14  	rv := reflect.ValueOf(v)
    15  	if rv.Kind() == reflect.Ptr {
    16  		return Unit(v)
    17  	}
    18  	n := reflect.New(rv.Type())
    19  	n.Elem().Set(rv)
    20  	return n.Interface()
    21  }
    22  
    23  // Assembly is a registry of related units.
    24  type Assembly struct {
    25  	units []Unit
    26  	mu    sync.Mutex
    27  }
    28  
    29  // New returns an Assembly with any values added as units.
    30  func New(v ...Unit) (*Assembly, error) {
    31  	a := &Assembly{}
    32  	return a, a.Add(v...)
    33  }
    34  
    35  // Units returns the units in the assembly.
    36  func (a *Assembly) Units() []Unit {
    37  	a.mu.Lock()
    38  	defer a.mu.Unlock()
    39  	e := make([]Unit, len(a.units))
    40  	copy(e, a.units)
    41  	return e
    42  }
    43  
    44  // Main returns the first registered unit in the assembly.
    45  // If there are no units it returns nil.
    46  func (a *Assembly) Main() Unit {
    47  	u := a.Units()
    48  	if len(u) == 0 {
    49  		return nil
    50  	}
    51  	return u[0]
    52  }
    53  
    54  // Add adds values to the assembly as units
    55  func (a *Assembly) Add(v ...Unit) error {
    56  	a.mu.Lock()
    57  	defer a.mu.Unlock()
    58  	for _, vv := range v {
    59  		a.units = append(a.units, unitFrom(vv))
    60  	}
    61  	return nil
    62  }
    63  
    64  // AssignableTo returns units that can be assigned to a value of the provided type.
    65  // If the type is a slice/array, it returns units assignable to the element of the type.
    66  func (a *Assembly) AssignableTo(t reflect.Type) (u []Unit) {
    67  	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
    68  		t = t.Elem()
    69  	}
    70  	for _, uu := range a.Units() {
    71  		ut := reflect.TypeOf(uu)
    72  		if ut.AssignableTo(t) {
    73  			u = append(u, uu)
    74  		}
    75  	}
    76  	return
    77  }
    78  
    79  // Assemble will set any fields on v that match a type or interface in the assembly.
    80  // It only sets fields that are exported and unset. If there is more than one match for a
    81  // field, the first one is used. If the field is a slice, it will be populated with all
    82  // the matches for the slice element type.
    83  func (a *Assembly) Assemble(v interface{}) error {
    84  	rv := reflect.ValueOf(v)
    85  	if rv.Kind() != reflect.Ptr {
    86  		return fmt.Errorf("Assemble: v is not a pointer")
    87  	}
    88  	target := rv.Elem()
    89  	if target.Kind() != reflect.Struct {
    90  		return fmt.Errorf("Assemble: v is not pointing to a struct")
    91  	}
    92  
    93  	// TODO: update docs
    94  	// TODO: Ensure helper to panic if nil?
    95  	// try to use Assemble method
    96  	asm := rv.MethodByName("Assemble")
    97  	if asm.IsValid() && !asm.IsZero() && rv.Type() != reflect.TypeOf(a) {
    98  		args := []reflect.Value{}
    99  		for i := 0; i < asm.Type().NumIn(); i++ {
   100  			argType := asm.Type().In(i)
   101  			if argType.Kind() == reflect.Interface && argType.Name() == "" {
   102  				args = append(args, reflect.Zero(argType))
   103  				continue
   104  			}
   105  			assignable := a.AssignableTo(argType)
   106  			if len(assignable) == 0 {
   107  				args = append(args, reflect.Zero(argType))
   108  				continue
   109  			}
   110  			switch argType.Kind() {
   111  			case reflect.Slice:
   112  				s := reflect.MakeSlice(argType, 0, len(assignable))
   113  				for _, u := range assignable {
   114  					v := reflect.ValueOf(u)
   115  					s.Set(reflect.Append(s, v))
   116  				}
   117  				args = append(args, s)
   118  			case reflect.Ptr, reflect.Interface:
   119  				args = append(args, reflect.ValueOf(assignable[0]))
   120  			default:
   121  				args = append(args, reflect.ValueOf(assignable[0]).Elem())
   122  			}
   123  		}
   124  		defer func() {
   125  			if err := recover(); err != nil {
   126  				log.Println("Assemble:", rv.Type())
   127  				panic(err)
   128  			}
   129  		}()
   130  		asm.Call(args)
   131  		return nil
   132  	}
   133  
   134  	// otherwise populate by exported fields
   135  	for i := 0; i < target.NumField(); i++ {
   136  		// filter out unexported fields
   137  		if len(target.Type().Field(i).PkgPath) > 0 {
   138  			continue
   139  		}
   140  		// TODO: struct tag ignore?
   141  		field := target.Field(i)
   142  		fieldType := target.Type().Field(i).Type
   143  		if !isNilOrZero(field, fieldType) {
   144  			continue
   145  		}
   146  		if fieldType.Kind() == reflect.Interface && fieldType.Name() == "" {
   147  			continue
   148  		}
   149  		assignable := a.AssignableTo(fieldType)
   150  		if len(assignable) == 0 {
   151  			continue
   152  		}
   153  		switch fieldType.Kind() {
   154  		case reflect.Slice:
   155  			field.Set(reflect.MakeSlice(fieldType, 0, len(assignable)))
   156  			for _, u := range assignable {
   157  				v := reflect.ValueOf(u)
   158  				field.Set(reflect.Append(field, v))
   159  			}
   160  		case reflect.Ptr, reflect.Interface:
   161  			field.Set(reflect.ValueOf(assignable[0]))
   162  		default:
   163  			field.Set(reflect.ValueOf(assignable[0]).Elem())
   164  		}
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // SelfAssemble runs AssembleTo on the assembly units.
   171  func (a *Assembly) SelfAssemble() error {
   172  	for _, u := range a.Units() {
   173  		if err := a.Assemble(u); err != nil {
   174  			return err
   175  		}
   176  	}
   177  	return nil
   178  }
   179  
   180  // ValueTo sets a value to the first unit that matches the value type.
   181  func (a *Assembly) ValueTo(v interface{}) error {
   182  	rv := reflect.ValueOf(v)
   183  	if rv.Kind() != reflect.Ptr {
   184  		return fmt.Errorf("ValueTo: v is not a pointer")
   185  	}
   186  	target := rv.Elem()
   187  	ptyp := rv.Type()
   188  
   189  	if target.Kind() == reflect.Ptr {
   190  		target.Set(reflect.New(rv.Type().Elem().Elem()))
   191  		ptyp = rv.Type().Elem()
   192  		target = target.Elem()
   193  	}
   194  
   195  	for _, u := range a.Units() {
   196  		up := reflect.ValueOf(u)
   197  		uv := up.Elem()
   198  		if up.Type().AssignableTo(ptyp) {
   199  			target.Set(uv)
   200  			return nil
   201  		}
   202  	}
   203  
   204  	return fmt.Errorf("ValueTo: no assignable unit for value")
   205  }
   206  
   207  func isNilOrZero(v reflect.Value, t reflect.Type) bool {
   208  	switch v.Kind() {
   209  	default:
   210  		return reflect.DeepEqual(v.Interface(), reflect.Zero(t).Interface())
   211  	case reflect.Interface, reflect.Ptr:
   212  		return v.IsNil()
   213  	}
   214  }