Skip to content

Commit

Permalink
Merge pull request #3611 from hashicorp/f/support-for-using-display-n…
Browse files Browse the repository at this point in the history
…ames-for-constants

`tools/importer-rest-api-specs`: support for pulling the Display Name for an Integer Constant if defined
  • Loading branch information
tombuildsstuff authored Jan 17, 2024
2 parents bd137ae + 6d81cb1 commit 0ce64cb
Show file tree
Hide file tree
Showing 4 changed files with 530 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import (
)

type constantExtension struct {
// name defines the Name that should be used for this Constant
name string

// valuesToDisplayNames defines any display name overrides that should be used for this Constant
// NOTE: whilst the API Definitions may define a value with no display name - this map contains
// only values with a name defined.
valuesToDisplayNames *map[interface{}]string
}

type ParsedConstant struct {
Expand Down Expand Up @@ -96,6 +102,14 @@ func MapConstant(typeVal spec.StringOrArray, fieldName string, values []interfac
}

key := keyValueForInteger(int64(value))
// if an override name is defined for this Constant then we should use it
if constExtension.valuesToDisplayNames != nil {
overrideName, hasOverride := (*constExtension.valuesToDisplayNames)[value]
if hasOverride {
key = overrideName
}
}

val := fmt.Sprintf("%d", int64(value))
normalizedName := normalizeConstantKey(key)
keysAndValues[normalizedName] = val
Expand Down Expand Up @@ -145,13 +159,38 @@ func parseConstantExtensionFromExtension(field spec.Extensions) (*constantExtens
}

var enumName *string
var valuesToDisplayNames *map[interface{}]string
for k, v := range enumDetails {
// presume inconsistencies in the data
if strings.EqualFold(k, "name") {
normalizedEnumName := cleanup.NormalizeName(v.(string))
enumName = &normalizedEnumName
}

if strings.EqualFold(k, "values") {
items := v.([]interface{})
displayNameOverrides := make(map[interface{}]string)
for _, itemRaw := range items {
item := itemRaw.(map[string]interface{})
name, ok := item["name"].(string)
if !ok || name == "" {
// there isn't a custom name defined for this, so we should ignore it
continue
}
value, ok := item["value"].(interface{})
if !ok {
continue
}
// NOTE: whilst `x-ms-enum` includes a `description` field we don't support that today
// support for that is tracked in https://github.com/hashicorp/pandora/issues/231

displayNameOverrides[value] = name
}
if len(displayNameOverrides) > 0 {
valuesToDisplayNames = &displayNameOverrides
}
}

// NOTE: the Swagger Extension defines `modelAsString` which is used to define whether
// this should be output as a fixed set of values (e.g. a constant) or an extendable
// list of strings (e.g. a set of possible string values with other values possible)
Expand All @@ -161,9 +200,13 @@ func parseConstantExtensionFromExtension(field spec.Extensions) (*constantExtens
return nil, fmt.Errorf("enum details are missing a `name`")
}

return &constantExtension{
output := constantExtension{
name: *enumName,
}, nil
}
if valuesToDisplayNames != nil {
output.valuesToDisplayNames = valuesToDisplayNames
}
return &output, nil
}

func keyValueForInteger(value int64) string {
Expand Down
190 changes: 190 additions & 0 deletions tools/importer-rest-api-specs/components/parser/constants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,101 @@ func TestParseConstantsIntegersTopLevelAsInts(t *testing.T) {
}
}

func TestParseConstantsIntegersTopLevelAsIntsWithDisplayName(t *testing.T) {
// This is an Integer Enum where there's a (Display) Name listed for the integer
// so we should be using `Name (string): Value (integer`)
result, err := ParseSwaggerFileForTesting(t, "constants_integers_with_names.json")
if err != nil {
t.Fatalf("parsing: %+v", err)
}
if result == nil {
t.Fatal("result was nil")
}
if len(result.Resources) != 1 {
t.Fatalf("expected 1 resource but got %d", len(result.Resources))
}

resource, ok := result.Resources["Discriminator"]
if !ok {
t.Fatal("the Resource 'Discriminator' was not found")
}

// sanity checking
if len(resource.Constants) != 1 {
t.Fatalf("expected 1 constant but got %d", len(resource.Constants))
}
if len(resource.Models) != 2 {
t.Fatalf("expected 2 models but got %d", len(resource.Models))
}
if len(resource.Operations) != 1 {
t.Fatalf("expected 1 operation but got %d", len(resource.Operations))
}
if len(resource.ResourceIds) != 1 {
t.Fatalf("expected 1 Resource ID but got %d", len(resource.ResourceIds))
}

wrapper, ok := resource.Models["ExampleWrapper"]
if !ok {
t.Fatalf("the Model `ExampleWrapper` was not found")
}
if len(wrapper.Fields) != 2 {
t.Fatalf("expected wrapper.Fields to have 2 fields but got %d", len(wrapper.Fields))
}

person, ok := resource.Models["Person"]
if !ok {
t.Fatalf("the Model `Person` was not found")
}
if len(person.Fields) != 1 {
t.Fatalf("expected person.Fields to have 1 field but got %d", len(person.Fields))
}
favouriteTableField, ok := person.Fields["FavouriteTable"]
if !ok {
t.Fatal("animal.Fields['FavouriteTable'] did not exist")
}
if favouriteTableField.ObjectDefinition == nil {
t.Fatal("animal.Fields['FavouriteTable'] had a nil ObjectDefinition")
}
if favouriteTableField.ObjectDefinition.Type != models.ObjectDefinitionReference {
t.Fatalf("animal.Fields['FavouriteTable'] should be a Reference but got %q", string(favouriteTableField.ObjectDefinition.Type))
}
if *favouriteTableField.ObjectDefinition.ReferenceName != "TableNumber" {
t.Fatalf("animal.Fields['FavouriteTable'] should be 'FavouriteTable' but was %q", *favouriteTableField.ObjectDefinition.ReferenceName)
}

favouriteTable, ok := resource.Constants["TableNumber"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] was not found")
}
if favouriteTable.Type != resourcemanager.IntegerConstant {
t.Fatalf("expected resource.Constants['TableNumber'].Type to be 'Integer' but got %q", favouriteTable.Type)
}
if len(favouriteTable.Values) != 3 {
t.Fatalf("expected resource.Constants['TableNumber'] to have 3 values but got %d", len(favouriteTable.Values))
}
v, ok := favouriteTable.Values["First"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] didn't contain the key 'First'")
}
if v != "1" {
t.Fatalf("expected the value for resource.Constants['TableNumber'].Values['First'] to be '1' but got %q", v)
}
v, ok = favouriteTable.Values["Second"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] didn't contain the key 'Second'")
}
if v != "2" {
t.Fatalf("expected the value for resource.Constants['TableNumber'].Values['Second'] to be '2' but got %q", v)
}
v, ok = favouriteTable.Values["Third"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] didn't contain the key 'Third'")
}
if v != "3" {
t.Fatalf("expected the value for resource.Constants['TableNumber'].Values['Third'] to be '3' but got %q", v)
}
}

func TestParseConstantsIntegersTopLevelAsStrings(t *testing.T) {
// Tests an Integer Constant with modelAsString, which is bad data / should be ignored
result, err := ParseSwaggerFileForTesting(t, "constants_integers_as_strings.json")
Expand Down Expand Up @@ -296,6 +391,101 @@ func TestParseConstantsIntegersInlinedAsInts(t *testing.T) {
}
}

func TestParseConstantsIntegersInlinedAsIntsWithDisplayName(t *testing.T) {
// This is an Integer Enum where there's a (Display) Name listed for the integer
// so we should be using `Name (string): Value (integer`)
result, err := ParseSwaggerFileForTesting(t, "constants_integers_with_names_inlined.json")
if err != nil {
t.Fatalf("parsing: %+v", err)
}
if result == nil {
t.Fatal("result was nil")
}
if len(result.Resources) != 1 {
t.Fatalf("expected 1 resource but got %d", len(result.Resources))
}

resource, ok := result.Resources["Discriminator"]
if !ok {
t.Fatal("the Resource 'Discriminator' was not found")
}

// sanity checking
if len(resource.Constants) != 1 {
t.Fatalf("expected 1 constant but got %d", len(resource.Constants))
}
if len(resource.Models) != 2 {
t.Fatalf("expected 2 models but got %d", len(resource.Models))
}
if len(resource.Operations) != 1 {
t.Fatalf("expected 1 operation but got %d", len(resource.Operations))
}
if len(resource.ResourceIds) != 1 {
t.Fatalf("expected 1 Resource ID but got %d", len(resource.ResourceIds))
}

wrapper, ok := resource.Models["ExampleWrapper"]
if !ok {
t.Fatalf("the Model `ExampleWrapper` was not found")
}
if len(wrapper.Fields) != 2 {
t.Fatalf("expected wrapper.Fields to have 2 fields but got %d", len(wrapper.Fields))
}

person, ok := resource.Models["Person"]
if !ok {
t.Fatalf("the Model `Person` was not found")
}
if len(person.Fields) != 1 {
t.Fatalf("expected person.Fields to have 1 field but got %d", len(person.Fields))
}
favouriteTableField, ok := person.Fields["FavouriteTable"]
if !ok {
t.Fatal("animal.Fields['FavouriteTable'] did not exist")
}
if favouriteTableField.ObjectDefinition == nil {
t.Fatal("animal.Fields['FavouriteTable'] had a nil ObjectDefinition")
}
if favouriteTableField.ObjectDefinition.Type != models.ObjectDefinitionReference {
t.Fatalf("animal.Fields['FavouriteTable'] should be a Reference but got %q", string(favouriteTableField.ObjectDefinition.Type))
}
if *favouriteTableField.ObjectDefinition.ReferenceName != "TableNumber" {
t.Fatalf("animal.Fields['FavouriteTable'] should be 'FavouriteTable' but was %q", *favouriteTableField.ObjectDefinition.ReferenceName)
}

favouriteTable, ok := resource.Constants["TableNumber"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] was not found")
}
if favouriteTable.Type != resourcemanager.IntegerConstant {
t.Fatalf("expected resource.Constants['TableNumber'].Type to be 'Integer' but got %q", favouriteTable.Type)
}
if len(favouriteTable.Values) != 3 {
t.Fatalf("expected resource.Constants['TableNumber'] to have 3 values but got %d", len(favouriteTable.Values))
}
v, ok := favouriteTable.Values["First"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] didn't contain the key 'First'")
}
if v != "1" {
t.Fatalf("expected the value for resource.Constants['TableNumber'].Values['First'] to be '1' but got %q", v)
}
v, ok = favouriteTable.Values["Second"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] didn't contain the key 'Second'")
}
if v != "2" {
t.Fatalf("expected the value for resource.Constants['TableNumber'].Values['Second'] to be '2' but got %q", v)
}
v, ok = favouriteTable.Values["Third"]
if !ok {
t.Fatalf("resource.Constants['TableNumber'] didn't contain the key 'Third'")
}
if v != "3" {
t.Fatalf("expected the value for resource.Constants['TableNumber'].Values['Third'] to be '3' but got %q", v)
}
}

func TestParseConstantsIntegersInlinedAsStrings(t *testing.T) {
// Tests an Integer Constant defined Inline with modelAsString, which is bad data / should be ignored
result, err := ParseSwaggerFileForTesting(t, "constants_integers_as_strings_inlined.json")
Expand Down
Loading

0 comments on commit 0ce64cb

Please sign in to comment.