1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package proptools 16 17import ( 18 "fmt" 19 "reflect" 20) 21 22// AppendProperties appends the values of properties in the property struct src to the property 23// struct dst. dst and src must be the same type, and both must be pointers to structs. 24// 25// The filter function can prevent individual properties from being appended by returning false, or 26// abort AppendProperties with an error by returning an error. Passing nil for filter will append 27// all properties. 28// 29// An error returned by AppendProperties that applies to a specific property will be an 30// *ExtendPropertyError, and can have the property name and error extracted from it. 31// 32// The append operation is defined as appending strings and slices of strings normally, OR-ing bool 33// values, replacing non-nil pointers to booleans or strings, and recursing into 34// embedded structs, pointers to structs, and interfaces containing 35// pointers to structs. Appending the zero value of a property will always be a no-op. 36func AppendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error { 37 return extendProperties(dst, src, filter, orderAppend) 38} 39 40// PrependProperties prepends the values of properties in the property struct src to the property 41// struct dst. dst and src must be the same type, and both must be pointers to structs. 42// 43// The filter function can prevent individual properties from being prepended by returning false, or 44// abort PrependProperties with an error by returning an error. Passing nil for filter will prepend 45// all properties. 46// 47// An error returned by PrependProperties that applies to a specific property will be an 48// *ExtendPropertyError, and can have the property name and error extracted from it. 49// 50// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing 51// bool values, replacing non-nil pointers to booleans or strings, and recursing into 52// embedded structs, pointers to structs, and interfaces containing 53// pointers to structs. Prepending the zero value of a property will always be a no-op. 54func PrependProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error { 55 return extendProperties(dst, src, filter, orderPrepend) 56} 57 58// AppendMatchingProperties appends the values of properties in the property struct src to the 59// property structs in dst. dst and src do not have to be the same type, but every property in src 60// must be found in at least one property in dst. dst must be a slice of pointers to structs, and 61// src must be a pointer to a struct. 62// 63// The filter function can prevent individual properties from being appended by returning false, or 64// abort AppendProperties with an error by returning an error. Passing nil for filter will append 65// all properties. 66// 67// An error returned by AppendMatchingProperties that applies to a specific property will be an 68// *ExtendPropertyError, and can have the property name and error extracted from it. 69// 70// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool 71// values, replacing non-nil pointers to booleans or strings, and recursing into 72// embedded structs, pointers to structs, and interfaces containing 73// pointers to structs. Appending the zero value of a property will always be a no-op. 74func AppendMatchingProperties(dst []interface{}, src interface{}, 75 filter ExtendPropertyFilterFunc) error { 76 return extendMatchingProperties(dst, src, filter, orderAppend) 77} 78 79// PrependMatchingProperties prepends the values of properties in the property struct src to the 80// property structs in dst. dst and src do not have to be the same type, but every property in src 81// must be found in at least one property in dst. dst must be a slice of pointers to structs, and 82// src must be a pointer to a struct. 83// 84// The filter function can prevent individual properties from being prepended by returning false, or 85// abort PrependProperties with an error by returning an error. Passing nil for filter will prepend 86// all properties. 87// 88// An error returned by PrependProperties that applies to a specific property will be an 89// *ExtendPropertyError, and can have the property name and error extracted from it. 90// 91// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing 92// bool values, replacing non-nil pointers to booleans or strings, and recursing into 93// embedded structs, pointers to structs, and interfaces containing 94// pointers to structs. Prepending the zero value of a property will always be a no-op. 95func PrependMatchingProperties(dst []interface{}, src interface{}, 96 filter ExtendPropertyFilterFunc) error { 97 return extendMatchingProperties(dst, src, filter, orderPrepend) 98} 99 100// ExtendProperties appends or prepends the values of properties in the property struct src to the 101// property struct dst. dst and src must be the same type, and both must be pointers to structs. 102// 103// The filter function can prevent individual properties from being appended or prepended by 104// returning false, or abort ExtendProperties with an error by returning an error. Passing nil for 105// filter will append or prepend all properties. 106// 107// The order function is called on each non-filtered property to determine if it should be appended 108// or prepended. 109// 110// An error returned by ExtendProperties that applies to a specific property will be an 111// *ExtendPropertyError, and can have the property name and error extracted from it. 112// 113// The append operation is defined as appending strings and slices of strings normally, OR-ing bool 114// values, replacing non-nil pointers to booleans or strings, and recursing into 115// embedded structs, pointers to structs, and interfaces containing 116// pointers to structs. Appending or prepending the zero value of a property will always be a 117// no-op. 118func ExtendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc, 119 order ExtendPropertyOrderFunc) error { 120 return extendProperties(dst, src, filter, order) 121} 122 123// ExtendMatchingProperties appends or prepends the values of properties in the property struct src 124// to the property structs in dst. dst and src do not have to be the same type, but every property 125// in src must be found in at least one property in dst. dst must be a slice of pointers to 126// structs, and src must be a pointer to a struct. 127// 128// The filter function can prevent individual properties from being appended or prepended by 129// returning false, or abort ExtendMatchingProperties with an error by returning an error. Passing 130// nil for filter will append or prepend all properties. 131// 132// The order function is called on each non-filtered property to determine if it should be appended 133// or prepended. 134// 135// An error returned by ExtendMatchingProperties that applies to a specific property will be an 136// *ExtendPropertyError, and can have the property name and error extracted from it. 137// 138// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool 139// values, replacing non-nil pointers to booleans or strings, and recursing into 140// embedded structs, pointers to structs, and interfaces containing 141// pointers to structs. Appending or prepending the zero value of a property will always be a 142// no-op. 143func ExtendMatchingProperties(dst []interface{}, src interface{}, 144 filter ExtendPropertyFilterFunc, order ExtendPropertyOrderFunc) error { 145 return extendMatchingProperties(dst, src, filter, order) 146} 147 148type Order int 149 150const ( 151 Append Order = iota 152 Prepend 153) 154 155type ExtendPropertyFilterFunc func(property string, 156 dstField, srcField reflect.StructField, 157 dstValue, srcValue interface{}) (bool, error) 158 159type ExtendPropertyOrderFunc func(property string, 160 dstField, srcField reflect.StructField, 161 dstValue, srcValue interface{}) (Order, error) 162 163func orderAppend(property string, 164 dstField, srcField reflect.StructField, 165 dstValue, srcValue interface{}) (Order, error) { 166 return Append, nil 167} 168 169func orderPrepend(property string, 170 dstField, srcField reflect.StructField, 171 dstValue, srcValue interface{}) (Order, error) { 172 return Prepend, nil 173} 174 175type ExtendPropertyError struct { 176 Err error 177 Property string 178} 179 180func (e *ExtendPropertyError) Error() string { 181 return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err) 182} 183 184func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError { 185 return &ExtendPropertyError{ 186 Err: fmt.Errorf(format, a...), 187 Property: property, 188 } 189} 190 191func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc, 192 order ExtendPropertyOrderFunc) error { 193 194 srcValue, err := getStruct(src) 195 if err != nil { 196 if _, ok := err.(getStructEmptyError); ok { 197 return nil 198 } 199 return err 200 } 201 202 dstValue, err := getOrCreateStruct(dst) 203 if err != nil { 204 return err 205 } 206 207 if dstValue.Type() != srcValue.Type() { 208 return fmt.Errorf("expected matching types for dst and src, got %T and %T", dst, src) 209 } 210 211 dstValues := []reflect.Value{dstValue} 212 213 return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, order) 214} 215 216func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc, 217 order ExtendPropertyOrderFunc) error { 218 219 srcValue, err := getStruct(src) 220 if err != nil { 221 if _, ok := err.(getStructEmptyError); ok { 222 return nil 223 } 224 return err 225 } 226 227 dstValues := make([]reflect.Value, len(dst)) 228 for i := range dst { 229 var err error 230 dstValues[i], err = getOrCreateStruct(dst[i]) 231 if err != nil { 232 return err 233 } 234 } 235 236 return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order) 237} 238 239func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value, 240 prefix string, filter ExtendPropertyFilterFunc, sameTypes bool, 241 orderFunc ExtendPropertyOrderFunc) error { 242 243 srcType := srcValue.Type() 244 for i, srcField := range typeFields(srcType) { 245 if srcField.PkgPath != "" { 246 // The field is not exported so just skip it. 247 continue 248 } 249 if HasTag(srcField, "blueprint", "mutated") { 250 continue 251 } 252 253 propertyName := prefix + PropertyNameForField(srcField.Name) 254 srcFieldValue := srcValue.Field(i) 255 256 // Step into source interfaces 257 if srcFieldValue.Kind() == reflect.Interface { 258 if srcFieldValue.IsNil() { 259 continue 260 } 261 262 srcFieldValue = srcFieldValue.Elem() 263 264 if srcFieldValue.Kind() != reflect.Ptr { 265 return extendPropertyErrorf(propertyName, "interface not a pointer") 266 } 267 } 268 269 // Step into source pointers to structs 270 if srcFieldValue.Kind() == reflect.Ptr && srcFieldValue.Type().Elem().Kind() == reflect.Struct { 271 if srcFieldValue.IsNil() { 272 continue 273 } 274 275 srcFieldValue = srcFieldValue.Elem() 276 } 277 278 found := false 279 var recurse []reflect.Value 280 for _, dstValue := range dstValues { 281 dstType := dstValue.Type() 282 var dstField reflect.StructField 283 284 dstFields := typeFields(dstType) 285 if dstType == srcType { 286 dstField = dstFields[i] 287 } else { 288 var ok bool 289 for _, field := range dstFields { 290 if field.Name == srcField.Name { 291 dstField = field 292 ok = true 293 } 294 } 295 if !ok { 296 continue 297 } 298 } 299 300 found = true 301 302 dstFieldValue := dstValue.FieldByIndex(dstField.Index) 303 origDstFieldValue := dstFieldValue 304 305 // Step into destination interfaces 306 if dstFieldValue.Kind() == reflect.Interface { 307 if dstFieldValue.IsNil() { 308 return extendPropertyErrorf(propertyName, "nilitude mismatch") 309 } 310 311 dstFieldValue = dstFieldValue.Elem() 312 313 if dstFieldValue.Kind() != reflect.Ptr { 314 return extendPropertyErrorf(propertyName, "interface not a pointer") 315 } 316 } 317 318 // Step into destination pointers to structs 319 if dstFieldValue.Kind() == reflect.Ptr && dstFieldValue.Type().Elem().Kind() == reflect.Struct { 320 if dstFieldValue.IsNil() { 321 dstFieldValue = reflect.New(dstFieldValue.Type().Elem()) 322 origDstFieldValue.Set(dstFieldValue) 323 } 324 325 dstFieldValue = dstFieldValue.Elem() 326 } 327 328 switch srcFieldValue.Kind() { 329 case reflect.Struct: 330 if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() { 331 return extendPropertyErrorf(propertyName, "mismatched types %s and %s", 332 dstFieldValue.Type(), srcFieldValue.Type()) 333 } 334 335 // Recursively extend the struct's fields. 336 recurse = append(recurse, dstFieldValue) 337 continue 338 case reflect.Bool, reflect.String, reflect.Slice: 339 if srcFieldValue.Type() != dstFieldValue.Type() { 340 return extendPropertyErrorf(propertyName, "mismatched types %s and %s", 341 dstFieldValue.Type(), srcFieldValue.Type()) 342 } 343 case reflect.Ptr: 344 if srcFieldValue.Type() != dstFieldValue.Type() { 345 return extendPropertyErrorf(propertyName, "mismatched types %s and %s", 346 dstFieldValue.Type(), srcFieldValue.Type()) 347 } 348 switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind { 349 case reflect.Bool, reflect.Int64, reflect.String, reflect.Struct: 350 // Nothing 351 default: 352 return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind) 353 } 354 default: 355 return extendPropertyErrorf(propertyName, "unsupported kind %s", 356 srcFieldValue.Kind()) 357 } 358 359 dstFieldInterface := dstFieldValue.Interface() 360 srcFieldInterface := srcFieldValue.Interface() 361 362 if filter != nil { 363 b, err := filter(propertyName, dstField, srcField, 364 dstFieldInterface, srcFieldInterface) 365 if err != nil { 366 return &ExtendPropertyError{ 367 Property: propertyName, 368 Err: err, 369 } 370 } 371 if !b { 372 continue 373 } 374 } 375 376 order := Append 377 if orderFunc != nil { 378 var err error 379 order, err = orderFunc(propertyName, dstField, srcField, 380 dstFieldInterface, srcFieldInterface) 381 if err != nil { 382 return &ExtendPropertyError{ 383 Property: propertyName, 384 Err: err, 385 } 386 } 387 } 388 389 ExtendBasicType(dstFieldValue, srcFieldValue, order) 390 } 391 392 if len(recurse) > 0 { 393 err := extendPropertiesRecursive(recurse, srcFieldValue, 394 propertyName+".", filter, sameTypes, orderFunc) 395 if err != nil { 396 return err 397 } 398 } else if !found { 399 return extendPropertyErrorf(propertyName, "failed to find property to extend") 400 } 401 } 402 403 return nil 404} 405 406func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) { 407 prepend := order == Prepend 408 409 switch srcFieldValue.Kind() { 410 case reflect.Bool: 411 // Boolean OR 412 dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool())) 413 case reflect.String: 414 if prepend { 415 dstFieldValue.SetString(srcFieldValue.String() + 416 dstFieldValue.String()) 417 } else { 418 dstFieldValue.SetString(dstFieldValue.String() + 419 srcFieldValue.String()) 420 } 421 case reflect.Slice: 422 if srcFieldValue.IsNil() { 423 break 424 } 425 426 newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0, 427 dstFieldValue.Len()+srcFieldValue.Len()) 428 if prepend { 429 newSlice = reflect.AppendSlice(newSlice, srcFieldValue) 430 newSlice = reflect.AppendSlice(newSlice, dstFieldValue) 431 } else { 432 newSlice = reflect.AppendSlice(newSlice, dstFieldValue) 433 newSlice = reflect.AppendSlice(newSlice, srcFieldValue) 434 } 435 dstFieldValue.Set(newSlice) 436 case reflect.Ptr: 437 if srcFieldValue.IsNil() { 438 break 439 } 440 441 switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind { 442 case reflect.Bool: 443 if prepend { 444 if dstFieldValue.IsNil() { 445 dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool()))) 446 } 447 } else { 448 // For append, replace the original value. 449 dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool()))) 450 } 451 case reflect.Int64: 452 if prepend { 453 if dstFieldValue.IsNil() { 454 // Int() returns Int64 455 dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int()))) 456 } 457 } else { 458 // For append, replace the original value. 459 // Int() returns Int64 460 dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int()))) 461 } 462 case reflect.String: 463 if prepend { 464 if dstFieldValue.IsNil() { 465 dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String()))) 466 } 467 } else { 468 // For append, replace the original value. 469 dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String()))) 470 } 471 default: 472 panic(fmt.Errorf("unexpected pointer kind %s", ptrKind)) 473 } 474 } 475} 476 477type getStructEmptyError struct{} 478 479func (getStructEmptyError) Error() string { return "interface containing nil pointer" } 480 481func getOrCreateStruct(in interface{}) (reflect.Value, error) { 482 value, err := getStruct(in) 483 if _, ok := err.(getStructEmptyError); ok { 484 value := reflect.ValueOf(in) 485 newValue := reflect.New(value.Type().Elem()) 486 value.Set(newValue) 487 } 488 489 return value, err 490} 491 492func getStruct(in interface{}) (reflect.Value, error) { 493 value := reflect.ValueOf(in) 494 if value.Kind() != reflect.Ptr { 495 return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in) 496 } 497 if value.Type().Elem().Kind() != reflect.Struct { 498 return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in) 499 } 500 if value.IsNil() { 501 return reflect.Value{}, getStructEmptyError{} 502 } 503 value = value.Elem() 504 return value, nil 505} 506