diff --git a/gen/utils/data/class.go b/gen/utils/data/class.go index bff13522a..36cc5aa0d 100644 --- a/gen/utils/data/class.go +++ b/gen/utils/data/class.go @@ -4,8 +4,10 @@ import ( "encoding/json" "fmt" "os" + "regexp" "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" @@ -27,6 +29,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. @@ -178,20 +182,92 @@ func (c *Class) loadMetaFile() error { func (c *Class) setClassData() error { genLogger.Debug(fmt.Sprintf("Setting class data for class '%s'.", c.ClassName)) - c.setResourceName() + // TODO: add function to set AllowDelete - // TODO: add functions to set the other class data + // 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 + } + + err = c.setResourceNameNested() + if err != nil { + return err + } + + // 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 } -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)) + + // 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) + } + + genLogger.Debug(fmt.Sprintf("Successfully set resource name '%s' for class '%s'.", c.ResourceName, c.ClassName)) + 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 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) + 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{}) { diff --git a/gen/utils/data/class_test.go b/gen/utils/data/class_test.go index 265fd4979..686ad94a9 100644 --- a/gen/utils/data/class_test.go +++ b/gen/utils/data/class_test.go @@ -12,10 +12,11 @@ const ( constTestClassNameSingleWordInShortName = "fvTenant" constTestClassNameMultipleWordsInShortName = "fvRsIpslaMonPol" constTestClassNameErrorInShortName = "error" + constTestMetaFileContentForLabel = "tenant" ) 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 +24,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,9 +32,56 @@ 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)) 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)) +} + +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)) + } + +} 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/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 } 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.go b/gen/utils/utils.go index d33f0ddc1..6929335c3 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,26 @@ func GetFileNamesFromDirectory(path string, removeExtension bool) []string { genLogger.Debug(fmt.Sprintf("The directory '%s' contains the file names: %s.", path, names)) 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])`} { + 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 b09959153..87b89bf42 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))) @@ -39,3 +39,44 @@ 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)) + } + +} + +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)) +}