From 15392cbb9c8df91afa360d1a3a1a6a7371946302 Mon Sep 17 00:00:00 2001 From: Shawn Wang Date: Mon, 10 Jun 2024 16:04:43 -0700 Subject: [PATCH] Add support for poly struct in flatten schema Signed-off-by: Shawn Wang --- nsxt/metadata/metadata.go | 95 +++++++++++------- nsxt/metadata/metadata_poly_test.go | 146 +++++++++++++++++++++++++--- 2 files changed, 193 insertions(+), 48 deletions(-) diff --git a/nsxt/metadata/metadata.go b/nsxt/metadata/metadata.go index aa393c64b..e910fe254 100644 --- a/nsxt/metadata/metadata.go +++ b/nsxt/metadata/metadata.go @@ -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 } @@ -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 { @@ -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 @@ -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 { @@ -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 } diff --git a/nsxt/metadata/metadata_poly_test.go b/nsxt/metadata/metadata_poly_test.go index 57989125a..03687d7d1 100644 --- a/nsxt/metadata/metadata_poly_test.go +++ b/nsxt/metadata/metadata_poly_test.go @@ -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": { @@ -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": { @@ -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), @@ -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, @@ -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), @@ -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, @@ -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" @@ -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) @@ -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", @@ -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()