diff --git a/helpers/reflect.go b/helpers/reflect.go index 0655e47..e501171 100644 --- a/helpers/reflect.go +++ b/helpers/reflect.go @@ -5,46 +5,8 @@ import ( "reflect" "regexp" "strconv" - "strings" - - "github.com/tkrajina/go-reflector/reflector" ) -// GetFieldValueFromInterface uses reflection to return the value of the given property path -func GetFieldValueFromInterface(i interface{}, fieldName string) (interface{}, bool) { - obj := reflector.New(i) - if arrayProperty, index, ok := IsFieldArray(fieldName); ok { - if arrVal, ok := GetFieldValueFromInterface(i, arrayProperty); ok { - return GetArrayValue(arrVal, index) - } - return nil, false - } - - if reflect.TypeOf(i).Kind() == reflect.Map { - return obj.GetByKey(fieldName) - } - - val, err := obj.Field(fieldName).Get() - return val, err == nil -} - -// GetNestedFieldValueFromInterface uses reflection to return the value of the given nested property path -func GetNestedFieldValueFromInterface(item interface{}, propertyPath string) (interface{}, bool) { - var value interface{} - var ok bool - parent := item - var pathSegments = strings.Split(propertyPath, ".") - for _, propertyName := range pathSegments { - value, ok = GetFieldValueFromInterface(parent, propertyName) - if !ok { - return nil, false - } - // update parent for next iteration - parent = value - } - return value, true -} - func GetArrayValue(i interface{}, index int) (interface{}, bool) { if reflect.TypeOf(i).Kind() != reflect.Slice { return nil, false diff --git a/helpers/reflect_field_value.go b/helpers/reflect_field_value.go new file mode 100644 index 0000000..9623031 --- /dev/null +++ b/helpers/reflect_field_value.go @@ -0,0 +1,61 @@ +package helpers + +import ( + "reflect" + "strings" + + "github.com/tkrajina/go-reflector/reflector" +) + +// GetFieldValueFromInterface uses reflection to return the value of the given property path +func GetFieldValueFromInterface(i interface{}, fieldName string) (interface{}, bool) { + // unescape property name + fieldName = UnescapePropertyName(fieldName) + obj := reflector.New(i) + if arrayProperty, index, ok := IsFieldArray(fieldName); ok { + if arrVal, ok := GetFieldValueFromInterface(i, arrayProperty); ok { + return GetArrayValue(arrVal, index) + } + return nil, false + } + + if reflect.TypeOf(i).Kind() == reflect.Map { + return obj.GetByKey(fieldName) + } + + val, err := obj.Field(fieldName).Get() + return val, err == nil +} + +// GetNestedFieldValueFromInterface uses reflection to return the value of the given nested property path +func GetNestedFieldValueFromInterface(item interface{}, propertyPath string) (interface{}, bool) { + var value interface{} + var ok bool + parent := item + var pathSegments = strings.Split(propertyPath, ".") + for _, fieldName := range pathSegments { + // if there are any dots encoded in this segment, decode + fieldName = UnescapePropertyName(fieldName) + value, ok = GetFieldValueFromInterface(parent, fieldName) + if !ok { + return nil, false + } + // update parent for next iteration + parent = value + } + return value, true +} + +const propertyPathDotEscape = "$steampipe_escaped_dot$" + +// helpers to support property names containing a dot + +// EscapePropertyName replaces any '.' characters in the property name with propertyPathDotEscape +func EscapePropertyName(name string) string { + return strings.Replace(name, ".", propertyPathDotEscape, -1) +} + +// UnescapePropertyName replaces any propertyPathDotEscape occurrences with "." +func UnescapePropertyName(name string) string { + return strings.Replace(name, propertyPathDotEscape, ".", -1) +} diff --git a/helpers/reflect_test.go b/helpers/reflect_test.go index dba0edc..b59cb05 100644 --- a/helpers/reflect_test.go +++ b/helpers/reflect_test.go @@ -121,6 +121,11 @@ var testCasesGetNestedFieldValueFromInterface = map[string]GetNestedFieldValueFr fieldName: "Str.S", expected: "foo S2", }, + "struct double level string escaped": { + input: s1Instance, + fieldName: fmt.Sprintf("%s.%s", EscapePropertyName("Str"), EscapePropertyName("S")), + expected: "foo S2", + }, "Array property path: Nested multi-dimensional failure": { input: s1Instance, fieldName: "Str.Arr[0][3]", @@ -186,6 +191,11 @@ var testCasesGetNestedFieldValueFromInterface = map[string]GetNestedFieldValueFr fieldName: "I", expected: 1, }, + "struct single level int (escaped)": { + input: s1Instance, + fieldName: EscapePropertyName("I"), + expected: 1, + }, "struct pointer single level int": { input: &s1Instance, fieldName: "I",