From 4eae1491ae85725fcf6eb6b5e884b6a4ab236684 Mon Sep 17 00:00:00 2001 From: Shawn Wang Date: Thu, 30 May 2024 15:47:14 -0700 Subject: [PATCH] Add support of polymorphic struct in reflect lib Signed-off-by: Shawn Wang --- nsxt/metadata/metadata.go | 233 +++++++++++- nsxt/metadata/metadata_poly_test.go | 526 ++++++++++++++++++++++++++++ 2 files changed, 750 insertions(+), 9 deletions(-) create mode 100644 nsxt/metadata/metadata_poly_test.go diff --git a/nsxt/metadata/metadata.go b/nsxt/metadata/metadata.go index 433250ab9..c4ec6a654 100644 --- a/nsxt/metadata/metadata.go +++ b/nsxt/metadata/metadata.go @@ -2,13 +2,14 @@ package metadata import ( "fmt" + "github.com/vmware/terraform-provider-nsxt/nsxt/util" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" "log" "os" "reflect" - "github.com/vmware/terraform-provider-nsxt/nsxt/util" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + vapiBindings_ "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" ) // package level logger to include log.Lshortfile context @@ -21,7 +22,13 @@ type Metadata struct { ReadOnly bool SdkFieldName string // This attribute is parent path for the object - IsParentPath bool + IsParentPath bool + // This attribute is polymorphic + IsPolymorphic bool + // SDK vapi binding type for converting polymorphic structs + BindingType vapiBindings_.BindingType + // Map from schema key of polymorphic attr to this SDK resource type + ResourceTypeMap map[string]string IntroducedInVersion string // skip handling of this attribute - it will be done manually Skip bool @@ -93,12 +100,15 @@ func getContextString(prefix, parent string, elemType reflect.Type) string { // currently supports nested subtype and trivial types func StructToSchema(elem reflect.Value, d *schema.ResourceData, metadata map[string]*ExtendedSchema, parent string, parentMap map[string]interface{}) (err error) { ctx := getContextString("from", parent, elem.Type()) - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("%s recovered from panic: %v", ctx, r) - logger.Printf("[ERROR] %v", err) - } - }() + /* + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%s recovered from panic: %v", ctx, r) + logger.Printf("[ERROR] %v", err) + } + }() + + */ for key, item := range metadata { if item.Metadata.Skip { @@ -121,6 +131,22 @@ func StructToSchema(elem reflect.Value, d *schema.ResourceData, metadata map[str logger.Printf("[TRACE] %s skip key %s with nil value", ctx, key) continue } + if item.Metadata.IsPolymorphic { + childElem := elem.FieldByName(item.Metadata.SdkFieldName) + var nestedVal interface{} + nestedVal, err = polyStructToSchema(ctx, childElem, item) + if err != nil { + return + } + + if len(parent) > 0 { + parentMap[key] = nestedVal + } else { + d.Set(key, nestedVal) + } + logger.Printf("[TRACE] %s adding polymorphic slice %+v to key %s", ctx, nestedVal, key) + continue + } if item.Metadata.SchemaType == "struct" { nestedObj := elem.FieldByName(item.Metadata.SdkFieldName) nestedSchema := make(map[string]interface{}) @@ -217,6 +243,26 @@ func SchemaToStruct(elem reflect.Value, d *schema.ResourceData, metadata map[str if len(parent) > 0 { logger.Printf("[TRACE] %s parent %s key %s", ctx, parent, key) } + if item.Metadata.IsPolymorphic { + var itemList []interface{} + if item.Metadata.SchemaType == "list" || item.Metadata.SchemaType == "struct" { + if len(parent) > 0 { + itemList = parentMap[key].([]interface{}) + } else { + itemList = d.Get(key).([]interface{}) + } + } else { + if len(parent) > 0 { + itemList = parentMap[key].(*schema.Set).List() + } else { + itemList = d.Get(key).(*schema.Set).List() + } + } + if err = polySchemaToStruct(ctx, elem, itemList, item); err != nil { + return + } + continue + } if item.Metadata.SchemaType == "string" { var value string if len(parent) > 0 { @@ -328,3 +374,172 @@ func SchemaToStruct(elem reflect.Value, d *schema.ResourceData, metadata map[str return } + +func polyStructToSchema(ctx string, elem reflect.Value, item *ExtendedSchema) (ret []map[string]interface{}, err error) { + if !item.Metadata.IsPolymorphic { + err = fmt.Errorf("%s polyStructToSchema called on non-polymorphic attr", ctx) + logger.Printf("[ERROR] %v", err) + return + } + + 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{}, elemSlice.Len()) + for i := 0; i < elemSlice.Len(); i++ { + childElem := elemSlice.Index(i).Elem().Interface().(data.StructValue) + nestedSchema := make(map[string]interface{}) + var key, rType string + var childExtSch *ExtendedSchema + + // Get resource type from SDK object + if !childElem.HasField("resource_type") { + err = fmt.Errorf("%s polyStructToSchema failed to get resource type for %s", + ctx, item.Metadata.SdkFieldName) + logger.Printf("[ERROR] %v", err) + return + } + var rTypeData data.DataValue + rTypeData, err = childElem.Field("resource_type") + if err != nil { + return + } + if strVal, ok := rTypeData.(*data.StringValue); ok { + rType = strVal.Value() + } else if opVal, ok := rTypeData.(*data.OptionalValue); ok { + rType, err = opVal.String() + if err != nil { + return + } + } else { + err = fmt.Errorf("%s polyStructToSchema failed to get resource type for %s", + ctx, item.Metadata.SdkFieldName) + logger.Printf("[ERROR] %v", err) + return + } + + // Get metadata for the corresponding type + for k, v := range item.Metadata.ResourceTypeMap { + if v == rType { + key = k + childExtSch = item.Schema.Elem.(*ExtendedResource).Schema[k] + break + } + } + if len(key) == 0 || childExtSch == nil { + err = fmt.Errorf("%s polyStructToSchema failed to get schema meta of type %s for %s", + ctx, rType, item.Metadata.SdkFieldName) + logger.Printf("[ERROR] %v", err) + return + } + + // Convert to concrete struct + dv, errors := converter.ConvertToGolang(&childElem, childExtSch.Metadata.BindingType) + if errors != nil { + err = errors[0] + logger.Printf("[ERROR] %v", err) + return + } + + if err = StructToSchema(reflect.ValueOf(dv), nil, childExtSch.Schema.Elem.(*ExtendedResource).Schema, key, nestedSchema); err != nil { + return + } + sliceValue[i] = make(map[string]interface{}) + sliceValue[i][key] = []interface{}{nestedSchema} + logger.Printf("[TRACE] %s adding %+v of key %s", ctx, dv, key) + } + + ret = sliceValue + return +} + +func polySchemaToStruct(ctx string, elem reflect.Value, dataList []interface{}, item *ExtendedSchema) (err error) { + if !item.Metadata.IsPolymorphic { + err = fmt.Errorf("%s polySchemaToStruct called on non-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) + return + } + + converter := vapiBindings_.NewTypeConverter() + dv := make([]*data.StructValue, len(dataList)) + for i, dataElem := range dataList { + dataMap := dataElem.(map[string]interface{}) + for k, v := range dataMap { + if len(v.([]interface{})) == 0 { + continue + } + rType, ok := item.Metadata.ResourceTypeMap[k] + if !ok { + err = fmt.Errorf("%s polymorphic attr has key %s not found in resource type map", ctx, k) + logger.Printf("[ERROR] %v", err) + return + } + if _, ok := childElem.Schema[k]; !ok { + err = fmt.Errorf("%s polymorphic attr has key %s not found in metadata", ctx, k) + logger.Printf("[ERROR] %v", err) + return + } + + childMeta := childElem.Schema[k] + nestedObj := reflect.New(childMeta.Metadata.ReflectType) + nestedSchema := v.([]interface{})[0].(map[string]interface{}) + if err = SchemaToStruct(nestedObj.Elem(), nil, childMeta.Schema.Elem.(*ExtendedResource).Schema, k, nestedSchema); err != nil { + return + } + // set resource type based on mapping + nestedObj.Elem().FieldByName("ResourceType").Set(reflect.ValueOf(&rType)) + + dataValue, errors := converter.ConvertToVapi(nestedObj.Interface(), childMeta.Metadata.BindingType) + if errors != nil { + err = errors[0] + logger.Printf("[ERROR] %v", err) + return + } + dv[i] = dataValue.(*data.StructValue) + logger.Printf("[TRACE] %s adding polymorphic value %+v to %s", + ctx, nestedObj.Interface(), item.Metadata.SdkFieldName) + + // there should be only one non-empty entry in the map + break + } + } + + if item.Metadata.SchemaType == "struct" { + elem.FieldByName(item.Metadata.SdkFieldName).Set(reflect.ValueOf(dv[0])) + } else if item.Metadata.SchemaType == "list" || item.Metadata.SchemaType == "set" { + sliceElem := elem.FieldByName(item.Metadata.SdkFieldName) + sliceElem.Set( + reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(&data.StructValue{})), len(dv), len(dv))) + for i, v := range dv { + sliceElem.Index(i).Set(reflect.ValueOf(v)) + } + } else { + err = fmt.Errorf("%s unsupported polymorphic schema type %s", ctx, item.Metadata.SchemaType) + logger.Printf("[ERROR] %v", err) + return + } + + return +} diff --git a/nsxt/metadata/metadata_poly_test.go b/nsxt/metadata/metadata_poly_test.go new file mode 100644 index 000000000..cc60a09df --- /dev/null +++ b/nsxt/metadata/metadata_poly_test.go @@ -0,0 +1,526 @@ +package metadata + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + vapiBindings_ "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" +) + +type testCatStruct struct { + Age *int64 + Name *string + ResourceType *string +} + +func testCatStructBindingType() vapiBindings_.BindingType { + fields := make(map[string]vapiBindings_.BindingType) + fieldNameMap := make(map[string]string) + fields["age"] = vapiBindings_.NewOptionalType(vapiBindings_.NewIntegerType()) + fieldNameMap["age"] = "Age" + fields["name"] = vapiBindings_.NewOptionalType(vapiBindings_.NewStringType()) + fieldNameMap["name"] = "Name" + fields["resource_type"] = vapiBindings_.NewOptionalType(vapiBindings_.NewStringType()) + fieldNameMap["resource_type"] = "ResourceType" + var validators = []vapiBindings_.Validator{} + return vapiBindings_.NewStructType("com.vmware.nsx.fake.cat", fields, + reflect.TypeOf(testCatStruct{}), fieldNameMap, validators) +} + +func TestCatStructBinding(t *testing.T) { + age := int64(123) + name := "John" + rType := "FakeCat" + obj := testCatStruct{ + Age: &age, + Name: &name, + ResourceType: &rType, + } + converter := vapiBindings_.NewTypeConverter() + dv, err := converter.ConvertToVapi(obj, testCatStructBindingType()) + assert.Nil(t, err) + obs, err := converter.ConvertToGolang(dv, testCatStructBindingType()) + assert.Nil(t, err) + assert.Equal(t, obj, obs.(testCatStruct)) +} + +type testCoffeeStruct struct { + IsDecaf *bool + Name *string + ResourceType *string +} + +func testCoffeeStructBindingType() vapiBindings_.BindingType { + fields := make(map[string]vapiBindings_.BindingType) + fieldNameMap := make(map[string]string) + fields["is_decaf"] = vapiBindings_.NewOptionalType(vapiBindings_.NewBooleanType()) + fieldNameMap["is_decaf"] = "IsDecaf" + fields["name"] = vapiBindings_.NewOptionalType(vapiBindings_.NewStringType()) + fieldNameMap["name"] = "Name" + fields["resource_type"] = vapiBindings_.NewOptionalType(vapiBindings_.NewStringType()) + fieldNameMap["resource_type"] = "ResourceType" + var validators = []vapiBindings_.Validator{} + return vapiBindings_.NewStructType("com.vmware.nsx.fake.coffee", fields, + reflect.TypeOf(testCoffeeStruct{}), fieldNameMap, validators) +} + +func TestCoffeeStructBinding(t *testing.T) { + decaf := false + name := "Latte" + rType := "FakeCoffee" + obj := testCoffeeStruct{ + IsDecaf: &decaf, + Name: &name, + ResourceType: &rType, + } + converter := vapiBindings_.NewTypeConverter() + dv, err := converter.ConvertToVapi(obj, testCoffeeStructBindingType()) + assert.Nil(t, err) + obs, err := converter.ConvertToGolang(dv, testCoffeeStructBindingType()) + assert.Nil(t, err) + assert.Equal(t, obj, obs.(testCoffeeStruct)) +} + +type testPolyStruct struct { + PolyStruct *data.StructValue +} + +type testPolyListStruct struct { + PolyList []*data.StructValue +} + +func testPolyStructSchema(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{ + "poly_struct": { + Type: schemaType, + MaxItems: maxItems, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cat": { + Type: schema.TypeList, + MaxItems: 1, + ConflictsWith: []string{"coffee"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + "age": { + Type: schema.TypeInt, + }, + }, + }, + }, + "coffee": { + Type: schema.TypeList, + MaxItems: 1, + ConflictsWith: []string{"cat"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + }, + "is_decaf": { + Type: schema.TypeBool, + }, + }, + }, + }, + }, + }, + }, + } +} + +func testPolyStructExtSchema(t, sdkName string) map[string]*ExtendedSchema { + schemaType := schema.TypeList + maxItems := 0 + if t == "set" { + schemaType = schema.TypeSet + } else if t == "struct" { + maxItems = 1 + } + return map[string]*ExtendedSchema{ + "poly_struct": { + Schema: schema.Schema{ + Type: schemaType, + MaxItems: maxItems, + Elem: &ExtendedResource{ + Schema: map[string]*ExtendedSchema{ + "cat": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + ConflictsWith: []string{"coffee"}, + Elem: &ExtendedResource{ + Schema: map[string]*ExtendedSchema{ + "name": basicStringSchema("Name", false), + "age": basicIntSchema("Age", false), + }, + }, + }, + Metadata: Metadata{ + SchemaType: "struct", + ReflectType: reflect.TypeOf(testCatStruct{}), + BindingType: testCatStructBindingType(), + }, + }, + "coffee": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + ConflictsWith: []string{"cat"}, + Elem: &ExtendedResource{ + Schema: map[string]*ExtendedSchema{ + "name": basicStringSchema("Name", false), + "is_decaf": basicBoolSchema("IsDecaf", false), + }, + }, + }, + Metadata: Metadata{ + SchemaType: "struct", + ReflectType: reflect.TypeOf(testCoffeeStruct{}), + BindingType: testCoffeeStructBindingType(), + }, + }, + }, + }, + }, + Metadata: Metadata{ + SchemaType: t, + SdkFieldName: sdkName, + IsPolymorphic: true, + ResourceTypeMap: map[string]string{ + "cat": "FakeCat", + "coffee": "FakeCoffee", + }, + }, + }, + } +} + +func TestPolyStructToSchema(t *testing.T) { + t.Run("cat struct", func(t *testing.T) { + name := "matcha" + rType := "FakeCat" + age := int64(1) + catObj := testCatStruct{ + Age: &age, + Name: &name, + ResourceType: &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, testPolyStructSchema("struct"), map[string]interface{}{}) + + elem := reflect.ValueOf(&obj).Elem() + err := StructToSchema(elem, d, testPolyStructExtSchema("struct", "PolyStruct"), "", nil) + assert.NoError(t, err, "unexpected error calling StructToSchema") + assert.Len(t, d.Get("poly_struct"), 1) + polyData := d.Get("poly_struct").([]interface{})[0].(map[string]interface{}) + assert.Len(t, polyData["cat"], 1) + assert.Len(t, polyData["coffee"], 0) + assert.Equal(t, map[string]interface{}{ + "name": name, + "age": 1, + }, polyData["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, + ResourceType: &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, testPolyStructSchema("struct"), map[string]interface{}{}) + + elem := reflect.ValueOf(&obj).Elem() + err := StructToSchema(elem, d, testPolyStructExtSchema("struct", "PolyStruct"), "", nil) + assert.NoError(t, err, "unexpected error calling StructToSchema") + assert.Len(t, d.Get("poly_struct"), 1) + polyData := d.Get("poly_struct").([]interface{})[0].(map[string]interface{}) + assert.Len(t, polyData["cat"], 0) + assert.Len(t, polyData["coffee"], 1) + assert.Equal(t, map[string]interface{}{ + "name": name, + "is_decaf": true, + }, polyData["coffee"].([]interface{})[0].(map[string]interface{})) + }) + + t.Run("mixed list", func(t *testing.T) { + catName := "oolong" + coffeeName := "mocha" + catResType := "FakeCat" + coffeeResType := "FakeCoffee" + isDecaf := false + age := int64(2) + catObj := testCatStruct{ + Age: &age, + Name: &catName, + ResourceType: &catResType, + } + coffeeObj := testCoffeeStruct{ + IsDecaf: &isDecaf, + Name: &coffeeName, + ResourceType: &coffeeResType, + } + obj := testPolyListStruct{ + PolyList: make([]*data.StructValue, 2), + } + converter := vapiBindings_.NewTypeConverter() + dv, errors := converter.ConvertToVapi(coffeeObj, testCoffeeStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + obj.PolyList[0] = dv.(*data.StructValue) + dv, errors = converter.ConvertToVapi(catObj, testCatStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + obj.PolyList[1] = dv.(*data.StructValue) + d := schema.TestResourceDataRaw( + t, testPolyStructSchema("struct"), map[string]interface{}{}) + + elem := reflect.ValueOf(&obj).Elem() + err := StructToSchema(elem, d, testPolyStructExtSchema("list", "PolyList"), "", nil) + assert.NoError(t, err, "unexpected error calling StructToSchema") + assert.Len(t, d.Get("poly_struct"), 2) + // idx 0: coffee + coffeeData := d.Get("poly_struct").([]interface{})[0].(map[string]interface{}) + assert.Len(t, coffeeData["cat"], 0) + assert.Len(t, coffeeData["coffee"], 1) + assert.Equal(t, map[string]interface{}{ + "name": coffeeName, + "is_decaf": false, + }, coffeeData["coffee"].([]interface{})[0].(map[string]interface{})) + // idx 1: cat + catData := d.Get("poly_struct").([]interface{})[1].(map[string]interface{}) + assert.Len(t, catData["cat"], 1) + assert.Len(t, catData["coffee"], 0) + assert.Equal(t, map[string]interface{}{ + "name": catName, + "age": 2, + }, catData["cat"].([]interface{})[0].(map[string]interface{})) + }) + + t.Run("mixed set", func(t *testing.T) { + catName := "oolong" + coffeeName := "mocha" + catResType := "FakeCat" + coffeeResType := "FakeCoffee" + isDecaf := false + age := int64(2) + catObj := testCatStruct{ + Age: &age, + Name: &catName, + ResourceType: &catResType, + } + coffeeObj := testCoffeeStruct{ + IsDecaf: &isDecaf, + Name: &coffeeName, + ResourceType: &coffeeResType, + } + obj := testPolyListStruct{ + PolyList: make([]*data.StructValue, 2), + } + converter := vapiBindings_.NewTypeConverter() + dv, errors := converter.ConvertToVapi(coffeeObj, testCoffeeStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + obj.PolyList[1] = dv.(*data.StructValue) + dv, errors = converter.ConvertToVapi(catObj, testCatStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + obj.PolyList[0] = dv.(*data.StructValue) + d := schema.TestResourceDataRaw( + t, testPolyStructSchema("struct"), map[string]interface{}{}) + + elem := reflect.ValueOf(&obj).Elem() + err := StructToSchema(elem, d, testPolyStructExtSchema("set", "PolyList"), "", nil) + assert.NoError(t, err, "unexpected error calling StructToSchema") + assert.Len(t, d.Get("poly_struct"), 2) + for _, v := range d.Get("poly_struct").([]interface{}) { + dataVal := v.(map[string]interface{}) + if len(dataVal["cat"].([]interface{})) > 0 { + assert.Len(t, dataVal["cat"], 1) + assert.Len(t, dataVal["coffee"], 0) + assert.Equal(t, map[string]interface{}{ + "name": catName, + "age": 2, + }, dataVal["cat"].([]interface{})[0].(map[string]interface{})) + } else { + assert.Len(t, dataVal["cat"], 0) + assert.Len(t, dataVal["coffee"], 1) + assert.Equal(t, map[string]interface{}{ + "name": coffeeName, + "is_decaf": false, + }, dataVal["coffee"].([]interface{})[0].(map[string]interface{})) + } + } + }) +} + +func TestSchemaToPolyStruct(t *testing.T) { + t.Run("cat struct", func(t *testing.T) { + d := schema.TestResourceDataRaw( + t, testPolyStructSchema("struct"), map[string]interface{}{ + "poly_struct": []interface{}{ + map[string]interface{}{ + "cat": []interface{}{ + map[string]interface{}{ + "name": "matcha", + "age": 1, + }, + }, + }, + }, + }) + + obj := testPolyStruct{} + elem := reflect.ValueOf(&obj).Elem() + err := SchemaToStruct(elem, d, testPolyStructExtSchema("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).ResourceType) + }) + + t.Run("coffee struct", func(t *testing.T) { + d := schema.TestResourceDataRaw( + t, testPolyStructSchema("struct"), map[string]interface{}{ + "poly_struct": []interface{}{ + map[string]interface{}{ + "coffee": []interface{}{ + map[string]interface{}{ + "name": "latte", + "is_decaf": true, + }, + }, + }, + }, + }) + + obj := testPolyStruct{} + elem := reflect.ValueOf(&obj).Elem() + err := SchemaToStruct(elem, d, testPolyStructExtSchema("struct", "PolyStruct"), "", nil) + assert.NoError(t, err, "unexpected error calling SchemaToStruct") + + converter := vapiBindings_.NewTypeConverter() + obs, errors := converter.ConvertToGolang(obj.PolyStruct, testCoffeeStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + assert.Equal(t, "latte", *obs.(testCoffeeStruct).Name) + assert.Equal(t, true, *obs.(testCoffeeStruct).IsDecaf) + assert.Equal(t, "FakeCoffee", *obs.(testCoffeeStruct).ResourceType) + }) + + t.Run("mixed list", func(t *testing.T) { + d := schema.TestResourceDataRaw( + t, testPolyStructSchema("list"), map[string]interface{}{ + "poly_struct": []interface{}{ + map[string]interface{}{ + "coffee": []interface{}{ + map[string]interface{}{ + "name": "latte", + "is_decaf": true, + }, + }, + }, + map[string]interface{}{ + "cat": []interface{}{ + map[string]interface{}{ + "name": "matcha", + "age": 1, + }, + }, + }, + }, + }) + + obj := testPolyListStruct{} + elem := reflect.ValueOf(&obj).Elem() + err := SchemaToStruct(elem, d, testPolyStructExtSchema("list", "PolyList"), "", nil) + assert.NoError(t, err, "unexpected error calling SchemaToStruct") + + converter := vapiBindings_.NewTypeConverter() + assert.Len(t, obj.PolyList, 2) + coffee, errors := converter.ConvertToGolang(obj.PolyList[0], testCoffeeStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + assert.Equal(t, "latte", *coffee.(testCoffeeStruct).Name) + assert.Equal(t, true, *coffee.(testCoffeeStruct).IsDecaf) + assert.Equal(t, "FakeCoffee", *coffee.(testCoffeeStruct).ResourceType) + cat, errors := converter.ConvertToGolang(obj.PolyList[1], testCatStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + assert.Equal(t, "matcha", *cat.(testCatStruct).Name) + assert.Equal(t, int64(1), *cat.(testCatStruct).Age) + assert.Equal(t, "FakeCat", *cat.(testCatStruct).ResourceType) + }) + + t.Run("mixed set", func(t *testing.T) { + d := schema.TestResourceDataRaw( + t, testPolyStructSchema("set"), map[string]interface{}{ + "poly_struct": []interface{}{ + map[string]interface{}{ + "cat": []interface{}{ + map[string]interface{}{ + "name": "oolong", + "age": 2, + }, + }, + }, + map[string]interface{}{ + "coffee": []interface{}{ + map[string]interface{}{ + "name": "mocha", + "is_decaf": false, + }, + }, + }, + }, + }) + + obj := testPolyListStruct{} + elem := reflect.ValueOf(&obj).Elem() + err := SchemaToStruct(elem, d, testPolyStructExtSchema("set", "PolyList"), "", nil) + assert.NoError(t, err, "unexpected error calling SchemaToStruct") + + converter := vapiBindings_.NewTypeConverter() + assert.Len(t, obj.PolyList, 2) + for _, item := range obj.PolyList { + if item.HasField("age") { + cat, errors := converter.ConvertToGolang(item, testCatStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + assert.Equal(t, "oolong", *cat.(testCatStruct).Name) + assert.Equal(t, int64(2), *cat.(testCatStruct).Age) + assert.Equal(t, "FakeCat", *cat.(testCatStruct).ResourceType) + } else { + coffee, errors := converter.ConvertToGolang(obj.PolyList[0], testCoffeeStructBindingType()) + assert.Nil(t, errors, "unexpected error calling ConvertToGolang") + assert.Equal(t, "mocha", *coffee.(testCoffeeStruct).Name) + assert.Equal(t, false, *coffee.(testCoffeeStruct).IsDecaf) + assert.Equal(t, "FakeCoffee", *coffee.(testCoffeeStruct).ResourceType) + } + } + }) +}