2015-10-28 02:15:15 +01:00
|
|
|
// Copyright 2015 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package proptools
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
)
|
|
|
|
|
|
|
|
// AppendProperties appends the values of properties in the property struct src to the property
|
2021-09-09 21:49:58 +02:00
|
|
|
// struct dst. dst and src must be the same type, and both must be pointers to structs. Properties
|
|
|
|
// tagged `blueprint:"mutated"` are skipped.
|
2015-10-28 02:15:15 +01:00
|
|
|
//
|
|
|
|
// The filter function can prevent individual properties from being appended by returning false, or
|
|
|
|
// abort AppendProperties with an error by returning an error. Passing nil for filter will append
|
|
|
|
// all properties.
|
|
|
|
//
|
|
|
|
// An error returned by AppendProperties that applies to a specific property will be an
|
|
|
|
// *ExtendPropertyError, and can have the property name and error extracted from it.
|
|
|
|
//
|
2016-01-05 23:16:04 +01:00
|
|
|
// The append operation is defined as appending strings and slices of strings normally, OR-ing bool
|
|
|
|
// values, replacing non-nil pointers to booleans or strings, and recursing into
|
2015-10-30 23:53:55 +01:00
|
|
|
// embedded structs, pointers to structs, and interfaces containing
|
2015-10-28 02:15:15 +01:00
|
|
|
// pointers to structs. Appending the zero value of a property will always be a no-op.
|
|
|
|
func AppendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error {
|
2019-06-22 20:19:34 +02:00
|
|
|
return extendProperties(dst, src, filter, OrderAppend)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// PrependProperties prepends the values of properties in the property struct src to the property
|
2021-09-09 21:49:58 +02:00
|
|
|
// struct dst. dst and src must be the same type, and both must be pointers to structs. Properties
|
|
|
|
// tagged `blueprint:"mutated"` are skipped.
|
2015-10-28 02:15:15 +01:00
|
|
|
//
|
|
|
|
// The filter function can prevent individual properties from being prepended by returning false, or
|
|
|
|
// abort PrependProperties with an error by returning an error. Passing nil for filter will prepend
|
|
|
|
// all properties.
|
|
|
|
//
|
|
|
|
// An error returned by PrependProperties that applies to a specific property will be an
|
|
|
|
// *ExtendPropertyError, and can have the property name and error extracted from it.
|
|
|
|
//
|
2016-01-05 23:16:04 +01:00
|
|
|
// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing
|
|
|
|
// bool values, replacing non-nil pointers to booleans or strings, and recursing into
|
2015-10-30 23:53:55 +01:00
|
|
|
// embedded structs, pointers to structs, and interfaces containing
|
2015-10-28 02:15:15 +01:00
|
|
|
// pointers to structs. Prepending the zero value of a property will always be a no-op.
|
|
|
|
func PrependProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error {
|
2019-06-22 20:19:34 +02:00
|
|
|
return extendProperties(dst, src, filter, OrderPrepend)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// AppendMatchingProperties appends the values of properties in the property struct src to the
|
|
|
|
// property structs in dst. dst and src do not have to be the same type, but every property in src
|
|
|
|
// must be found in at least one property in dst. dst must be a slice of pointers to structs, and
|
2021-09-09 21:49:58 +02:00
|
|
|
// src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are skipped.
|
2015-10-28 02:15:15 +01:00
|
|
|
//
|
|
|
|
// The filter function can prevent individual properties from being appended by returning false, or
|
|
|
|
// abort AppendProperties with an error by returning an error. Passing nil for filter will append
|
|
|
|
// all properties.
|
|
|
|
//
|
|
|
|
// An error returned by AppendMatchingProperties that applies to a specific property will be an
|
|
|
|
// *ExtendPropertyError, and can have the property name and error extracted from it.
|
|
|
|
//
|
2016-01-05 23:16:04 +01:00
|
|
|
// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool
|
2019-07-08 22:40:28 +02:00
|
|
|
// values, replacing pointers to booleans or strings whether they are nil or not, and recursing into
|
2015-10-30 23:53:55 +01:00
|
|
|
// embedded structs, pointers to structs, and interfaces containing
|
2015-10-28 02:15:15 +01:00
|
|
|
// pointers to structs. Appending the zero value of a property will always be a no-op.
|
|
|
|
func AppendMatchingProperties(dst []interface{}, src interface{},
|
|
|
|
filter ExtendPropertyFilterFunc) error {
|
2019-06-22 20:19:34 +02:00
|
|
|
return extendMatchingProperties(dst, src, filter, OrderAppend)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// PrependMatchingProperties prepends the values of properties in the property struct src to the
|
|
|
|
// property structs in dst. dst and src do not have to be the same type, but every property in src
|
|
|
|
// must be found in at least one property in dst. dst must be a slice of pointers to structs, and
|
2021-09-09 21:49:58 +02:00
|
|
|
// src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are skipped.
|
2015-10-28 02:15:15 +01:00
|
|
|
//
|
|
|
|
// The filter function can prevent individual properties from being prepended by returning false, or
|
|
|
|
// abort PrependProperties with an error by returning an error. Passing nil for filter will prepend
|
|
|
|
// all properties.
|
|
|
|
//
|
|
|
|
// An error returned by PrependProperties that applies to a specific property will be an
|
|
|
|
// *ExtendPropertyError, and can have the property name and error extracted from it.
|
|
|
|
//
|
2016-01-05 23:16:04 +01:00
|
|
|
// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing
|
2019-07-08 22:40:28 +02:00
|
|
|
// bool values, replacing nil pointers to booleans or strings, and recursing into
|
2015-10-30 23:53:55 +01:00
|
|
|
// embedded structs, pointers to structs, and interfaces containing
|
2015-10-28 02:15:15 +01:00
|
|
|
// pointers to structs. Prepending the zero value of a property will always be a no-op.
|
|
|
|
func PrependMatchingProperties(dst []interface{}, src interface{},
|
|
|
|
filter ExtendPropertyFilterFunc) error {
|
2019-06-22 20:19:34 +02:00
|
|
|
return extendMatchingProperties(dst, src, filter, OrderPrepend)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
2016-05-06 00:58:02 +02:00
|
|
|
// ExtendProperties appends or prepends the values of properties in the property struct src to the
|
|
|
|
// property struct dst. dst and src must be the same type, and both must be pointers to structs.
|
2021-09-09 21:49:58 +02:00
|
|
|
// Properties tagged `blueprint:"mutated"` are skipped.
|
2016-05-06 00:58:02 +02:00
|
|
|
//
|
|
|
|
// The filter function can prevent individual properties from being appended or prepended by
|
|
|
|
// returning false, or abort ExtendProperties with an error by returning an error. Passing nil for
|
|
|
|
// filter will append or prepend all properties.
|
|
|
|
//
|
|
|
|
// The order function is called on each non-filtered property to determine if it should be appended
|
|
|
|
// or prepended.
|
|
|
|
//
|
|
|
|
// An error returned by ExtendProperties that applies to a specific property will be an
|
|
|
|
// *ExtendPropertyError, and can have the property name and error extracted from it.
|
|
|
|
//
|
|
|
|
// The append operation is defined as appending strings and slices of strings normally, OR-ing bool
|
|
|
|
// values, replacing non-nil pointers to booleans or strings, and recursing into
|
|
|
|
// embedded structs, pointers to structs, and interfaces containing
|
|
|
|
// pointers to structs. Appending or prepending the zero value of a property will always be a
|
|
|
|
// no-op.
|
|
|
|
func ExtendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
|
|
|
order ExtendPropertyOrderFunc) error {
|
|
|
|
return extendProperties(dst, src, filter, order)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtendMatchingProperties appends or prepends the values of properties in the property struct src
|
|
|
|
// to the property structs in dst. dst and src do not have to be the same type, but every property
|
|
|
|
// in src must be found in at least one property in dst. dst must be a slice of pointers to
|
2021-09-09 21:49:58 +02:00
|
|
|
// structs, and src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are
|
|
|
|
// skipped.
|
2016-05-06 00:58:02 +02:00
|
|
|
//
|
|
|
|
// The filter function can prevent individual properties from being appended or prepended by
|
|
|
|
// returning false, or abort ExtendMatchingProperties with an error by returning an error. Passing
|
|
|
|
// nil for filter will append or prepend all properties.
|
|
|
|
//
|
|
|
|
// The order function is called on each non-filtered property to determine if it should be appended
|
|
|
|
// or prepended.
|
|
|
|
//
|
|
|
|
// An error returned by ExtendMatchingProperties that applies to a specific property will be an
|
|
|
|
// *ExtendPropertyError, and can have the property name and error extracted from it.
|
|
|
|
//
|
|
|
|
// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool
|
|
|
|
// values, replacing non-nil pointers to booleans or strings, and recursing into
|
|
|
|
// embedded structs, pointers to structs, and interfaces containing
|
|
|
|
// pointers to structs. Appending or prepending the zero value of a property will always be a
|
|
|
|
// no-op.
|
|
|
|
func ExtendMatchingProperties(dst []interface{}, src interface{},
|
|
|
|
filter ExtendPropertyFilterFunc, order ExtendPropertyOrderFunc) error {
|
|
|
|
return extendMatchingProperties(dst, src, filter, order)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Order int
|
|
|
|
|
|
|
|
const (
|
|
|
|
Append Order = iota
|
|
|
|
Prepend
|
2019-11-15 10:31:56 +01:00
|
|
|
Replace
|
2016-05-06 00:58:02 +02:00
|
|
|
)
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
type ExtendPropertyFilterFunc func(property string,
|
|
|
|
dstField, srcField reflect.StructField,
|
|
|
|
dstValue, srcValue interface{}) (bool, error)
|
|
|
|
|
2016-05-06 00:58:02 +02:00
|
|
|
type ExtendPropertyOrderFunc func(property string,
|
|
|
|
dstField, srcField reflect.StructField,
|
|
|
|
dstValue, srcValue interface{}) (Order, error)
|
|
|
|
|
2019-06-22 20:19:34 +02:00
|
|
|
func OrderAppend(property string,
|
2016-05-06 00:58:02 +02:00
|
|
|
dstField, srcField reflect.StructField,
|
|
|
|
dstValue, srcValue interface{}) (Order, error) {
|
|
|
|
return Append, nil
|
|
|
|
}
|
|
|
|
|
2019-06-22 20:19:34 +02:00
|
|
|
func OrderPrepend(property string,
|
2016-05-06 00:58:02 +02:00
|
|
|
dstField, srcField reflect.StructField,
|
|
|
|
dstValue, srcValue interface{}) (Order, error) {
|
|
|
|
return Prepend, nil
|
|
|
|
}
|
|
|
|
|
2019-11-15 10:31:56 +01:00
|
|
|
func OrderReplace(property string,
|
|
|
|
dstField, srcField reflect.StructField,
|
|
|
|
dstValue, srcValue interface{}) (Order, error) {
|
|
|
|
return Replace, nil
|
|
|
|
}
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
type ExtendPropertyError struct {
|
|
|
|
Err error
|
|
|
|
Property string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ExtendPropertyError) Error() string {
|
|
|
|
return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError {
|
|
|
|
return &ExtendPropertyError{
|
|
|
|
Err: fmt.Errorf(format, a...),
|
|
|
|
Property: property,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
2016-05-06 00:58:02 +02:00
|
|
|
order ExtendPropertyOrderFunc) error {
|
2015-10-28 02:15:15 +01:00
|
|
|
|
2016-08-06 02:19:36 +02:00
|
|
|
srcValue, err := getStruct(src)
|
2015-10-28 02:15:15 +01:00
|
|
|
if err != nil {
|
2016-08-06 02:19:36 +02:00
|
|
|
if _, ok := err.(getStructEmptyError); ok {
|
|
|
|
return nil
|
|
|
|
}
|
2015-10-28 02:15:15 +01:00
|
|
|
return err
|
|
|
|
}
|
2016-08-06 02:19:36 +02:00
|
|
|
|
|
|
|
dstValue, err := getOrCreateStruct(dst)
|
2015-10-28 02:15:15 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if dstValue.Type() != srcValue.Type() {
|
|
|
|
return fmt.Errorf("expected matching types for dst and src, got %T and %T", dst, src)
|
|
|
|
}
|
|
|
|
|
|
|
|
dstValues := []reflect.Value{dstValue}
|
|
|
|
|
2016-05-06 00:58:02 +02:00
|
|
|
return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, order)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
|
2016-05-06 00:58:02 +02:00
|
|
|
order ExtendPropertyOrderFunc) error {
|
2015-10-28 02:15:15 +01:00
|
|
|
|
2016-08-06 02:19:36 +02:00
|
|
|
srcValue, err := getStruct(src)
|
|
|
|
if err != nil {
|
|
|
|
if _, ok := err.(getStructEmptyError); ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
dstValues := make([]reflect.Value, len(dst))
|
|
|
|
for i := range dst {
|
|
|
|
var err error
|
2016-08-06 02:19:36 +02:00
|
|
|
dstValues[i], err = getOrCreateStruct(dst[i])
|
2015-10-28 02:15:15 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-06 00:58:02 +02:00
|
|
|
return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value,
|
2016-05-06 00:58:02 +02:00
|
|
|
prefix string, filter ExtendPropertyFilterFunc, sameTypes bool,
|
2017-07-29 02:51:37 +02:00
|
|
|
orderFunc ExtendPropertyOrderFunc) error {
|
2015-10-28 02:15:15 +01:00
|
|
|
|
2021-06-26 01:44:30 +02:00
|
|
|
dstValuesCopied := false
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
srcType := srcValue.Type()
|
2016-05-17 22:57:12 +02:00
|
|
|
for i, srcField := range typeFields(srcType) {
|
2022-01-10 22:01:18 +01:00
|
|
|
if ShouldSkipProperty(srcField) {
|
2015-10-28 02:15:15 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
propertyName := prefix + PropertyNameForField(srcField.Name)
|
|
|
|
srcFieldValue := srcValue.Field(i)
|
|
|
|
|
2016-08-20 03:16:33 +02:00
|
|
|
// Step into source interfaces
|
|
|
|
if srcFieldValue.Kind() == reflect.Interface {
|
|
|
|
if srcFieldValue.IsNil() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
srcFieldValue = srcFieldValue.Elem()
|
|
|
|
|
|
|
|
if srcFieldValue.Kind() != reflect.Ptr {
|
|
|
|
return extendPropertyErrorf(propertyName, "interface not a pointer")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step into source pointers to structs
|
2020-01-28 01:48:30 +01:00
|
|
|
if isStructPtr(srcFieldValue.Type()) {
|
2016-08-20 03:16:33 +02:00
|
|
|
if srcFieldValue.IsNil() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
srcFieldValue = srcFieldValue.Elem()
|
|
|
|
}
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
found := false
|
2016-08-20 03:16:33 +02:00
|
|
|
var recurse []reflect.Value
|
2021-06-26 01:44:30 +02:00
|
|
|
// Use an iteration loop so elements can be added to the end of dstValues inside the loop.
|
|
|
|
for j := 0; j < len(dstValues); j++ {
|
|
|
|
dstValue := dstValues[j]
|
2015-10-28 02:15:15 +01:00
|
|
|
dstType := dstValue.Type()
|
|
|
|
var dstField reflect.StructField
|
|
|
|
|
2016-05-17 22:57:12 +02:00
|
|
|
dstFields := typeFields(dstType)
|
2015-10-28 02:15:15 +01:00
|
|
|
if dstType == srcType {
|
2016-05-17 22:57:12 +02:00
|
|
|
dstField = dstFields[i]
|
2015-10-28 02:15:15 +01:00
|
|
|
} else {
|
|
|
|
var ok bool
|
2016-05-17 22:57:12 +02:00
|
|
|
for _, field := range dstFields {
|
|
|
|
if field.Name == srcField.Name {
|
|
|
|
dstField = field
|
|
|
|
ok = true
|
2022-01-10 22:01:18 +01:00
|
|
|
} else if IsEmbedded(field) {
|
2021-06-26 01:44:30 +02:00
|
|
|
embeddedDstValue := dstValue.FieldByIndex(field.Index)
|
|
|
|
if isStructPtr(embeddedDstValue.Type()) {
|
|
|
|
if embeddedDstValue.IsNil() {
|
|
|
|
newEmbeddedDstValue := reflect.New(embeddedDstValue.Type().Elem())
|
|
|
|
embeddedDstValue.Set(newEmbeddedDstValue)
|
|
|
|
}
|
|
|
|
embeddedDstValue = embeddedDstValue.Elem()
|
|
|
|
}
|
|
|
|
if !isStruct(embeddedDstValue.Type()) {
|
|
|
|
return extendPropertyErrorf(propertyName, "%s is not a struct (%s)",
|
|
|
|
prefix+field.Name, embeddedDstValue.Type())
|
|
|
|
}
|
|
|
|
// The destination struct contains an embedded struct, add it to the list
|
|
|
|
// of destinations to consider. Make a copy of dstValues if necessary
|
|
|
|
// to avoid modifying the backing array of an input parameter.
|
|
|
|
if !dstValuesCopied {
|
|
|
|
dstValues = append([]reflect.Value(nil), dstValues...)
|
|
|
|
dstValuesCopied = true
|
|
|
|
}
|
|
|
|
dstValues = append(dstValues, embeddedDstValue)
|
2016-05-17 22:57:12 +02:00
|
|
|
}
|
|
|
|
}
|
2015-10-28 02:15:15 +01:00
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
found = true
|
|
|
|
|
|
|
|
dstFieldValue := dstValue.FieldByIndex(dstField.Index)
|
2016-08-06 02:19:36 +02:00
|
|
|
origDstFieldValue := dstFieldValue
|
2015-10-28 02:15:15 +01:00
|
|
|
|
2016-08-20 03:16:33 +02:00
|
|
|
// Step into destination interfaces
|
|
|
|
if dstFieldValue.Kind() == reflect.Interface {
|
2015-10-28 02:15:15 +01:00
|
|
|
if dstFieldValue.IsNil() {
|
2016-08-20 03:16:33 +02:00
|
|
|
return extendPropertyErrorf(propertyName, "nilitude mismatch")
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
dstFieldValue = dstFieldValue.Elem()
|
|
|
|
|
2016-08-20 03:16:33 +02:00
|
|
|
if dstFieldValue.Kind() != reflect.Ptr {
|
2015-10-28 02:15:15 +01:00
|
|
|
return extendPropertyErrorf(propertyName, "interface not a pointer")
|
|
|
|
}
|
2016-08-20 03:16:33 +02:00
|
|
|
}
|
2015-10-28 02:15:15 +01:00
|
|
|
|
2016-08-20 03:16:33 +02:00
|
|
|
// Step into destination pointers to structs
|
2020-01-28 01:48:30 +01:00
|
|
|
if isStructPtr(dstFieldValue.Type()) {
|
2015-10-28 02:15:15 +01:00
|
|
|
if dstFieldValue.IsNil() {
|
2016-08-20 03:16:33 +02:00
|
|
|
dstFieldValue = reflect.New(dstFieldValue.Type().Elem())
|
2016-08-06 02:19:36 +02:00
|
|
|
origDstFieldValue.Set(dstFieldValue)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
dstFieldValue = dstFieldValue.Elem()
|
2016-08-20 03:16:33 +02:00
|
|
|
}
|
2015-10-28 02:15:15 +01:00
|
|
|
|
2016-08-20 03:16:33 +02:00
|
|
|
switch srcFieldValue.Kind() {
|
2015-10-28 02:15:15 +01:00
|
|
|
case reflect.Struct:
|
|
|
|
if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
|
|
|
|
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
|
|
|
dstFieldValue.Type(), srcFieldValue.Type())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recursively extend the struct's fields.
|
2016-08-20 03:16:33 +02:00
|
|
|
recurse = append(recurse, dstFieldValue)
|
2015-10-28 02:15:15 +01:00
|
|
|
continue
|
2021-05-21 23:56:53 +02:00
|
|
|
case reflect.Bool, reflect.String, reflect.Slice, reflect.Map:
|
2015-10-28 02:15:15 +01:00
|
|
|
if srcFieldValue.Type() != dstFieldValue.Type() {
|
|
|
|
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
|
|
|
dstFieldValue.Type(), srcFieldValue.Type())
|
|
|
|
}
|
2016-08-20 03:16:33 +02:00
|
|
|
case reflect.Ptr:
|
|
|
|
if srcFieldValue.Type() != dstFieldValue.Type() {
|
|
|
|
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
|
|
|
dstFieldValue.Type(), srcFieldValue.Type())
|
|
|
|
}
|
|
|
|
switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
case reflect.Bool, reflect.Int64, reflect.String, reflect.Struct:
|
2016-08-20 03:16:33 +02:00
|
|
|
// Nothing
|
|
|
|
default:
|
|
|
|
return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
|
|
|
|
}
|
2015-10-28 02:15:15 +01:00
|
|
|
default:
|
|
|
|
return extendPropertyErrorf(propertyName, "unsupported kind %s",
|
|
|
|
srcFieldValue.Kind())
|
|
|
|
}
|
|
|
|
|
2016-05-17 22:57:12 +02:00
|
|
|
dstFieldInterface := dstFieldValue.Interface()
|
|
|
|
srcFieldInterface := srcFieldValue.Interface()
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
if filter != nil {
|
|
|
|
b, err := filter(propertyName, dstField, srcField,
|
2016-05-17 22:57:12 +02:00
|
|
|
dstFieldInterface, srcFieldInterface)
|
2015-10-28 02:15:15 +01:00
|
|
|
if err != nil {
|
|
|
|
return &ExtendPropertyError{
|
|
|
|
Property: propertyName,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !b {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-29 02:51:37 +02:00
|
|
|
order := Append
|
|
|
|
if orderFunc != nil {
|
|
|
|
var err error
|
|
|
|
order, err = orderFunc(propertyName, dstField, srcField,
|
2016-05-17 22:57:12 +02:00
|
|
|
dstFieldInterface, srcFieldInterface)
|
2016-05-06 00:58:02 +02:00
|
|
|
if err != nil {
|
|
|
|
return &ExtendPropertyError{
|
|
|
|
Property: propertyName,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-29 02:51:37 +02:00
|
|
|
ExtendBasicType(dstFieldValue, srcFieldValue, order)
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
2017-07-29 02:51:37 +02:00
|
|
|
|
2016-08-20 03:16:33 +02:00
|
|
|
if len(recurse) > 0 {
|
|
|
|
err := extendPropertiesRecursive(recurse, srcFieldValue,
|
2017-07-29 02:51:37 +02:00
|
|
|
propertyName+".", filter, sameTypes, orderFunc)
|
2016-08-20 03:16:33 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if !found {
|
2015-10-28 02:15:15 +01:00
|
|
|
return extendPropertyErrorf(propertyName, "failed to find property to extend")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-29 02:51:37 +02:00
|
|
|
func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) {
|
|
|
|
prepend := order == Prepend
|
|
|
|
|
|
|
|
switch srcFieldValue.Kind() {
|
|
|
|
case reflect.Bool:
|
|
|
|
// Boolean OR
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool()))
|
|
|
|
case reflect.String:
|
|
|
|
if prepend {
|
|
|
|
dstFieldValue.SetString(srcFieldValue.String() +
|
|
|
|
dstFieldValue.String())
|
|
|
|
} else {
|
|
|
|
dstFieldValue.SetString(dstFieldValue.String() +
|
|
|
|
srcFieldValue.String())
|
|
|
|
}
|
|
|
|
case reflect.Slice:
|
|
|
|
if srcFieldValue.IsNil() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0,
|
|
|
|
dstFieldValue.Len()+srcFieldValue.Len())
|
|
|
|
if prepend {
|
|
|
|
newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
|
|
|
|
newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
|
2019-11-15 10:31:56 +01:00
|
|
|
} else if order == Append {
|
2017-07-29 02:51:37 +02:00
|
|
|
newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
|
|
|
|
newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
|
2019-11-15 10:31:56 +01:00
|
|
|
} else {
|
|
|
|
// replace
|
|
|
|
newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
|
2017-07-29 02:51:37 +02:00
|
|
|
}
|
|
|
|
dstFieldValue.Set(newSlice)
|
2021-05-21 23:56:53 +02:00
|
|
|
case reflect.Map:
|
|
|
|
if srcFieldValue.IsNil() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
var mapValue reflect.Value
|
|
|
|
// for append/prepend, maintain keys from original value
|
|
|
|
// for replace, replace entire map
|
|
|
|
if order == Replace || dstFieldValue.IsNil() {
|
|
|
|
mapValue = srcFieldValue
|
|
|
|
} else {
|
|
|
|
mapValue = dstFieldValue
|
|
|
|
|
|
|
|
iter := srcFieldValue.MapRange()
|
|
|
|
for iter.Next() {
|
|
|
|
dstValue := dstFieldValue.MapIndex(iter.Key())
|
|
|
|
if prepend {
|
|
|
|
// if the key exists in the map, keep the original value.
|
|
|
|
if !dstValue.IsValid() {
|
|
|
|
// otherwise, add the new value
|
|
|
|
mapValue.SetMapIndex(iter.Key(), iter.Value())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// For append, replace the original value.
|
|
|
|
mapValue.SetMapIndex(iter.Key(), iter.Value())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dstFieldValue.Set(mapValue)
|
2017-07-29 02:51:37 +02:00
|
|
|
case reflect.Ptr:
|
|
|
|
if srcFieldValue.IsNil() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
|
|
|
|
case reflect.Bool:
|
|
|
|
if prepend {
|
|
|
|
if dstFieldValue.IsNil() {
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// For append, replace the original value.
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
|
|
|
|
}
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
case reflect.Int64:
|
|
|
|
if prepend {
|
|
|
|
if dstFieldValue.IsNil() {
|
|
|
|
// Int() returns Int64
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int())))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// For append, replace the original value.
|
|
|
|
// Int() returns Int64
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int())))
|
|
|
|
}
|
2017-07-29 02:51:37 +02:00
|
|
|
case reflect.String:
|
|
|
|
if prepend {
|
|
|
|
if dstFieldValue.IsNil() {
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// For append, replace the original value.
|
|
|
|
dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unexpected pointer kind %s", ptrKind))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 22:01:18 +01:00
|
|
|
// ShouldSkipProperty indicates whether a property should be skipped in processing.
|
|
|
|
func ShouldSkipProperty(structField reflect.StructField) bool {
|
|
|
|
return structField.PkgPath != "" || // The field is not exported so just skip it.
|
|
|
|
HasTag(structField, "blueprint", "mutated") // The field is not settable in a blueprint file
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsEmbedded indicates whether a property is embedded. This is useful for determining nesting name
|
|
|
|
// as the name of the embedded field is _not_ used in blueprint files.
|
|
|
|
func IsEmbedded(structField reflect.StructField) bool {
|
|
|
|
return structField.Name == "BlueprintEmbed" || structField.Anonymous
|
|
|
|
}
|
|
|
|
|
2016-08-06 02:19:36 +02:00
|
|
|
type getStructEmptyError struct{}
|
|
|
|
|
|
|
|
func (getStructEmptyError) Error() string { return "interface containing nil pointer" }
|
|
|
|
|
|
|
|
func getOrCreateStruct(in interface{}) (reflect.Value, error) {
|
|
|
|
value, err := getStruct(in)
|
|
|
|
if _, ok := err.(getStructEmptyError); ok {
|
|
|
|
value := reflect.ValueOf(in)
|
|
|
|
newValue := reflect.New(value.Type().Elem())
|
|
|
|
value.Set(newValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
return value, err
|
|
|
|
}
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
func getStruct(in interface{}) (reflect.Value, error) {
|
|
|
|
value := reflect.ValueOf(in)
|
2020-01-28 01:48:30 +01:00
|
|
|
if !isStructPtr(value.Type()) {
|
|
|
|
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %s", value.Type())
|
2015-10-28 02:15:15 +01:00
|
|
|
}
|
2016-08-06 02:19:36 +02:00
|
|
|
if value.IsNil() {
|
|
|
|
return reflect.Value{}, getStructEmptyError{}
|
|
|
|
}
|
|
|
|
value = value.Elem()
|
2015-10-28 02:15:15 +01:00
|
|
|
return value, nil
|
|
|
|
}
|