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 }