From 46f5495b35e5b92abe41e7fd5a4fa9c98c8d59ed Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Mon, 12 May 2025 11:50:20 +0200 Subject: [PATCH 01/12] [ignore] simplify the test name printing for test initalisation --- gen/utils/data/class_test.go | 6 +++--- gen/utils/data/data_test.go | 2 +- gen/utils/logger/logger_test.go | 2 +- gen/utils/test/test.go | 16 +++------------- gen/utils/utils_test.go | 4 ++-- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index 265fd4979..8e04bfa3b 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -15,7 +15,7 @@ const ( ) func TestSplitClassNameToPackageNameAndShortNameSingle(t *testing.T) { - test.InitializeTest(t, 0) + test.InitializeTest(t) packageName, shortName, err := splitClassNameToPackageNameAndShortName(constTestClassNameSingleWordInShortName) assert.Equal(t, packageName, "fv", fmt.Sprintf("Expected package name to be 'fv', but got '%s'", packageName)) assert.Equal(t, shortName, "Tenant", fmt.Sprintf("Expected short name to be 'Tenant', but got '%s'", shortName)) @@ -23,7 +23,7 @@ func TestSplitClassNameToPackageNameAndShortNameSingle(t *testing.T) { } func TestSplitClassNameToPackageNameAndShortNameMultiple(t *testing.T) { - test.InitializeTest(t, 0) + test.InitializeTest(t) packageName, shortName, err := splitClassNameToPackageNameAndShortName(constTestClassNameMultipleWordsInShortName) assert.Equal(t, packageName, "fv", fmt.Sprintf("Expected package name to be 'fv', but got '%s'", packageName)) assert.Equal(t, shortName, "RsIpslaMonPol", fmt.Sprintf("Expected short name to be 'RsIpslaMonPol', but got '%s'", shortName)) @@ -31,7 +31,7 @@ func TestSplitClassNameToPackageNameAndShortNameMultiple(t *testing.T) { } func TestSplitClassNameToPackageNameAndShortNameError(t *testing.T) { - test.InitializeTest(t, 0) + test.InitializeTest(t) packageName, shortName, err := splitClassNameToPackageNameAndShortName(constTestClassNameErrorInShortName) assert.Equal(t, packageName, "", fmt.Sprintf("Expected package name to be '', but got '%s'", packageName)) assert.Equal(t, shortName, "", fmt.Sprintf("Expected short name to be '', but got '%s'", shortName)) diff --git a/gen/utils/data/data_test.go b/gen/utils/data/data_test.go index 228913083..64826fc25 100644 --- a/gen/utils/data/data_test.go +++ b/gen/utils/data/data_test.go @@ -12,7 +12,7 @@ const ( ) func initializeDataStoreTest(t *testing.T) *DataStore { - test.InitializeTest(t, 1) + test.InitializeTest(t) return &DataStore{} } diff --git a/gen/utils/logger/logger_test.go b/gen/utils/logger/logger_test.go index 653d1b663..95b0fb6e1 100644 --- a/gen/utils/logger/logger_test.go +++ b/gen/utils/logger/logger_test.go @@ -14,7 +14,7 @@ const ( ) func initializeLogTest(t *testing.T) *Logger { - test.InitializeTest(t, 1) + test.InitializeTest(t) genLogger := InitializeLogger() if genLogger == nil { diff --git a/gen/utils/test/test.go b/gen/utils/test/test.go index 28b6027f0..0395890e0 100644 --- a/gen/utils/test/test.go +++ b/gen/utils/test/test.go @@ -1,20 +1,10 @@ package test import ( - "runtime" - "strings" + "fmt" "testing" ) -func InitializeTest(t *testing.T, functionCounter int) { - println("Executing:", GetTestName(t, functionCounter+1)) -} - -func GetTestName(t *testing.T, functionCounter int) string { - counter, _, _, success := runtime.Caller(functionCounter + 1) - if !success { - t.Fatalf("Failed to get caller information: %v", success) - } - splittedName := strings.Split(runtime.FuncForPC(counter).Name(), ".") - return splittedName[len(splittedName)-1] +func InitializeTest(t *testing.T) { + println(fmt.Sprintf("Executing: %s", t.Name())) } diff --git a/gen/utils/utils_test.go b/gen/utils/utils_test.go index b09959153..e5ba7f277 100644 --- a/gen/utils/utils_test.go +++ b/gen/utils/utils_test.go @@ -19,7 +19,7 @@ const ( ) func TestGetFileNamesFromDirectoryWithExtension(t *testing.T) { - test.InitializeTest(t, 0) + test.InitializeTest(t) filenames := GetFileNamesFromDirectory(constTestDirectoryForGetFileNamesFromDirectory, false) assert.NotEmpty(t, filenames, "Expected to get file names from directory, but got empty list") assert.Equal(t, len(filenames), 3, fmt.Sprintf("Expected to get 2 file names from directory, but got %d", len(filenames))) @@ -30,7 +30,7 @@ func TestGetFileNamesFromDirectoryWithExtension(t *testing.T) { } func TestGetFileNamesFromDirectoryWithoutExtension(t *testing.T) { - test.InitializeTest(t, 0) + test.InitializeTest(t) filenames := GetFileNamesFromDirectory(constTestDirectoryForGetFileNamesFromDirectory, true) assert.NotEmpty(t, filenames, "Expected to get file names from directory, but got empty list") assert.Equal(t, len(filenames), 3, fmt.Sprintf("Expected to get 2 file names from directory, but got %d", len(filenames))) From 903196377468abad8f9eddc821e14eec68403243 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Mon, 12 May 2025 11:52:24 +0200 Subject: [PATCH 02/12] [ignore] add function for replacing capital letters and spaces with underscores --- gen/utils/utils.go | 10 ++++++++++ gen/utils/utils_test.go | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/gen/utils/utils.go b/gen/utils/utils.go index d33f0ddc1..4cac03337 100644 --- a/gen/utils/utils.go +++ b/gen/utils/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" "github.com/CiscoDevNet/terraform-provider-aci/v2/gen/utils/logger" @@ -33,3 +34,12 @@ func GetFileNamesFromDirectory(path string, removeExtension bool) []string { genLogger.Debug(fmt.Sprintf("The directory '%s' contains the file names: %s.", path, names)) return names } + +// Reused from https://github.com/buxizhizhoum/inflection/blob/master/inflection.go#L8 to avoid importing the whole package +func Underscore(s string) string { + for _, reStr := range []string{`([A-Z]+)([A-Z][a-z])`, `([a-z\d])([A-Z])`} { + re := regexp.MustCompile(reStr) + s = re.ReplaceAllString(s, "${1}_${2}") + } + return strings.ReplaceAll(strings.ToLower(s), " ", "_") +} diff --git a/gen/utils/utils_test.go b/gen/utils/utils_test.go index e5ba7f277..7a94b39f0 100644 --- a/gen/utils/utils_test.go +++ b/gen/utils/utils_test.go @@ -39,3 +39,22 @@ func TestGetFileNamesFromDirectoryWithoutExtension(t *testing.T) { assert.Contains(t, filenames, constTestFile3, fmt.Sprintf("Expected to find file name '%s' in the list, but it was not found", constTestFile3)) assert.NotContains(t, filenames, constTestDir1, fmt.Sprintf("Expected to not find directory name '%s' in the list, but it was found", constTestDir1)) } + +func TestUnderscore(t *testing.T) { + test.InitializeTest(t) + + tests := []map[string]string{ + {"input": "tenant", "expected": "tenant"}, + {"input": "Tenant", "expected": "tenant"}, + {"input": "Tenant1", "expected": "tenant1"}, + {"input": "ApplicationEndpointGroup", "expected": "application_endpoint_group"}, + {"input": "Application Endpoint Group", "expected": "application_endpoint_group"}, + } + + for _, test := range tests { + genLogger.Info(fmt.Sprintf("Executing: %s' with input '%s' and expected output '%s'", t.Name(), test["input"], test["expected"])) + result := Underscore(test["input"]) + assert.Equal(t, test["expected"], result, fmt.Sprintf("Expected '%s', but got '%s'", test["expected"], result)) + } + +} From a31cba7838a9baba1cd5ee1e49d7c40d6196bea2 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Mon, 12 May 2025 11:53:18 +0200 Subject: [PATCH 03/12] [ignore] add function to set the resource name in the class struct --- gen/utils/data/class.go | 19 ++++++++++++++++--- gen/utils/data/class_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index bff13522a..0d82585d9 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -6,6 +6,7 @@ import ( "os" "unicode" + "github.com/CiscoDevNet/terraform-provider-aci/v2/gen/utils" "github.com/CiscoDevNet/terraform-provider-aci/v2/internal/provider" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -178,7 +179,10 @@ func (c *Class) loadMetaFile() error { func (c *Class) setClassData() error { genLogger.Debug(fmt.Sprintf("Setting class data for class '%s'.", c.ClassName)) - c.setResourceName() + err := c.setResourceName() + if err != nil { + return err + } // TODO: add functions to set the other class data @@ -190,8 +194,17 @@ func (c *Class) setClassData() error { return nil } -func (c *Class) setResourceName() { - genLogger.Trace(fmt.Sprintf("Implement setting resourceName for class '%s'.", c.ClassName)) +func (c *Class) setResourceName() error { + genLogger.Debug(fmt.Sprintf("Setting resource name for class '%s'.", c.ClassName)) + + if label, ok := c.MetaFileContent["label"]; ok && label != "" { + c.ResourceName = utils.Underscore(label.(string)) + } else { + return fmt.Errorf("failed to set resource name for class '%s': label not found", c.ClassName) + } + + genLogger.Debug(fmt.Sprintf("Successfully set resource name '%s' for class '%s'.", c.ResourceName, c.ClassName)) + return nil } func (c *Class) setProperties(properties map[string]interface{}) { diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index 8e04bfa3b..ccec4d2ad 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -12,6 +12,7 @@ const ( constTestClassNameSingleWordInShortName = "fvTenant" constTestClassNameMultipleWordsInShortName = "fvRsIpslaMonPol" constTestClassNameErrorInShortName = "error" + constTestMetaFileContentForLabel = "tenant" ) func TestSplitClassNameToPackageNameAndShortNameSingle(t *testing.T) { @@ -37,3 +38,28 @@ func TestSplitClassNameToPackageNameAndShortNameError(t *testing.T) { assert.Equal(t, shortName, "", fmt.Sprintf("Expected short name to be '', but got '%s'", shortName)) assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) } + +func TestSetResourceNameFromLabel(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: constTestClassNameSingleWordInShortName} + class.MetaFileContent = map[string]interface{}{"label": constTestMetaFileContentForLabel} + err := class.setResourceName() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.ResourceName, constTestMetaFileContentForLabel, fmt.Sprintf("Expected resource name to be 'tenant', but got '%s'", class.ResourceName)) +} + +func TestSetResourceNameFromEmptyLabelError(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: constTestClassNameSingleWordInShortName} + class.MetaFileContent = map[string]interface{}{"label": ""} + err := class.setResourceName() + assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) +} + +func TestSetResourceNameFromNoLabelError(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: constTestClassNameSingleWordInShortName} + class.MetaFileContent = map[string]interface{}{"no_label": ""} + err := class.setResourceName() + assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) +} From 9012df2319dfb9ad93c3d4d6ad7126c2bdbaa1a6 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Mon, 12 May 2025 12:30:07 +0200 Subject: [PATCH 04/12] [ignore] add TODO comments for functions required to set class and property information from meta --- gen/utils/data/class.go | 38 ++++++++++++++++++++++++++++++++++---- gen/utils/data/property.go | 32 +++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index 0d82585d9..72d7cb456 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -28,6 +28,8 @@ type Class struct { Children []Child // List of all possible parent classes. ContainedBy []string + // Deprecated resources include a warning the resource and datasource schemas. + Deprecated bool // The APIC versions in which the class is deprecated. DeprecatedVersions []VersionRange // Documentation specific information for the class. @@ -179,16 +181,44 @@ func (c *Class) loadMetaFile() error { func (c *Class) setClassData() error { genLogger.Debug(fmt.Sprintf("Setting class data for class '%s'.", c.ClassName)) + // TODO: add function to set AllowDelete + + // TODO: add function to set Children + + // TODO: add function to set ContainedBy + + // TODO: add placeholder function for Deprecated + + // TODO: add placeholder function for DeprecatedVersions + + // TODO: add function to set Documentation + + // TODO: add function to set IdentifiedBy + + // TODO: add function to set IsMigration + + // TODO: add function to set IsRelational + + // TODO: add function to set IsSingleNested + + // TODO: add function to set PlatformType + + if properties, ok := c.MetaFileContent["properties"]; ok { + c.setProperties(properties.(map[string]interface{})) + } + + // TODO: add function to set RequiredAsChild + err := c.setResourceName() if err != nil { return err } - // TODO: add functions to set the other class data + // TODO: add function to set ResourceNameNested - if properties, ok := c.MetaFileContent["properties"]; ok { - c.setProperties(properties.(map[string]interface{})) - } + // TODO: add function to set RnFormat + + // TODO: add function to set Versions genLogger.Debug(fmt.Sprintf("Successfully set class data for class '%s'.", c.ClassName)) return nil diff --git a/gen/utils/data/property.go b/gen/utils/data/property.go index f52cd18f8..9e4973d85 100644 --- a/gen/utils/data/property.go +++ b/gen/utils/data/property.go @@ -126,7 +126,37 @@ func NewProperty(name string, details map[string]interface{}) *Property { genLogger.Trace(fmt.Sprintf("Creating new property struct for property: %s.", name)) property := &Property{PropertyName: name} - // TODO: add functions to set the values of the property. + // TODO: add function to set AttributeName + + // TODO: add function to set Computed + + // TODO: add function to set CustomType + + // TODO: add placeholder function for Deprecated + + // TODO: add placeholder function for DeprecatedVersions + + // TODO: add function to set Documentation + + // TODO: add function to set MigrationValues + + // TODO: add function to set Optional + + // TODO: add function to set PointsToClass + + // TODO: add function to set ReadOnly + + // TODO: add function to set Required + + // TODO: add function to set TestValues + + // TODO: add function to set Validators + + // TODO: add function to set ValidValues + + // TODO: add function to set ValueType + + // TODO: add function to set Versions return property } From 9b7d3ab329d0ac7dbdbae7c16bedca4c25e0c463 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Tue, 13 May 2025 09:35:11 +0200 Subject: [PATCH 05/12] [ignore] Add function in utils to create plural forms of words --- gen/utils/utils.go | 14 ++++++++++++++ gen/utils/utils_test.go | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/gen/utils/utils.go b/gen/utils/utils.go index 4cac03337..6929335c3 100644 --- a/gen/utils/utils.go +++ b/gen/utils/utils.go @@ -35,6 +35,20 @@ func GetFileNamesFromDirectory(path string, removeExtension bool) []string { return names } +// A simplified version of a function to create plural forms for words. +// This function is not comprehensive and is not intended to cover all pluralization rules in English. +// It is intended for basic use cases and does not handle irregular plurals like "mouse" -> "mice". +// If we need a more robust pluralization solution, we should consider using a external package or expanding the logic. +func Plural(notPlural string) (string, error) { + if strings.HasSuffix(notPlural, "y") { + return fmt.Sprintf("%s%s", strings.TrimSuffix(notPlural, "y"), "ies"), nil + } else if !strings.HasSuffix(notPlural, "s") { + return fmt.Sprintf("%ss", notPlural), nil + } + genLogger.Error(fmt.Sprintf("The word '%s' has no plural rule defined.", notPlural)) + return "", fmt.Errorf("the word '%s' has no plural rule defined", notPlural) +} + // Reused from https://github.com/buxizhizhoum/inflection/blob/master/inflection.go#L8 to avoid importing the whole package func Underscore(s string) string { for _, reStr := range []string{`([A-Z]+)([A-Z][a-z])`, `([a-z\d])([A-Z])`} { diff --git a/gen/utils/utils_test.go b/gen/utils/utils_test.go index 7a94b39f0..87b89bf42 100644 --- a/gen/utils/utils_test.go +++ b/gen/utils/utils_test.go @@ -58,3 +58,25 @@ func TestUnderscore(t *testing.T) { } } + +func TestPlural(t *testing.T) { + test.InitializeTest(t) + + tests := []map[string]string{ + {"input": "monitor_policy", "expected": "monitor_policies"}, + {"input": "annotation", "expected": "annotations"}, + } + + for _, test := range tests { + genLogger.Info(fmt.Sprintf("Executing: %s' with input '%s' and expected output '%s'", t.Name(), test["input"], test["expected"])) + result, err := Plural(test["input"]) + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, test["expected"], result, fmt.Sprintf("Expected '%s', but got '%s'", test["expected"], result)) + } +} + +func TestPluralError(t *testing.T) { + test.InitializeTest(t) + _, err := Plural("contracts") + assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) +} From ba20e14414d6ad1c0c6a9444ada85a3b0c6b04ac Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Tue, 13 May 2025 09:38:20 +0200 Subject: [PATCH 06/12] [ignore] add function to set the nested resource name for children in the class struct --- gen/utils/data/class.go | 35 ++++++++++++++++++++++++++++++++++- gen/utils/data/class_test.go | 22 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index 72d7cb456..1c0041bc3 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "regexp" "unicode" "github.com/CiscoDevNet/terraform-provider-aci/v2/gen/utils" @@ -214,7 +215,10 @@ func (c *Class) setClassData() error { return err } - // TODO: add function to set ResourceNameNested + err = c.setResourceNameNested() + if err != nil { + return err + } // TODO: add function to set RnFormat @@ -227,6 +231,7 @@ func (c *Class) setClassData() error { func (c *Class) setResourceName() error { genLogger.Debug(fmt.Sprintf("Setting resource name for class '%s'.", c.ClassName)) + // TODO: add logic to override the resource name from an definition file. if label, ok := c.MetaFileContent["label"]; ok && label != "" { c.ResourceName = utils.Underscore(label.(string)) } else { @@ -237,6 +242,34 @@ func (c *Class) setResourceName() error { return nil } +func (c *Class) setResourceNameNested() error { + genLogger.Debug(fmt.Sprintf("Setting resource name when used as nested attribute for class '%s'.", c.ClassName)) + + // The assumption is made that when a resource name has no identifying properties, there will be only one configurable item. + // This means that the resource name will not be in plural form when exposed as an nested attribute in it's parent. + if len(c.IdentifiedBy) == 0 { + c.ResourceNameNested = c.ResourceName + } else { + // ex. 'relation_to_consumed_contract' would be pluralized to 'relation_to_consumed_contracts'. + // ex. 'relation_from_bridge_domain_to_netflow_monitor_policy' would be pluralized to 'relation_from_bridge_domain_to_netflow_monitor_policies'. + pluralForm, err := utils.Plural(c.ResourceName) + if err != nil { + return err + } + c.ResourceNameNested = pluralForm + } + + // For relational class nested attributes the name is changed to 'to_resource_name' when the resource name includes 'from_resource_name'. + // ex. 'relation_from_bridge_domain_to_netflow_monitor_policy' would translate to 'relation_to_netflow_monitor_policy'. + m := regexp.MustCompile("from_(.*)_to_") + if m.MatchString(c.ResourceNameNested) { + c.ResourceNameNested = m.ReplaceAllString(c.ResourceNameNested, "to_") + } + + genLogger.Debug(fmt.Sprintf("Successfully set resource name when used as nested attribute '%s' for class '%s'.", c.ResourceNameNested, c.ClassName)) + return nil +} + func (c *Class) setProperties(properties map[string]interface{}) { genLogger.Debug(fmt.Sprintf("Setting properties for class '%s'.", c.ClassName)) for name, propertyDetails := range properties { diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index ccec4d2ad..686ad94a9 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -63,3 +63,25 @@ func TestSetResourceNameFromNoLabelError(t *testing.T) { err := class.setResourceName() assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) } + +func TestSetResourceNameNested(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: constTestClassNameSingleWordInShortName} + + tests := []map[string]interface{}{ + {"resource_name": "relation_from_bridge_domain_to_netflow_monitor_policy", "identifiers": []string{"tnNetflowMonitorPolName", "fltType"}, "expected": "relation_to_netflow_monitor_policies"}, + {"resource_name": "annotation", "identifiers": []string{"key"}, "expected": "annotations"}, + {"resource_name": "relation_to_consumed_contract", "identifiers": []string{"tnVzBrCPName"}, "expected": "relation_to_consumed_contracts"}, + {"resource_name": "associated_site", "identifiers": []string{}, "expected": "associated_site"}, + {"resource_name": "relation_from_netflow_exporter_to_vrf", "identifiers": []string{}, "expected": "relation_to_vrf"}, + } + + for _, test := range tests { + genLogger.Info(fmt.Sprintf("Executing: %s' with input '%s' and expected output '%s'", t.Name(), test["resource_name"], test["expected"])) + class.ResourceName = test["resource_name"].(string) + class.IdentifiedBy = test["identifiers"].([]string) + class.setResourceNameNested() + assert.Equal(t, test["expected"], class.ResourceNameNested, fmt.Sprintf("Expected '%s', but got '%s'", test["expected"], class.ResourceNameNested)) + } + +} From 5d992002b74e804d7efdc343188735d9d3f02b0d Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Wed, 14 May 2025 18:40:55 +0200 Subject: [PATCH 07/12] [ignore] change article of noun in inline comment --- gen/utils/data/class.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index 1c0041bc3..7d6200771 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -231,7 +231,7 @@ func (c *Class) setClassData() error { func (c *Class) setResourceName() error { genLogger.Debug(fmt.Sprintf("Setting resource name for class '%s'.", c.ClassName)) - // TODO: add logic to override the resource name from an definition file. + // TODO: add logic to override the resource name from a definition file. if label, ok := c.MetaFileContent["label"]; ok && label != "" { c.ResourceName = utils.Underscore(label.(string)) } else { From f7a64e40122ce014b3dd71889cadd2998752aaa9 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Wed, 21 May 2025 22:03:04 +0200 Subject: [PATCH 08/12] [ignore] change article of noun in inline comment --- gen/utils/data/class.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index 7d6200771..36cc5aa0d 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -246,7 +246,7 @@ func (c *Class) setResourceNameNested() error { genLogger.Debug(fmt.Sprintf("Setting resource name when used as nested attribute for class '%s'.", c.ClassName)) // The assumption is made that when a resource name has no identifying properties, there will be only one configurable item. - // This means that the resource name will not be in plural form when exposed as an nested attribute in it's parent. + // This means that the resource name will not be in plural form when exposed as a nested attribute in its parent. if len(c.IdentifiedBy) == 0 { c.ResourceNameNested = c.ResourceName } else { From 65415024a011ae5b85f79a1393eaf7c2c0fba4e4 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Tue, 3 Jun 2025 14:08:26 +0200 Subject: [PATCH 09/12] [ignore] add logic for reading a global definition file and adjust resource name setting for relationships --- gen/definitions/global.yaml | 23 +++ gen/utils/data/class.go | 297 ++++++++++++++++++++++++++-------- gen/utils/data/class_test.go | 179 +++++++++++++++----- gen/utils/data/constants.go | 4 + gen/utils/data/data.go | 37 ++++- gen/utils/data/data_test.go | 7 + gen/utils/data/definitions.go | 28 ++++ gen/utils/utils.go | 4 +- gen/utils/utils_test.go | 2 + go.mod | 2 +- 10 files changed, 472 insertions(+), 111 deletions(-) create mode 100644 gen/definitions/global.yaml create mode 100644 gen/utils/data/definitions.go diff --git a/gen/definitions/global.yaml b/gen/definitions/global.yaml new file mode 100644 index 000000000..d0655289d --- /dev/null +++ b/gen/definitions/global.yaml @@ -0,0 +1,23 @@ +nometafile: + bgpCtxAfPol: bgp_address_family_context + bgpCtxPol: bgp_timers + dhcpRelayP: dhcp_relay_policy + extdevSDWanVpnEntry: wan_vpn + fabricNode: fabric_node + fabricPathEp: fabric_path_endpoint + fvABDPol: bridge_domain + fvEPg: epg + fvEpRetPol: endpoint_retention_policy + fvIPSLAMonitoringPol: ip_sla_monitoring_policy + infraAccBaseGrp: infra_access_base_group + infraDomP: infra_domain_policy + l3extAConsLbl: l3out_consumer_label + l3extInstP: external_network_instance_profile + l3extOut: l3_outside + l3extRouteTagPol: route_tag_policy + monEPGPol: monitoring_policy + netflowAExporterPol: netflow_exporter_policy + ospfCtxPol: ospf_timers + vzBrCP: contract + vzCPIf: imported_contract + vzFilter: filter diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index 36cc5aa0d..3877fc20c 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -4,7 +4,8 @@ import ( "encoding/json" "fmt" "os" - "regexp" + "slices" + "strings" "unicode" "github.com/CiscoDevNet/terraform-provider-aci/v2/gen/utils" @@ -26,7 +27,7 @@ type Class struct { ClassNameShort string // List of all child classes which are included inside the resource. // When looping over maps in golang the order of the returned elements is random, thus list is used for order consistency. - Children []Child + Children []string // List of all possible parent classes. ContainedBy []string // Deprecated resources include a warning the resource and datasource schemas. @@ -41,8 +42,6 @@ type Class struct { // Indicates that the class is migrated from previous version of the provider. // This is used to determine if legacy attributes have to be exposed in the resource. IsMigration bool - // Indicates that the class is a relationship class. - IsRelational bool // Indicates that when the class is included in a resource as a child it can only be configured once. // This is used to determine the type of the nested attribute to be a map or list. IsSingleNested bool @@ -59,6 +58,9 @@ type Class struct { // The full content from the meta file. // Storing the content proactively in case we need to access the data at a later stage. MetaFileContent map[string]interface{} + // The relationship information of the class. + // If the class is a relationship class, it will contain the information about the relationship. + Relation Relation // Indicates if the class is required when defined as a child in a parent resource. RequiredAsChild bool // The resource name is the name of the resource in the provider, ex "aci_tenant". @@ -87,12 +89,41 @@ const ( Cloud ) -type Child struct { - // The name of the child class, ex "fvTenant". - // This is used as the key for the map of all classes. - ClassName string - // When it is a relationship class, this is the class to which the relationship points. - PointsToClass string +type Relation struct { + // The class from which the relationship is defined. + FromClass string + // Indicates if _from_ should be included in the resource name. + // ex. "fvRsBdToOut" would be "data_source_aci_relation_from_bridge_domain_to_l3_outside". + IncludeFrom bool + // Indicates if the class is a relational class. + RelationalClass bool + // The class to which the relationship points. + ToClass string + // The type of the relationship. + Type RelationShipTypeEnum +} + +// The enumeration options of the relationship type. +type RelationShipTypeEnum int + +const ( + // Named indicates that the relationship is a named relation. + Named RelationShipTypeEnum = iota + 1 + // Explicit indicates that the relationship is an explicit relation. + Explicit + // Undefined indicates that the relationship type is unknown. + Undefined RelationShipTypeEnum = iota +) + +func setRelationShipTypeEnum(relationType string) (RelationShipTypeEnum, error) { + switch relationType { + case "named": + return Named, nil + case "explicit": + return Explicit, nil + default: + return Undefined, fmt.Errorf("undefined relationship type '%s'", relationType) + } } type ClassDocumentation struct { @@ -121,7 +152,7 @@ type VersionRange struct { Min provider.Version } -func NewClass(className string) (*Class, error) { +func NewClass(className string, ds *DataStore) (*Class, error) { genLogger.Trace(fmt.Sprintf("Creating new class struct with class name: %s.", className)) // Splitting the class name into the package and short name. packageName, shortName, err := splitClassNameToPackageNameAndShortName(className) @@ -144,7 +175,7 @@ func NewClass(className string) (*Class, error) { return nil, err } - err = class.setClassData() + err = class.setClassData(ds) if err != nil { return nil, err } @@ -157,8 +188,7 @@ func (c *Class) loadMetaFile() error { fileContent, err := os.ReadFile(fmt.Sprintf("%s/%s.json", constMetaPath, c.ClassName)) if err != nil { - genLogger.Error(fmt.Sprintf("Error during loading of meta file: %s", err.Error())) - return err + return fmt.Errorf("failed to load meta file for class '%s': %s", c.ClassName, err.Error()) } genLogger.Trace(fmt.Sprintf("Parsing meta file for class '%s'.", c.ClassName)) @@ -168,8 +198,7 @@ func (c *Class) loadMetaFile() error { var metaFileContent map[string]interface{} err = json.Unmarshal(fileContent, &metaFileContent) if err != nil { - genLogger.Error(fmt.Sprintf("Error during parsing of meta file: %s", err.Error())) - return err + return fmt.Errorf("failed to parse meta file for class '%s': %s", c.ClassName, err.Error()) } c.MetaFileContent = metaFileContent[fmt.Sprintf("%s:%s", c.ClassNamePackage, c.ClassNameShort)].(map[string]interface{}) @@ -179,111 +208,231 @@ func (c *Class) loadMetaFile() error { return nil } -func (c *Class) setClassData() error { +func (c *Class) setClassData(ds *DataStore) error { genLogger.Debug(fmt.Sprintf("Setting class data for class '%s'.", c.ClassName)) + var err error + // TODO: add function to set AllowDelete + c.setAllowDelete() // TODO: add function to set Children + c.setChildren() // TODO: add function to set ContainedBy + c.setContainedBy() // TODO: add placeholder function for Deprecated + c.setDeprecated() // TODO: add placeholder function for DeprecatedVersions + c.setDeprecatedVersions() // TODO: add function to set Documentation + c.setDocumentation() // TODO: add function to set IdentifiedBy + c.setIdentifiedBy() // TODO: add function to set IsMigration + c.setIsMigration() - // TODO: add function to set IsRelational + err = c.setRelation() + if err != nil { + return err + } // TODO: add function to set IsSingleNested + c.setIsSingleNested() // TODO: add function to set PlatformType + c.setPlatformType() - if properties, ok := c.MetaFileContent["properties"]; ok { - c.setProperties(properties.(map[string]interface{})) - } + // TODO: add function to set Properties + c.setProperties() // TODO: add function to set RequiredAsChild + c.setRequiredAsChild() - err := c.setResourceName() - if err != nil { - return err - } - - err = c.setResourceNameNested() + err = c.setResourceName(ds) if err != nil { return err } // TODO: add function to set RnFormat + c.setRnFormat() // TODO: add function to set Versions + c.setVersions() genLogger.Debug(fmt.Sprintf("Successfully set class data for class '%s'.", c.ClassName)) return nil } -func (c *Class) setResourceName() error { - genLogger.Debug(fmt.Sprintf("Setting resource name for class '%s'.", c.ClassName)) +func (c *Class) setAllowDelete() { + // Determine if the class can be deleted. + genLogger.Debug(fmt.Sprintf("Setting AllowDelete for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set AllowDelete for class '%s'.", c.ClassName)) +} - // TODO: add logic to override the resource name from a definition file. - if label, ok := c.MetaFileContent["label"]; ok && label != "" { - c.ResourceName = utils.Underscore(label.(string)) - } else { - return fmt.Errorf("failed to set resource name for class '%s': label not found", c.ClassName) +func (c *Class) setChildren() { + // Determine the child classes for the class. + genLogger.Debug(fmt.Sprintf("Setting Children for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set Children for class '%s'.", c.ClassName)) +} + +func (c *Class) setContainedBy() { + // Determine the parent classes for the class. + genLogger.Debug(fmt.Sprintf("Setting ContainedBy for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set ContainedBy for class '%s'.", c.ClassName)) +} + +func (c *Class) setDeprecated() { + // Determine if the class is deprecated. + genLogger.Debug(fmt.Sprintf("Setting Deprecated for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set Deprecated for class '%s'.", c.ClassName)) +} + +func (c *Class) setDeprecatedVersions() { + // Determine the APIC versions in which the class is deprecated. + genLogger.Debug(fmt.Sprintf("Setting DeprecatedVersions for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set DeprecatedVersions for class '%s'.", c.ClassName)) +} + +func (c *Class) setDocumentation() { + // Determine the documentation specific information for the class. + genLogger.Debug(fmt.Sprintf("Setting Documentation for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set Documentation for class '%s'.", c.ClassName)) +} + +func (c *Class) setIdentifiedBy() { + // Determine the identifying properties of the class. + genLogger.Debug(fmt.Sprintf("Setting IdentifiedBy for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set IdentifiedBy for class '%s'.", c.ClassName)) +} + +func (c *Class) setIsMigration() { + // Determine if the class is migrated from previous version of the provider. + genLogger.Debug(fmt.Sprintf("Setting IsMigration for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set IsMigration for class '%s'.", c.ClassName)) +} + +func (c *Class) setIsSingleNested() { + // Determine if the class can only be configured once when used as a nested attribute in a parent resource. + genLogger.Debug(fmt.Sprintf("Setting IsSingleNested for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set IsSingleNested for class '%s'.", c.ClassName)) +} + +func (c *Class) setPlatformType() { + // Determine the platform type of the class. + genLogger.Debug(fmt.Sprintf("Setting PlatformType for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set PlatformType for class '%s'.", c.ClassName)) +} + +func (c *Class) setProperties() { + genLogger.Debug(fmt.Sprintf("Setting properties for class '%s'.", c.ClassName)) + + if properties, ok := c.MetaFileContent["properties"]; ok { + for name, propertyDetails := range properties.(map[string]interface{}) { + details := propertyDetails.(map[string]interface{}) + // TODO: add logic to set the property data based on ignore/include/exclude overwrites (read-only) from definition files. + if details["isConfigurable"] == true { + c.Properties[name] = NewProperty(name, details) + c.PropertiesAll = append(c.PropertiesAll, name) + // TODO: add logic to set the required/optional/read-only list logic + } + } } - genLogger.Debug(fmt.Sprintf("Successfully set resource name '%s' for class '%s'.", c.ResourceName, c.ClassName)) - return nil + genLogger.Debug(fmt.Sprintf("Successfully set properties for class '%s'.", c.ClassName)) + // TODO: add sorting logic for the properties } -func (c *Class) setResourceNameNested() error { - genLogger.Debug(fmt.Sprintf("Setting resource name when used as nested attribute for class '%s'.", c.ClassName)) +func (c *Class) setRelation() error { + // Determine if the class is a relational class. + genLogger.Debug(fmt.Sprintf("Setting Relation details for class '%s'.", c.ClassName)) - // The assumption is made that when a resource name has no identifying properties, there will be only one configurable item. - // This means that the resource name will not be in plural form when exposed as a nested attribute in its parent. - if len(c.IdentifiedBy) == 0 { - c.ResourceNameNested = c.ResourceName - } else { - // ex. 'relation_to_consumed_contract' would be pluralized to 'relation_to_consumed_contracts'. - // ex. 'relation_from_bridge_domain_to_netflow_monitor_policy' would be pluralized to 'relation_from_bridge_domain_to_netflow_monitor_policies'. - pluralForm, err := utils.Plural(c.ResourceName) + // TODO: add logic to override the relational status from a definition file. + if relationInfo, ok := c.MetaFileContent["relationInfo"]; ok { + relationType, err := setRelationShipTypeEnum(relationInfo.(map[string]interface{})["type"].(string)) if err != nil { return err } - c.ResourceNameNested = pluralForm - } - // For relational class nested attributes the name is changed to 'to_resource_name' when the resource name includes 'from_resource_name'. - // ex. 'relation_from_bridge_domain_to_netflow_monitor_policy' would translate to 'relation_to_netflow_monitor_policy'. - m := regexp.MustCompile("from_(.*)_to_") - if m.MatchString(c.ResourceNameNested) { - c.ResourceNameNested = m.ReplaceAllString(c.ResourceNameNested, "to_") + c.Relation.FromClass = strings.Replace(relationInfo.(map[string]interface{})["fromMo"].(string), ":", "", -1) + if strings.Contains(c.ClassName, "To") { + c.Relation.IncludeFrom = true + } + c.Relation.RelationalClass = true + c.Relation.ToClass = strings.Replace(relationInfo.(map[string]interface{})["toMo"].(string), ":", "", -1) + c.Relation.Type = RelationShipTypeEnum(relationType) + } + genLogger.Debug(fmt.Sprintf("Successfully set Relation details for class '%s'.", c.ClassName)) - genLogger.Debug(fmt.Sprintf("Successfully set resource name when used as nested attribute '%s' for class '%s'.", c.ResourceNameNested, c.ClassName)) return nil } -func (c *Class) setProperties(properties map[string]interface{}) { - genLogger.Debug(fmt.Sprintf("Setting properties for class '%s'.", c.ClassName)) - for name, propertyDetails := range properties { - details := propertyDetails.(map[string]interface{}) - // TODO: add logic to set the property data based on ignore/include/exclude overwrites (read-only) from definition files. - if details["isConfigurable"] == true { - c.Properties[name] = NewProperty(name, details) - c.PropertiesAll = append(c.PropertiesAll, name) - // TODO: add logic to set the required/optional/read-only list logic +func (c *Class) setRequiredAsChild() { + // Determine if the class is required when defined as a child in a parent resource. + genLogger.Debug(fmt.Sprintf("Setting RequiredAsChild for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set RequiredAsChild for class '%s'.", c.ClassName)) +} + +func (c *Class) setResourceName(ds *DataStore) error { + genLogger.Debug(fmt.Sprintf("Setting resource name for class '%s'.", c.ClassName)) + + // TODO: add logic to override the resource name from a definition file. + // TODO: add logic to override the label from a definition file. + // TODO: add logic to override the class the nested resource name from the parent + // ex. is fvRsSecInherited with fvESg and fvAEPg. + // fvESg: relation_to_end_point_security_groups + // fvAEPg: relation_to_application_epgs + if label, ok := c.MetaFileContent["label"]; ok && label != "" { + if c.Relation.RelationalClass { + // If the relation includes 'To' in the classname, the resource name will be in the format 'relation_from_{from_class}_to_{to_class}'. + // If the relation does not include 'To' in the classname, the resource name will be in the format 'relation_to_{to_class}'. + toClass := getRelationshipResourceName(ds, c.Relation.ToClass) + if c.Relation.IncludeFrom { + // If the class is a relational class and the relation includes 'from', the resource name will be in the format 'relation_from_{from_class}_to_{to_class}'. + c.ResourceName = fmt.Sprintf("relation_from_%s_to_%s", getRelationshipResourceName(ds, c.Relation.FromClass), toClass) + c.ResourceNameNested = fmt.Sprintf("relation_to_%s", toClass) + } else { + // If the class is a relational class and the relation does not include 'from', the resource name will be in the format 'relation_to_{to_class}'. + c.ResourceName = fmt.Sprintf("relation_to_%s", toClass) + c.ResourceNameNested = fmt.Sprintf("relation_to_%s", toClass) + } + } else { + c.ResourceName = utils.Underscore(label.(string)) + c.ResourceNameNested = c.ResourceName } + + // If the class is a relational class and the relation has identifiers, the plural form of the resource name will be set. + if len(c.IdentifiedBy) != 0 { + pluralForm, err := utils.Plural(c.ResourceName) + if err != nil { + return err + } + c.ResourceNameNested = pluralForm + } + } else { + return fmt.Errorf("failed to set resource name for class '%s': label not found", c.ClassName) } - genLogger.Debug(fmt.Sprintf("Successfully set properties for class '%s'.", c.ClassName)) - // TODO: add sorting logic for the properties + genLogger.Debug(fmt.Sprintf("Successfully set resource name '%s' for class '%s'.", c.ResourceName, c.ClassName)) + return nil +} + +func (c *Class) setRnFormat() { + // Determine the relative name (RN) format of the class. + genLogger.Debug(fmt.Sprintf("Setting RnFormat for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set RnFormat for class '%s'.", c.ClassName)) +} + +func (c *Class) setVersions() { + // Determine the supported APIC versions for the class. + genLogger.Debug(fmt.Sprintf("Setting Versions for class '%s'.", c.ClassName)) + genLogger.Debug(fmt.Sprintf("Successfully set Versions for class '%s'.", c.ClassName)) } func splitClassNameToPackageNameAndShortName(className string) (string, string, error) { @@ -309,3 +458,21 @@ func splitClassNameToPackageNameAndShortName(className string) (string, string, return packageName, shortName, nil } + +func getRelationshipResourceName(ds *DataStore, toClass string) string { + err := ds.loadClass(toClass) + if err != nil { + // If the class is not found, try returning the resource name of the class from global definition file. + if resourceName, ok := ds.GlobalMetaDefinition.NoMetaFile[toClass]; ok { + genLogger.Debug(fmt.Sprintf("Failed to load class '%s'. Using resource name from global definition file: %s.", toClass, resourceName)) + return resourceName + } + if !slices.Contains(failedToLoadClasses, toClass) { + // If the class is not found and it is not already in the failed to load classes, add it to the list to be errored. + failedToLoadClasses = append(failedToLoadClasses, toClass) + } + return toClass + } + // If the class is found, return the resource name of the class. + return ds.Classes[toClass].ResourceName +} diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index 686ad94a9..8206d2675 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -8,16 +8,27 @@ import ( "github.com/stretchr/testify/assert" ) -const ( - constTestClassNameSingleWordInShortName = "fvTenant" - constTestClassNameMultipleWordsInShortName = "fvRsIpslaMonPol" - constTestClassNameErrorInShortName = "error" - constTestMetaFileContentForLabel = "tenant" -) +var relationInfoFvRsCons = map[string]interface{}{ + "label": "contract", + "relationInfo": map[string]interface{}{ + "type": "named", + "fromMo": "fv:EPg", + "toMo": "vz:BrCP", + }, +} + +var relationInfoNetflowRsExporterToCtx = map[string]interface{}{ + "label": "netflow exporter", + "relationInfo": map[string]interface{}{ + "type": "explicit", + "fromMo": "netflow:AExporterPol", + "toMo": "fv:Ctx", + }, +} func TestSplitClassNameToPackageNameAndShortNameSingle(t *testing.T) { test.InitializeTest(t) - packageName, shortName, err := splitClassNameToPackageNameAndShortName(constTestClassNameSingleWordInShortName) + packageName, shortName, err := splitClassNameToPackageNameAndShortName("fvTenant") assert.Equal(t, packageName, "fv", fmt.Sprintf("Expected package name to be 'fv', but got '%s'", packageName)) assert.Equal(t, shortName, "Tenant", fmt.Sprintf("Expected short name to be 'Tenant', but got '%s'", shortName)) assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) @@ -25,7 +36,7 @@ func TestSplitClassNameToPackageNameAndShortNameSingle(t *testing.T) { func TestSplitClassNameToPackageNameAndShortNameMultiple(t *testing.T) { test.InitializeTest(t) - packageName, shortName, err := splitClassNameToPackageNameAndShortName(constTestClassNameMultipleWordsInShortName) + packageName, shortName, err := splitClassNameToPackageNameAndShortName("fvRsIpslaMonPol") assert.Equal(t, packageName, "fv", fmt.Sprintf("Expected package name to be 'fv', but got '%s'", packageName)) assert.Equal(t, shortName, "RsIpslaMonPol", fmt.Sprintf("Expected short name to be 'RsIpslaMonPol', but got '%s'", shortName)) assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) @@ -33,55 +44,147 @@ func TestSplitClassNameToPackageNameAndShortNameMultiple(t *testing.T) { func TestSplitClassNameToPackageNameAndShortNameError(t *testing.T) { test.InitializeTest(t) - packageName, shortName, err := splitClassNameToPackageNameAndShortName(constTestClassNameErrorInShortName) + packageName, shortName, err := splitClassNameToPackageNameAndShortName("error") assert.Equal(t, packageName, "", fmt.Sprintf("Expected package name to be '', but got '%s'", packageName)) assert.Equal(t, shortName, "", fmt.Sprintf("Expected short name to be '', but got '%s'", shortName)) assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) } -func TestSetResourceNameFromLabel(t *testing.T) { - test.InitializeTest(t) - class := Class{ClassName: constTestClassNameSingleWordInShortName} - class.MetaFileContent = map[string]interface{}{"label": constTestMetaFileContentForLabel} - err := class.setResourceName() +func TestSetResourceNameFromLabelNoRelationWithIdentifier(t *testing.T) { + ds := initializeDataStoreTest(t) + class := Class{ClassName: "tagAnnotation", IdentifiedBy: []string{"key"}} + class.MetaFileContent = map[string]interface{}{ + "label": "annotation", + } + err := class.setResourceName(ds) + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.ResourceName, "annotation", fmt.Sprintf("Expected resource name to be 'annotation', but got '%s'", class.ResourceName)) + assert.Equal(t, class.ResourceNameNested, "annotations", fmt.Sprintf("Expected nested resource name to be 'annotations', but got '%s'", class.ResourceNameNested)) +} + +func TestSetResourceNameFromLabelNoRelationWithoutIdentifier(t *testing.T) { + ds := initializeDataStoreTest(t) + ds.GlobalMetaDefinition = GlobalMetaDefinition{ + NoMetaFile: map[string]string{ + "fvCtx": "vrf", + }, + } + class := Class{ClassName: "fvRsScope"} + class.MetaFileContent = map[string]interface{}{ + "label": "Private Network", + "relationInfo": map[string]interface{}{ + "type": "named", + "fromMo": "fv:ESg", + "toMo": "fv:Ctx", + }, + } + err := class.setRelation() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + err = class.setResourceName(ds) + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.ResourceName, "relation_to_vrf", fmt.Sprintf("Expected resource name to be 'relation_to_vrf', but got '%s'", class.ResourceName)) + assert.Equal(t, class.ResourceNameNested, "relation_to_vrf", fmt.Sprintf("Expected nested resource name to be 'relation_to_vrf', but got '%s'", class.ResourceNameNested)) +} + +func TestSetResourceNameToRelation(t *testing.T) { + ds := initializeDataStoreTest(t) + ds.GlobalMetaDefinition = GlobalMetaDefinition{ + NoMetaFile: map[string]string{ + "vzBrCP": "contract", + }, + } + class := Class{ClassName: "fvRsCons", IdentifiedBy: []string{"tnVzBrCPName"}} + class.MetaFileContent = relationInfoFvRsCons + err := class.setRelation() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + err = class.setResourceName(ds) + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.ResourceName, "relation_to_contract", fmt.Sprintf("Expected resource name to be 'relation_to_contract', but got '%s'", class.ResourceName)) + assert.Equal(t, class.ResourceNameNested, "relation_to_contracts", fmt.Sprintf("Expected nested resource name to be 'relation_to_contracts', but got '%s'", class.ResourceName)) +} + +func TestSetResourceNameFromToRelation(t *testing.T) { + ds := initializeDataStoreTest(t) + ds.GlobalMetaDefinition = GlobalMetaDefinition{ + NoMetaFile: map[string]string{ + "fvCtx": "vrf", + "netflowAExporterPol": "netflow_exporter_policy", + }, + } + class := Class{ClassName: "netflowRsExporterToCtx"} + class.MetaFileContent = relationInfoNetflowRsExporterToCtx + err := class.setRelation() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + err = class.setResourceName(ds) assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) - assert.Equal(t, class.ResourceName, constTestMetaFileContentForLabel, fmt.Sprintf("Expected resource name to be 'tenant', but got '%s'", class.ResourceName)) + assert.Equal(t, class.ResourceName, "relation_from_netflow_exporter_policy_to_vrf", fmt.Sprintf("Expected resource name to be 'relation_from_netflow_exporter_policy_to_vrf', but got '%s'", class.ResourceName)) + assert.Equal(t, class.ResourceNameNested, "relation_to_vrf", fmt.Sprintf("Expected nested resource name to be 'relation_to_vrf', but got '%s'", class.ResourceNameNested)) } func TestSetResourceNameFromEmptyLabelError(t *testing.T) { - test.InitializeTest(t) - class := Class{ClassName: constTestClassNameSingleWordInShortName} + ds := initializeDataStoreTest(t) + class := Class{} class.MetaFileContent = map[string]interface{}{"label": ""} - err := class.setResourceName() + err := class.setResourceName(ds) assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) } func TestSetResourceNameFromNoLabelError(t *testing.T) { - test.InitializeTest(t) - class := Class{ClassName: constTestClassNameSingleWordInShortName} + ds := initializeDataStoreTest(t) + class := Class{} class.MetaFileContent = map[string]interface{}{"no_label": ""} - err := class.setResourceName() + err := class.setResourceName(ds) assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) } -func TestSetResourceNameNested(t *testing.T) { +func TestSetRelationNoRelation(t *testing.T) { test.InitializeTest(t) - class := Class{ClassName: constTestClassNameSingleWordInShortName} - - tests := []map[string]interface{}{ - {"resource_name": "relation_from_bridge_domain_to_netflow_monitor_policy", "identifiers": []string{"tnNetflowMonitorPolName", "fltType"}, "expected": "relation_to_netflow_monitor_policies"}, - {"resource_name": "annotation", "identifiers": []string{"key"}, "expected": "annotations"}, - {"resource_name": "relation_to_consumed_contract", "identifiers": []string{"tnVzBrCPName"}, "expected": "relation_to_consumed_contracts"}, - {"resource_name": "associated_site", "identifiers": []string{}, "expected": "associated_site"}, - {"resource_name": "relation_from_netflow_exporter_to_vrf", "identifiers": []string{}, "expected": "relation_to_vrf"}, - } + class := Class{ClassName: "fvTenant"} + err := class.setRelation() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.Relation.FromClass, "", fmt.Sprintf("Expected FromClass to be 'fvEPg', but got '%s'", class.Relation.FromClass)) + assert.Equal(t, class.Relation.ToClass, "", fmt.Sprintf("Expected ToClass to be 'vzBrCP', but got '%s'", class.Relation.ToClass)) + assert.Equal(t, class.Relation.Type, RelationShipTypeEnum(0), fmt.Sprintf("Expected Type to be 'Undefined', but got '%v'", class.Relation.Type)) + assert.Equal(t, class.Relation.IncludeFrom, false, fmt.Sprintf("Expected IncludeFrom to be 'false', but got '%v'", class.Relation.IncludeFrom)) + assert.Equal(t, class.Relation.RelationalClass, false, fmt.Sprintf("Expected RelationalClass to be 'false', but got '%v'", class.Relation.RelationalClass)) +} - for _, test := range tests { - genLogger.Info(fmt.Sprintf("Executing: %s' with input '%s' and expected output '%s'", t.Name(), test["resource_name"], test["expected"])) - class.ResourceName = test["resource_name"].(string) - class.IdentifiedBy = test["identifiers"].([]string) - class.setResourceNameNested() - assert.Equal(t, test["expected"], class.ResourceNameNested, fmt.Sprintf("Expected '%s', but got '%s'", test["expected"], class.ResourceNameNested)) - } +func TestSetRelationIncludeFromFalseAndTypeNamed(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: "fvRsCons"} + class.MetaFileContent = relationInfoFvRsCons + err := class.setRelation() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.Relation.FromClass, "fvEPg", fmt.Sprintf("Expected FromClass to be 'fvEPg', but got '%s'", class.Relation.FromClass)) + assert.Equal(t, class.Relation.ToClass, "vzBrCP", fmt.Sprintf("Expected ToClass to be 'vzBrCP', but got '%s'", class.Relation.ToClass)) + assert.Equal(t, class.Relation.Type, Named, fmt.Sprintf("Expected Type to be 'Named', but got '%v'", class.Relation.Type)) + assert.Equal(t, class.Relation.IncludeFrom, false, fmt.Sprintf("Expected IncludeFrom to be 'false', but got '%v'", class.Relation.IncludeFrom)) + assert.Equal(t, class.Relation.RelationalClass, true, fmt.Sprintf("Expected RelationalClass to be 'true', but got '%v'", class.Relation.RelationalClass)) +} +func TestSetRelationIncludeFromTrueAndTypeExplicit(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: "netflowRsExporterToCtx"} + class.MetaFileContent = relationInfoNetflowRsExporterToCtx + err := class.setRelation() + assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) + assert.Equal(t, class.Relation.FromClass, "netflowAExporterPol", fmt.Sprintf("Expected FromClass to be 'netflowAExporterPol', but got '%s'", class.Relation.FromClass)) + assert.Equal(t, class.Relation.ToClass, "fvCtx", fmt.Sprintf("Expected ToClass to be 'fvCtx', but got '%s'", class.Relation.ToClass)) + assert.Equal(t, class.Relation.Type, Explicit, fmt.Sprintf("Expected Type to be 'Explicit', but got '%v'", class.Relation.Type)) + assert.Equal(t, class.Relation.IncludeFrom, true, fmt.Sprintf("Expected IncludeFrom to be 'false', but got '%v'", class.Relation.IncludeFrom)) + assert.Equal(t, class.Relation.RelationalClass, true, fmt.Sprintf("Expected RelationalClass to be 'true', but got '%v'", class.Relation.RelationalClass)) +} + +func TestSetRelationUndefinedType(t *testing.T) { + test.InitializeTest(t) + class := Class{ClassName: "netflowRsExporterToCtx"} + class.MetaFileContent = map[string]interface{}{ + "relationInfo": map[string]interface{}{ + "type": "undefinedType", + "fromMo": "netflow:AExporterPol", + "toMo": "fv:Ctx", + }, + } + err := class.setRelation() + assert.Error(t, err, fmt.Sprintf("Expected error, but got '%s'", err)) } diff --git a/gen/utils/data/constants.go b/gen/utils/data/constants.go index 3ada2e01d..ecbbd59a1 100644 --- a/gen/utils/data/constants.go +++ b/gen/utils/data/constants.go @@ -2,6 +2,10 @@ package data const ( // Environment variables for logging configuration. + // The path to the definition files. + constDefinitionsPath = "./gen/definitions" + // The path to the global definition file. + constGlobalDefinitionFilePath = "./gen/definitions/global.yaml" // Determine if the meta data in the meta directory should be refreshed from remote location // The default is to not refresh the meta data. constEnvMetaRefresh = "GEN_ACI_TF_META_REFRESH_ALL" diff --git a/gen/utils/data/data.go b/gen/utils/data/data.go index cfe3bc2bf..14234bddf 100644 --- a/gen/utils/data/data.go +++ b/gen/utils/data/data.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "slices" + "sort" "strconv" "strings" @@ -16,12 +17,14 @@ import ( // Initialize a logger instance for the generator. var genLogger = logger.InitializeLogger() +var failedToLoadClasses = []string{} type DataStore struct { // A map containing all the information about the classes required to render the templates. Classes map[string]Class // The client used to retrieve the meta data from the remote location. - client *http.Client + client *http.Client + GlobalMetaDefinition GlobalMetaDefinition // The host from which the meta data is retrieved. metaHost string // A list of all the classes that have been retrieved from the remote location. @@ -31,8 +34,9 @@ type DataStore struct { func NewDataStore() (*DataStore, error) { dataStore := &DataStore{ - Classes: make(map[string]Class), - client: &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}, + Classes: make(map[string]Class), + client: &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}, + GlobalMetaDefinition: loadGlobalMetaDefinition(), } // Set the meta data host for retrieval of meta files. dataStore.setMetaHost() @@ -158,13 +162,34 @@ func (ds *DataStore) loadClasses() error { // Load the meta data for all classes in the meta directory. genLogger.Debug(fmt.Sprintf("Loading classes from: %s.", constMetaPath)) for _, className := range utils.GetFileNamesFromDirectory(constMetaPath, true) { - // Create a new class object and add it to the data store. - classDetails, err := NewClass(className) + err := ds.loadClass(className) if err != nil { return err } - ds.Classes[className] = *classDetails } + + // If there are any classes that failed to load, log a Error. + // The resource names for these classes require to be defined in global definition file + if len(failedToLoadClasses) > 0 { + sort.Strings(failedToLoadClasses) + return fmt.Errorf("failed to load classes: %s", failedToLoadClasses) + } + genLogger.Debug(fmt.Sprintf("Successfully loaded classes from: %s.", constMetaPath)) return nil } + +func (ds *DataStore) loadClass(className string) error { + // Load the meta data for a class to the data store if it is not already loaded. + if _, ok := ds.Classes[className]; !ok { + genLogger.Debug(fmt.Sprintf("Loading class: %s.", className)) + classDetails, err := NewClass(className, ds) + if err != nil { + return err + } + ds.Classes[className] = *classDetails + } else { + genLogger.Debug(fmt.Sprintf("Class '%s' already loaded, skipping.", className)) + } + return nil +} diff --git a/gen/utils/data/data_test.go b/gen/utils/data/data_test.go index 64826fc25..932339757 100644 --- a/gen/utils/data/data_test.go +++ b/gen/utils/data/data_test.go @@ -28,3 +28,10 @@ func TestSetHostFromEnvironmentVariable(t *testing.T) { ds.setMetaHost() assert.Equal(t, ds.metaHost, metaHost, "Expected meta host to be set to the custom value %s, but got %s", metaHost, ds.metaHost) } + +// TODO: determine if tests are needed for ds methods +// ds.retrieveEnvMetaClassesFromRemote() +// ds.refreshMetaFiles() +// ds.retrieveMetaFileFromRemote() +// ds.loadClasses() +// ds.loadClass() diff --git a/gen/utils/data/definitions.go b/gen/utils/data/definitions.go new file mode 100644 index 000000000..4f3459af0 --- /dev/null +++ b/gen/utils/data/definitions.go @@ -0,0 +1,28 @@ +package data + +import ( + "os" + + "gopkg.in/yaml.v2" +) + +type GlobalMetaDefinition struct { + // A map containing class names as keys and their corresponding resource names as values. + // This is used search for the resource name of a class when it is not defined in meta directory. + NoMetaFile map[string]string +} + +func loadGlobalMetaDefinition() GlobalMetaDefinition { + definition, err := os.ReadFile(constGlobalDefinitionFilePath) + if err != nil { + genLogger.Fatal("A file 'global.yaml' is required to be defined in the definitions folder.") + } + + var definitionGlobalMetaData GlobalMetaDefinition + err = yaml.Unmarshal(definition, &definitionGlobalMetaData) + if err != nil { + genLogger.Fatal(err.Error()) + } + + return definitionGlobalMetaData +} diff --git a/gen/utils/utils.go b/gen/utils/utils.go index 6929335c3..2d6288508 100644 --- a/gen/utils/utils.go +++ b/gen/utils/utils.go @@ -55,5 +55,7 @@ func Underscore(s string) string { re := regexp.MustCompile(reStr) s = re.ReplaceAllString(s, "${1}_${2}") } - return strings.ReplaceAll(strings.ToLower(s), " ", "_") + s = strings.ReplaceAll(strings.ToLower(s), " ", "_") + s = strings.ReplaceAll(s, "-", "_") + return s } diff --git a/gen/utils/utils_test.go b/gen/utils/utils_test.go index 87b89bf42..89424c4e0 100644 --- a/gen/utils/utils_test.go +++ b/gen/utils/utils_test.go @@ -49,6 +49,8 @@ func TestUnderscore(t *testing.T) { {"input": "Tenant1", "expected": "tenant1"}, {"input": "ApplicationEndpointGroup", "expected": "application_endpoint_group"}, {"input": "Application Endpoint Group", "expected": "application_endpoint_group"}, + {"input": "Application-Endpoint-Group", "expected": "application_endpoint_group"}, + {"input": "Application Endpoint-Group", "expected": "application_endpoint_group"}, } for _, test := range tests { diff --git a/go.mod b/go.mod index 4aa5c22f7..a768b48e1 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/stretchr/testify v1.10.0 golang.org/x/text v0.24.0 golang.org/x/tools v0.32.0 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -69,6 +70,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/grpc v1.69.4 // indirect google.golang.org/protobuf v1.36.3 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) From 47fcf21a496660bfe1d4545b09a5df5b05a5d65d Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Wed, 25 Jun 2025 07:58:59 +0200 Subject: [PATCH 10/12] [ignore] change yaml keys to snakecase --- gen/definitions/global.yaml | 2 +- gen/utils/data/definitions.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gen/definitions/global.yaml b/gen/definitions/global.yaml index d0655289d..b2f6c0e99 100644 --- a/gen/definitions/global.yaml +++ b/gen/definitions/global.yaml @@ -1,4 +1,4 @@ -nometafile: +no_meta_file: bgpCtxAfPol: bgp_address_family_context bgpCtxPol: bgp_timers dhcpRelayP: dhcp_relay_policy diff --git a/gen/utils/data/definitions.go b/gen/utils/data/definitions.go index 4f3459af0..60d6a55ef 100644 --- a/gen/utils/data/definitions.go +++ b/gen/utils/data/definitions.go @@ -8,8 +8,8 @@ import ( type GlobalMetaDefinition struct { // A map containing class names as keys and their corresponding resource names as values. - // This is used search for the resource name of a class when it is not defined in meta directory. - NoMetaFile map[string]string + // This is to used search for the resource name of a class when it is not defined in meta directory. + NoMetaFile map[string]string `yaml:"no_meta_file"` } func loadGlobalMetaDefinition() GlobalMetaDefinition { From 6ae0ab715cb14b36bd2ae666b3cf59bb8b6b0a3b Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Wed, 25 Jun 2025 07:59:52 +0200 Subject: [PATCH 11/12] [ignore] change variable RelationShipTypeEnum to RelationshipTypeEnum --- gen/utils/data/class.go | 14 +++++++------- gen/utils/data/class_test.go | 2 +- gen/utils/data/definitions.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index 3877fc20c..cacf1de40 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -100,22 +100,22 @@ type Relation struct { // The class to which the relationship points. ToClass string // The type of the relationship. - Type RelationShipTypeEnum + Type RelationhipTypeEnum } // The enumeration options of the relationship type. -type RelationShipTypeEnum int +type RelationhipTypeEnum int const ( // Named indicates that the relationship is a named relation. - Named RelationShipTypeEnum = iota + 1 + Named RelationhipTypeEnum = iota + 1 // Explicit indicates that the relationship is an explicit relation. Explicit // Undefined indicates that the relationship type is unknown. - Undefined RelationShipTypeEnum = iota + Undefined RelationhipTypeEnum = iota ) -func setRelationShipTypeEnum(relationType string) (RelationShipTypeEnum, error) { +func setRelationhipTypeEnum(relationType string) (RelationhipTypeEnum, error) { switch relationType { case "named": return Named, nil @@ -354,7 +354,7 @@ func (c *Class) setRelation() error { // TODO: add logic to override the relational status from a definition file. if relationInfo, ok := c.MetaFileContent["relationInfo"]; ok { - relationType, err := setRelationShipTypeEnum(relationInfo.(map[string]interface{})["type"].(string)) + relationType, err := setRelationhipTypeEnum(relationInfo.(map[string]interface{})["type"].(string)) if err != nil { return err } @@ -365,7 +365,7 @@ func (c *Class) setRelation() error { } c.Relation.RelationalClass = true c.Relation.ToClass = strings.Replace(relationInfo.(map[string]interface{})["toMo"].(string), ":", "", -1) - c.Relation.Type = RelationShipTypeEnum(relationType) + c.Relation.Type = RelationhipTypeEnum(relationType) } genLogger.Debug(fmt.Sprintf("Successfully set Relation details for class '%s'.", c.ClassName)) diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index 8206d2675..d4d4cd6b1 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -144,7 +144,7 @@ func TestSetRelationNoRelation(t *testing.T) { assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) assert.Equal(t, class.Relation.FromClass, "", fmt.Sprintf("Expected FromClass to be 'fvEPg', but got '%s'", class.Relation.FromClass)) assert.Equal(t, class.Relation.ToClass, "", fmt.Sprintf("Expected ToClass to be 'vzBrCP', but got '%s'", class.Relation.ToClass)) - assert.Equal(t, class.Relation.Type, RelationShipTypeEnum(0), fmt.Sprintf("Expected Type to be 'Undefined', but got '%v'", class.Relation.Type)) + assert.Equal(t, class.Relation.Type, RelationhipTypeEnum(0), fmt.Sprintf("Expected Type to be 'Undefined', but got '%v'", class.Relation.Type)) assert.Equal(t, class.Relation.IncludeFrom, false, fmt.Sprintf("Expected IncludeFrom to be 'false', but got '%v'", class.Relation.IncludeFrom)) assert.Equal(t, class.Relation.RelationalClass, false, fmt.Sprintf("Expected RelationalClass to be 'false', but got '%v'", class.Relation.RelationalClass)) } diff --git a/gen/utils/data/definitions.go b/gen/utils/data/definitions.go index 60d6a55ef..3a29f70f7 100644 --- a/gen/utils/data/definitions.go +++ b/gen/utils/data/definitions.go @@ -8,7 +8,7 @@ import ( type GlobalMetaDefinition struct { // A map containing class names as keys and their corresponding resource names as values. - // This is to used search for the resource name of a class when it is not defined in meta directory. + // This is used to search for the resource name of a class when it is not defined in meta directory. NoMetaFile map[string]string `yaml:"no_meta_file"` } From fa10194e9297a2c2db41a471cc8009a2335e6149 Mon Sep 17 00:00:00 2001 From: akinross <akinross@cisco.com> Date: Thu, 3 Jul 2025 19:28:12 +0200 Subject: [PATCH 12/12] [ignore] fix typo in variable --- gen/utils/data/class.go | 14 +++++++------- gen/utils/data/class_test.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index cacf1de40..e2ef666e6 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -100,22 +100,22 @@ type Relation struct { // The class to which the relationship points. ToClass string // The type of the relationship. - Type RelationhipTypeEnum + Type RelationshipTypeEnum } // The enumeration options of the relationship type. -type RelationhipTypeEnum int +type RelationshipTypeEnum int const ( // Named indicates that the relationship is a named relation. - Named RelationhipTypeEnum = iota + 1 + Named RelationshipTypeEnum = iota + 1 // Explicit indicates that the relationship is an explicit relation. Explicit // Undefined indicates that the relationship type is unknown. - Undefined RelationhipTypeEnum = iota + Undefined RelationshipTypeEnum = iota ) -func setRelationhipTypeEnum(relationType string) (RelationhipTypeEnum, error) { +func setRelationshipTypeEnum(relationType string) (RelationshipTypeEnum, error) { switch relationType { case "named": return Named, nil @@ -354,7 +354,7 @@ func (c *Class) setRelation() error { // TODO: add logic to override the relational status from a definition file. if relationInfo, ok := c.MetaFileContent["relationInfo"]; ok { - relationType, err := setRelationhipTypeEnum(relationInfo.(map[string]interface{})["type"].(string)) + relationType, err := setRelationshipTypeEnum(relationInfo.(map[string]interface{})["type"].(string)) if err != nil { return err } @@ -365,7 +365,7 @@ func (c *Class) setRelation() error { } c.Relation.RelationalClass = true c.Relation.ToClass = strings.Replace(relationInfo.(map[string]interface{})["toMo"].(string), ":", "", -1) - c.Relation.Type = RelationhipTypeEnum(relationType) + c.Relation.Type = RelationshipTypeEnum(relationType) } genLogger.Debug(fmt.Sprintf("Successfully set Relation details for class '%s'.", c.ClassName)) diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index d4d4cd6b1..d4e3da770 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -144,7 +144,7 @@ func TestSetRelationNoRelation(t *testing.T) { assert.NoError(t, err, fmt.Sprintf("Expected no error, but got '%s'", err)) assert.Equal(t, class.Relation.FromClass, "", fmt.Sprintf("Expected FromClass to be 'fvEPg', but got '%s'", class.Relation.FromClass)) assert.Equal(t, class.Relation.ToClass, "", fmt.Sprintf("Expected ToClass to be 'vzBrCP', but got '%s'", class.Relation.ToClass)) - assert.Equal(t, class.Relation.Type, RelationhipTypeEnum(0), fmt.Sprintf("Expected Type to be 'Undefined', but got '%v'", class.Relation.Type)) + assert.Equal(t, class.Relation.Type, RelationshipTypeEnum(0), fmt.Sprintf("Expected Type to be 'Undefined', but got '%v'", class.Relation.Type)) assert.Equal(t, class.Relation.IncludeFrom, false, fmt.Sprintf("Expected IncludeFrom to be 'false', but got '%v'", class.Relation.IncludeFrom)) assert.Equal(t, class.Relation.RelationalClass, false, fmt.Sprintf("Expected RelationalClass to be 'false', but got '%v'", class.Relation.RelationalClass)) }