Skip to content

Commit

Permalink
Add support for poly struct in flatten schema
Browse files Browse the repository at this point in the history
Signed-off-by: Shawn Wang <[email protected]>
  • Loading branch information
wsquan171 committed Jun 10, 2024
1 parent 40070d3 commit 15392cb
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 48 deletions.
95 changes: 60 additions & 35 deletions nsxt/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,25 @@ func getResourceTypeFromStructValue(ctx string, value data.StructValue, identifi
return "", err
}

func polyStructToNestedSchema(ctx string, elem reflect.Value, item *ExtendedSchema) (ret []map[string]interface{}, err error) {
if item.Metadata.PolymorphicType != PolymorphicTypeNested {
err = fmt.Errorf("%s polyStructToNestedSchema called on non-wrapped polymorphic attr", ctx)
func polyMetadataSanityCheck(ctx string, item *ExtendedSchema, polyType string) error {
if item.Metadata.PolymorphicType != polyType {
err := fmt.Errorf("%s unexpected polymorphic attr", ctx)
logger.Printf("[ERROR] %v", err)
return err
}

_, ok := item.Schema.Elem.(*ExtendedResource)
if !ok {
err := fmt.Errorf("%s polymorphic attr has non-ExtendedResource element", ctx)
logger.Printf("[ERROR] %v", err)
return err
}

return nil
}

func polyStructToNestedSchema(ctx string, elem reflect.Value, item *ExtendedSchema) (ret []map[string]interface{}, err error) {
if err = polyMetadataSanityCheck(ctx, item, PolymorphicTypeNested); err != nil {
return
}

Expand Down Expand Up @@ -509,19 +524,11 @@ func polyStructToNestedSchema(ctx string, elem reflect.Value, item *ExtendedSche
}

func polyNestedSchemaToStruct(ctx string, elem reflect.Value, dataList []interface{}, item *ExtendedSchema) (err error) {
if item.Metadata.PolymorphicType != PolymorphicTypeNested {
err = fmt.Errorf("%s polyNestedSchemaToStruct called on non-wrapped polymorphic attr", ctx)
logger.Printf("[ERROR] %v", err)
return
}

childElem, ok := item.Schema.Elem.(*ExtendedResource)
if !ok {
err = fmt.Errorf("%s polymorphic attr has non-ExtendedResource element", ctx)
logger.Printf("[ERROR] %v", err)
if err = polyMetadataSanityCheck(ctx, item, PolymorphicTypeNested); err != nil {
return
}

childElem := item.Schema.Elem.(*ExtendedResource)
converter := vapiBindings_.NewTypeConverter()
dv := make([]*data.StructValue, len(dataList))
for i, dataElem := range dataList {
Expand Down Expand Up @@ -585,23 +592,31 @@ func polyNestedSchemaToStruct(ctx string, elem reflect.Value, dataList []interfa
}

func polyStructToFlattenSchema(ctx string, elem reflect.Value, key string, item *ExtendedSchema) (ret []map[string]interface{}, err error) {
if item.Metadata.PolymorphicType != PolymorphicTypeFlatten {
err = fmt.Errorf("%s polyStructToFlattenSchema called on non-flat polymorphic attr", ctx)
logger.Printf("[ERROR] %v", err)
if err = polyMetadataSanityCheck(ctx, item, PolymorphicTypeFlatten); err != nil {
return
}

_, ok := item.Schema.Elem.(*ExtendedResource)
if !ok {
err = fmt.Errorf("%s polymorphic attr has non-ExtendedResource element", ctx)
elemSlice := elem
if item.Metadata.SchemaType == "struct" {
if elem.IsNil() {
return
}
// convert to slice to share logic with list and set
elemSlice = reflect.MakeSlice(reflect.SliceOf(elem.Type()), 1, 1)
elemSlice.Index(0).Set(elem)
} else if item.Metadata.SchemaType != "list" && item.Metadata.SchemaType != "set" {
err = fmt.Errorf("%s unsupported polymorphic schema type %s", ctx, item.Metadata.SchemaType)
logger.Printf("[ERROR] %v", err)
return
}
if elemSlice.Len() == 0 {
return
}

converter := vapiBindings_.NewTypeConverter()
sliceValue := make([]map[string]interface{}, 0)
for i := 0; i < elem.Len(); i++ {
childValue := elem.Index(i).Elem().Interface().(data.StructValue)
for i := 0; i < elemSlice.Len(); i++ {
childValue := elemSlice.Index(i).Elem().Interface().(data.StructValue)
nestedSchema := make(map[string]interface{})
var rType string

Expand Down Expand Up @@ -630,19 +645,11 @@ func polyStructToFlattenSchema(ctx string, elem reflect.Value, key string, item
}

func polyFlattenSchemaToStruct(ctx string, elem reflect.Value, key string, dataList []interface{}, item *ExtendedSchema) (err error) {
if item.Metadata.PolymorphicType != PolymorphicTypeFlatten {
err = fmt.Errorf("%s polyFlattenSchemaToStruct called on non-flat polymorphic attr", ctx)
logger.Printf("[ERROR] %v", err)
return
}

childElem, ok := item.Schema.Elem.(*ExtendedResource)
if !ok {
err = fmt.Errorf("%s polymorphic attr has non-ExtendedResource element", ctx)
logger.Printf("[ERROR] %v", err)
if err = polyMetadataSanityCheck(ctx, item, PolymorphicTypeFlatten); err != nil {
return
}

childElem := item.Schema.Elem.(*ExtendedResource)
converter := vapiBindings_.NewTypeConverter()
rSlice := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(&data.StructValue{})), len(dataList), len(dataList))
for i, dataElem := range dataList {
Expand All @@ -667,11 +674,29 @@ func polyFlattenSchemaToStruct(ctx string, elem reflect.Value, key string, dataL
ctx, nestedObj.Interface(), item.Metadata.SdkFieldName)
}

sliceElem := elem.FieldByName(item.Metadata.SdkFieldName)
if sliceElem.IsZero() {
sliceElem.Set(rSlice)
if item.Metadata.SchemaType == "struct" {
if rSlice.Len() == 0 {
return
}
structElem := elem.FieldByName(item.Metadata.SdkFieldName)
if !structElem.IsZero() {
err = fmt.Errorf("%s %s is alreay set", ctx, item.Metadata.SdkFieldName)
logger.Printf("[ERROR] %v", err)
return
}
structElem.Set(rSlice.Index(0))
} else if item.Metadata.SchemaType == "list" || item.Metadata.SchemaType == "set" {
sliceElem := elem.FieldByName(item.Metadata.SdkFieldName)
if sliceElem.IsZero() {
sliceElem.Set(rSlice)
} else {
sliceElem.Set(reflect.AppendSlice(sliceElem, rSlice))
}
} else {
sliceElem.Set(reflect.AppendSlice(sliceElem, rSlice))
err = fmt.Errorf("%s unsupported polymorphic schema type %s", ctx, item.Metadata.SchemaType)
logger.Printf("[ERROR] %v", err)
return
}

return
}
146 changes: 133 additions & 13 deletions nsxt/metadata/metadata_poly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,18 @@ func TestNestedSchemaToPolyStruct(t *testing.T) {
})
}

func testPolyStructFlattenSchema() map[string]*schema.Schema {
func testPolyStructFlattenSchema(t string) map[string]*schema.Schema {
schemaType := schema.TypeList
maxItems := 0
if t == "set" {
schemaType = schema.TypeSet
} else if t == "struct" {
maxItems = 1
}
return map[string]*schema.Schema{
"cat": {
Type: schema.TypeList,
Type: schemaType,
MaxItems: maxItems,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Expand All @@ -549,7 +557,8 @@ func testPolyStructFlattenSchema() map[string]*schema.Schema {
},
},
"coffee": {
Type: schema.TypeList,
Type: schemaType,
MaxItems: maxItems,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Expand All @@ -564,16 +573,23 @@ func testPolyStructFlattenSchema() map[string]*schema.Schema {
}
}

func testPolyStructFlattenExtSchema(sdkName string) map[string]*ExtendedSchema {
func testPolyStructFlattenExtSchema(t, sdkName string) map[string]*ExtendedSchema {
typeIdentifier := TypeIdentifier{
SdkName: "Type",
APIFieldName: "type",
}

schemaType := schema.TypeList
maxItems := 0
if t == "set" {
schemaType = schema.TypeSet
} else if t == "struct" {
maxItems = 1
}
return map[string]*ExtendedSchema{
"cat": {
Schema: schema.Schema{
Type: schema.TypeList,
Type: schemaType,
MaxItems: maxItems,
Elem: &ExtendedResource{
Schema: map[string]*ExtendedSchema{
"name": basicStringSchema("Name", false),
Expand All @@ -582,7 +598,7 @@ func testPolyStructFlattenExtSchema(sdkName string) map[string]*ExtendedSchema {
},
},
Metadata: Metadata{
SchemaType: "list",
SchemaType: t,
ReflectType: reflect.TypeOf(testCatStruct{}),
BindingType: testCatStructBindingType(),
TypeIdentifier: typeIdentifier,
Expand All @@ -593,7 +609,8 @@ func testPolyStructFlattenExtSchema(sdkName string) map[string]*ExtendedSchema {
},
"coffee": {
Schema: schema.Schema{
Type: schema.TypeList,
Type: schemaType,
MaxItems: maxItems,
Elem: &ExtendedResource{
Schema: map[string]*ExtendedSchema{
"name": basicStringSchema("Name", false),
Expand All @@ -602,7 +619,7 @@ func testPolyStructFlattenExtSchema(sdkName string) map[string]*ExtendedSchema {
},
},
Metadata: Metadata{
SchemaType: "list",
SchemaType: t,
ReflectType: reflect.TypeOf(testCoffeeStruct{}),
BindingType: testCoffeeStructBindingType(),
TypeIdentifier: typeIdentifier,
Expand All @@ -615,6 +632,62 @@ func testPolyStructFlattenExtSchema(sdkName string) map[string]*ExtendedSchema {
}

func TestPolyStructToFlattenSchema(t *testing.T) {
t.Run("cat struct", func(t *testing.T) {
name := "matcha"
rType := "FakeCat"
age := int64(1)
catObj := testCatStruct{
Age: &age,
Name: &name,
Type: &rType,
}
obj := testPolyStruct{}
converter := vapiBindings_.NewTypeConverter()
dv, errors := converter.ConvertToVapi(catObj, testCatStructBindingType())
assert.Nil(t, errors, "unexpected error calling ConvertToGolang")
obj.PolyStruct = dv.(*data.StructValue)
d := schema.TestResourceDataRaw(
t, testPolyStructFlattenSchema("struct"), map[string]interface{}{})

elem := reflect.ValueOf(&obj).Elem()
err := StructToSchema(elem, d, testPolyStructFlattenExtSchema("struct", "PolyStruct"), "", nil)
assert.NoError(t, err, "unexpected error calling StructToSchema")
assert.Len(t, d.Get("cat"), 1)
assert.Len(t, d.Get("coffee"), 0)
assert.Equal(t, map[string]interface{}{
"name": name,
"age": 1,
}, d.Get("cat").([]interface{})[0].(map[string]interface{}))
})

t.Run("coffee struct", func(t *testing.T) {
name := "latte"
rType := "FakeCoffee"
isDecaf := true
coffeeObj := testCoffeeStruct{
IsDecaf: &isDecaf,
Name: &name,
Type: &rType,
}
obj := testPolyStruct{}
converter := vapiBindings_.NewTypeConverter()
dv, errors := converter.ConvertToVapi(coffeeObj, testCoffeeStructBindingType())
assert.Nil(t, errors, "unexpected error calling ConvertToGolang")
obj.PolyStruct = dv.(*data.StructValue)
d := schema.TestResourceDataRaw(
t, testPolyStructFlattenSchema("struct"), map[string]interface{}{})

elem := reflect.ValueOf(&obj).Elem()
err := StructToSchema(elem, d, testPolyStructFlattenExtSchema("struct", "PolyStruct"), "", nil)
assert.NoError(t, err, "unexpected error calling StructToSchema")
assert.Len(t, d.Get("coffee"), 1)
assert.Len(t, d.Get("cat"), 0)
assert.Equal(t, map[string]interface{}{
"name": name,
"is_decaf": true,
}, d.Get("coffee").([]interface{})[0].(map[string]interface{}))
})

t.Run("mixed list", func(t *testing.T) {
catName := "oolong"
coffeeName := "mocha"
Expand Down Expand Up @@ -643,10 +716,10 @@ func TestPolyStructToFlattenSchema(t *testing.T) {
assert.Nil(t, errors, "unexpected error calling ConvertToGolang")
obj.PolyList[1] = dv.(*data.StructValue)
d := schema.TestResourceDataRaw(
t, testPolyStructFlattenSchema(), map[string]interface{}{})
t, testPolyStructFlattenSchema("list"), map[string]interface{}{})

elem := reflect.ValueOf(&obj).Elem()
err := StructToSchema(elem, d, testPolyStructFlattenExtSchema("PolyList"), "", nil)
err := StructToSchema(elem, d, testPolyStructFlattenExtSchema("list", "PolyList"), "", nil)
assert.NoError(t, err, "unexpected error calling StructToSchema")

assert.Len(t, d.Get("coffee"), 1)
Expand All @@ -664,9 +737,56 @@ func TestPolyStructToFlattenSchema(t *testing.T) {
}

func TestFlattenSchemaToPolyStruct(t *testing.T) {
t.Run("cat struct", func(t *testing.T) {
d := schema.TestResourceDataRaw(
t, testPolyStructFlattenSchema("struct"), map[string]interface{}{
"cat": []interface{}{
map[string]interface{}{
"name": "matcha",
"age": 1,
},
},
})

obj := testPolyStruct{}
elem := reflect.ValueOf(&obj).Elem()
err := SchemaToStruct(elem, d, testPolyStructFlattenExtSchema("struct", "PolyStruct"), "", nil)
assert.NoError(t, err, "unexpected error calling SchemaToStruct")

converter := vapiBindings_.NewTypeConverter()
obs, errors := converter.ConvertToGolang(obj.PolyStruct, testCatStructBindingType())
assert.Nil(t, errors, "unexpected error calling ConvertToGolang")
assert.Equal(t, "matcha", *obs.(testCatStruct).Name)
assert.Equal(t, int64(1), *obs.(testCatStruct).Age)
assert.Equal(t, "FakeCat", *obs.(testCatStruct).Type)
})

t.Run("error on assigning struct multiple times", func(t *testing.T) {
d := schema.TestResourceDataRaw(
t, testPolyStructFlattenSchema("struct"), map[string]interface{}{
"cat": []interface{}{
map[string]interface{}{
"name": "matcha",
"age": 1,
},
},
"coffee": []interface{}{
map[string]interface{}{
"name": "mocha",
"is_decaf": true,
},
},
})

obj := testPolyStruct{}
elem := reflect.ValueOf(&obj).Elem()
err := SchemaToStruct(elem, d, testPolyStructFlattenExtSchema("struct", "PolyStruct"), "", nil)
assert.ErrorContainsf(t, err, "is alreay set", "expected error raised if same sdk is set twice")
})

t.Run("mixed list", func(t *testing.T) {
d := schema.TestResourceDataRaw(
t, testPolyStructFlattenSchema(), map[string]interface{}{
t, testPolyStructFlattenSchema("list"), map[string]interface{}{
"coffee": []interface{}{
map[string]interface{}{
"name": "mocha",
Expand All @@ -683,7 +803,7 @@ func TestFlattenSchemaToPolyStruct(t *testing.T) {

obj := testPolyListStruct{}
elem := reflect.ValueOf(&obj).Elem()
err := SchemaToStruct(elem, d, testPolyStructFlattenExtSchema("PolyList"), "", nil)
err := SchemaToStruct(elem, d, testPolyStructFlattenExtSchema("list", "PolyList"), "", nil)
assert.NoError(t, err, "unexpected error calling SchemaToStruct")

converter := vapiBindings_.NewTypeConverter()
Expand Down

0 comments on commit 15392cb

Please sign in to comment.