| // 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" |
| "slices" |
| "strings" |
| ) |
| |
| // AppendProperties appends 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. Properties |
| // tagged `blueprint:"mutated"` are skipped. |
| // |
| // 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. |
| // |
| // 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 the zero value of a property will always be a no-op. |
| func AppendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error { |
| return extendProperties(dst, src, filter, OrderAppend) |
| } |
| |
| // PrependProperties 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. Properties |
| // tagged `blueprint:"mutated"` are skipped. |
| // |
| // 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. |
| // |
| // 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 |
| // embedded structs, pointers to structs, and interfaces containing |
| // 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 { |
| return extendProperties(dst, src, filter, OrderPrepend) |
| } |
| |
| // 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 |
| // src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are skipped. |
| // |
| // 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. |
| // |
| // The append operation is defined as appending strings, and slices of strings normally, OR-ing bool |
| // values, replacing pointers to booleans or strings whether they are nil or not, and recursing into |
| // embedded structs, pointers to structs, and interfaces containing |
| // 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 { |
| return extendMatchingProperties(dst, src, filter, OrderAppend) |
| } |
| |
| // 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 |
| // src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are skipped. |
| // |
| // 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. |
| // |
| // The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing |
| // bool values, replacing nil pointers to booleans or strings, and recursing into |
| // embedded structs, pointers to structs, and interfaces containing |
| // 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 { |
| return extendMatchingProperties(dst, src, filter, OrderPrepend) |
| } |
| |
| // 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. |
| // Properties tagged `blueprint:"mutated"` are skipped. |
| // |
| // 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 |
| // structs, and src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are |
| // skipped. |
| // |
| // 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 ( |
| // When merging properties, strings and lists will be concatenated, and booleans will be OR'd together |
| Append Order = iota |
| // Same as append, but acts as if the arguments to the extend* functions were swapped. The src value will be |
| // prepended to the dst value instead of appended. |
| Prepend |
| // Instead of concatenating/ORing properties, the dst value will be completely replaced by the src value. |
| // Replace currently only works for slices, maps, and configurable properties. Due to legacy behavior, |
| // pointer properties will always act as if they're using replace ordering. |
| Replace |
| // Same as replace, but acts as if the arguments to the extend* functions were swapped. The src value will be |
| // used only if the dst value was unset. |
| Prepend_replace |
| ) |
| |
| type ExtendPropertyFilterFunc func(dstField, srcField reflect.StructField) (bool, error) |
| |
| type ExtendPropertyOrderFunc func(dstField, srcField reflect.StructField) (Order, error) |
| |
| func OrderAppend(dstField, srcField reflect.StructField) (Order, error) { |
| return Append, nil |
| } |
| |
| func OrderPrepend(dstField, srcField reflect.StructField) (Order, error) { |
| return Prepend, nil |
| } |
| |
| func OrderReplace(dstField, srcField reflect.StructField) (Order, error) { |
| return Replace, nil |
| } |
| |
| 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, |
| order ExtendPropertyOrderFunc) error { |
| |
| srcValue, err := getStruct(src) |
| if err != nil { |
| if _, ok := err.(getStructEmptyError); ok { |
| return nil |
| } |
| return err |
| } |
| |
| dstValue, err := getOrCreateStruct(dst) |
| 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} |
| |
| return extendPropertiesRecursive(dstValues, srcValue, make([]string, 0, 8), filter, true, order) |
| } |
| |
| func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc, |
| order ExtendPropertyOrderFunc) error { |
| |
| srcValue, err := getStruct(src) |
| if err != nil { |
| if _, ok := err.(getStructEmptyError); ok { |
| return nil |
| } |
| return err |
| } |
| |
| dstValues := make([]reflect.Value, len(dst)) |
| for i := range dst { |
| var err error |
| dstValues[i], err = getOrCreateStruct(dst[i]) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return extendPropertiesRecursive(dstValues, srcValue, make([]string, 0, 8), filter, false, order) |
| } |
| |
| func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value, |
| prefix []string, filter ExtendPropertyFilterFunc, sameTypes bool, |
| orderFunc ExtendPropertyOrderFunc) error { |
| |
| dstValuesCopied := false |
| |
| propertyName := func(field reflect.StructField) string { |
| names := make([]string, 0, len(prefix)+1) |
| for _, s := range prefix { |
| names = append(names, PropertyNameForField(s)) |
| } |
| names = append(names, PropertyNameForField(field.Name)) |
| return strings.Join(names, ".") |
| } |
| |
| srcType := srcValue.Type() |
| for i, srcField := range typeFields(srcType) { |
| if ShouldSkipProperty(srcField) { |
| continue |
| } |
| |
| srcFieldValue := srcValue.Field(i) |
| |
| // Step into source interfaces |
| if srcFieldValue.Kind() == reflect.Interface { |
| if srcFieldValue.IsNil() { |
| continue |
| } |
| |
| srcFieldValue = srcFieldValue.Elem() |
| |
| if srcFieldValue.Kind() != reflect.Ptr { |
| return extendPropertyErrorf(propertyName(srcField), "interface not a pointer") |
| } |
| } |
| |
| // Step into source pointers to structs |
| if isStructPtr(srcFieldValue.Type()) { |
| if srcFieldValue.IsNil() { |
| continue |
| } |
| |
| srcFieldValue = srcFieldValue.Elem() |
| } |
| |
| found := false |
| var recurse []reflect.Value |
| // 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] |
| dstType := dstValue.Type() |
| var dstField reflect.StructField |
| |
| dstFields := typeFields(dstType) |
| if dstType == srcType { |
| dstField = dstFields[i] |
| } else { |
| var ok bool |
| for _, field := range dstFields { |
| if field.Name == srcField.Name { |
| dstField = field |
| ok = true |
| } else if IsEmbedded(field) { |
| 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(srcField), "%s is not a struct (%s)", |
| propertyName(field), 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 = slices.Clone(dstValues) |
| dstValuesCopied = true |
| } |
| dstValues = append(dstValues, embeddedDstValue) |
| } |
| } |
| if !ok { |
| continue |
| } |
| } |
| |
| found = true |
| |
| dstFieldValue := dstValue.FieldByIndex(dstField.Index) |
| origDstFieldValue := dstFieldValue |
| |
| // Step into destination interfaces |
| if dstFieldValue.Kind() == reflect.Interface { |
| if dstFieldValue.IsNil() { |
| return extendPropertyErrorf(propertyName(srcField), "nilitude mismatch") |
| } |
| |
| dstFieldValue = dstFieldValue.Elem() |
| |
| if dstFieldValue.Kind() != reflect.Ptr { |
| return extendPropertyErrorf(propertyName(srcField), "interface not a pointer") |
| } |
| } |
| |
| // Step into destination pointers to structs |
| if isStructPtr(dstFieldValue.Type()) { |
| if dstFieldValue.IsNil() { |
| dstFieldValue = reflect.New(dstFieldValue.Type().Elem()) |
| origDstFieldValue.Set(dstFieldValue) |
| } |
| |
| dstFieldValue = dstFieldValue.Elem() |
| } |
| |
| switch srcFieldValue.Kind() { |
| case reflect.Struct: |
| if isConfigurable(srcField.Type) { |
| if srcFieldValue.Type() != dstFieldValue.Type() { |
| return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s", |
| dstFieldValue.Type(), srcFieldValue.Type()) |
| } |
| } else { |
| if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() { |
| return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s", |
| dstFieldValue.Type(), srcFieldValue.Type()) |
| } |
| |
| // Recursively extend the struct's fields. |
| recurse = append(recurse, dstFieldValue) |
| continue |
| } |
| case reflect.Bool, reflect.String, reflect.Slice, reflect.Map: |
| // If the types don't match or srcFieldValue cannot be converted to a Configurable type, it's an error |
| ct, err := configurableType(srcFieldValue.Type()) |
| if srcFieldValue.Type() != dstFieldValue.Type() && (err != nil || dstFieldValue.Type() != ct) { |
| return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s", |
| dstFieldValue.Type(), srcFieldValue.Type()) |
| } |
| case reflect.Ptr: |
| // If the types don't match or srcFieldValue cannot be converted to a Configurable type, it's an error |
| ct, err := configurableType(srcFieldValue.Type().Elem()) |
| if srcFieldValue.Type() != dstFieldValue.Type() && (err != nil || dstFieldValue.Type() != ct) { |
| return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s", |
| dstFieldValue.Type(), srcFieldValue.Type()) |
| } |
| switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind { |
| case reflect.Bool, reflect.Int64, reflect.String, reflect.Struct: |
| // Nothing |
| default: |
| return extendPropertyErrorf(propertyName(srcField), "pointer is a %s", ptrKind) |
| } |
| default: |
| return extendPropertyErrorf(propertyName(srcField), "unsupported kind %s", |
| srcFieldValue.Kind()) |
| } |
| |
| if filter != nil { |
| b, err := filter(dstField, srcField) |
| if err != nil { |
| return &ExtendPropertyError{ |
| Property: propertyName(srcField), |
| Err: err, |
| } |
| } |
| if !b { |
| continue |
| } |
| } |
| |
| order := Append |
| if orderFunc != nil { |
| var err error |
| order, err = orderFunc(dstField, srcField) |
| if err != nil { |
| return &ExtendPropertyError{ |
| Property: propertyName(srcField), |
| Err: err, |
| } |
| } |
| } |
| |
| if HasTag(dstField, "android", "replace_instead_of_append") { |
| if order == Append { |
| order = Replace |
| } else if order == Prepend { |
| order = Prepend_replace |
| } |
| } |
| |
| ExtendBasicType(dstFieldValue, srcFieldValue, order) |
| } |
| |
| if len(recurse) > 0 { |
| err := extendPropertiesRecursive(recurse, srcFieldValue, |
| append(prefix, srcField.Name), filter, sameTypes, orderFunc) |
| if err != nil { |
| return err |
| } |
| } else if !found { |
| return extendPropertyErrorf(propertyName(srcField), "failed to find property to extend") |
| } |
| } |
| |
| return nil |
| } |
| |
| func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) { |
| prepend := order == Prepend || order == Prepend_replace |
| |
| if !srcFieldValue.IsValid() { |
| return |
| } |
| |
| // If dst is a Configurable and src isn't, promote src to a Configurable. |
| // This isn't necessary if all property structs are using Configurable values, |
| // but it's helpful to avoid having to change as many places in the code when |
| // converting properties to Configurable properties. For example, load hooks |
| // make their own mini-property structs and append them onto the main property |
| // structs when they want to change the default values of properties. |
| srcFieldType := srcFieldValue.Type() |
| if isConfigurable(dstFieldValue.Type()) && !isConfigurable(srcFieldType) { |
| var value reflect.Value |
| if srcFieldType.Kind() == reflect.Pointer { |
| srcFieldType = srcFieldType.Elem() |
| if srcFieldValue.IsNil() { |
| value = srcFieldValue |
| } else { |
| // Copy the pointer |
| value = reflect.New(srcFieldType) |
| value.Elem().Set(srcFieldValue.Elem()) |
| } |
| } else { |
| value = reflect.New(srcFieldType) |
| value.Elem().Set(srcFieldValue) |
| } |
| caseType := configurableCaseType(srcFieldType) |
| case_ := reflect.New(caseType) |
| case_.Interface().(configurableCaseReflection).initialize(nil, value.Interface()) |
| cases := reflect.MakeSlice(reflect.SliceOf(caseType), 0, 1) |
| cases = reflect.Append(cases, case_.Elem()) |
| ct, err := configurableType(srcFieldType) |
| if err != nil { |
| // Should be unreachable due to earlier checks |
| panic(err.Error()) |
| } |
| temp := reflect.New(ct) |
| temp.Interface().(configurablePtrReflection).initialize("", nil, cases.Interface()) |
| srcFieldValue = temp.Elem() |
| } |
| |
| switch srcFieldValue.Kind() { |
| case reflect.Struct: |
| if !isConfigurable(srcFieldValue.Type()) { |
| panic("Should be unreachable") |
| } |
| replace := order == Prepend_replace || order == Replace |
| unpackedDst := dstFieldValue.Interface().(configurableReflection) |
| if unpackedDst.isEmpty() { |
| // Properties that were never initialized via unpacking from a bp file value |
| // will have a nil inner value, making them unable to be modified without a pointer |
| // like we don't have here. So instead replace the whole configurable object. |
| dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Interface().(configurableReflection).clone())) |
| } else { |
| unpackedDst.setAppend(srcFieldValue.Interface(), replace, prepend) |
| } |
| 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) |
| } else if order == Append { |
| newSlice = reflect.AppendSlice(newSlice, dstFieldValue) |
| newSlice = reflect.AppendSlice(newSlice, srcFieldValue) |
| } else { |
| // replace |
| newSlice = reflect.AppendSlice(newSlice, srcFieldValue) |
| } |
| dstFieldValue.Set(newSlice) |
| 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) |
| 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()))) |
| } |
| 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()))) |
| } |
| 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()))) |
| } |
| case reflect.Struct: |
| srcFieldValue := srcFieldValue.Elem() |
| if !isConfigurable(srcFieldValue.Type()) { |
| panic("Should be unreachable") |
| } |
| panic("Don't use pointers to Configurable properties. All Configurable properties can be unset, " + |
| "and the 'replacing' behavior can be accomplished with the `blueprint:\"replace_instead_of_append\" " + |
| "struct field tag. There's no reason to have a pointer configurable property.") |
| default: |
| panic(fmt.Errorf("unexpected pointer kind %s", ptrKind)) |
| } |
| } |
| } |
| |
| // 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 |
| } |
| |
| 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 |
| } |
| |
| func getStruct(in interface{}) (reflect.Value, error) { |
| value := reflect.ValueOf(in) |
| if !isStructPtr(value.Type()) { |
| return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %s", value.Type()) |
| } |
| if value.IsNil() { |
| return reflect.Value{}, getStructEmptyError{} |
| } |
| value = value.Elem() |
| return value, nil |
| } |