Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tools/importer-rest-api-specs: support for pulling the Display Name for an Integer Constant if defined #3611

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to check that this isn't nil or is it fine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension is defined in this manner, and everything we're importing matches it - so I think this is fine as-is for now. If this is an issue we can fix it at that point, but that's likely going to be bad data - so would want resolving upstream anyway - as such this is fine 👍

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
Loading