Skip to content

Commit

Permalink
Strongly type ApiVersion and SdkVersion (#3753)
Browse files Browse the repository at this point in the history
- Rename `packageGenerator.apiVersion` to `packageGenerator.sdkVersion`.
- Rename `packageGenerator.allApiVersions` to
`packageGenerator.allSdkVersions`.
- Add packageGenerator.apiVersion as pointer to an ApiVersion, if we can
convert it.
- Add `collections.OrderableSet` as replacement for `codegen.StringSet`
which allows for non-string types.
- Document where a string can be either API or SDK versions and is
therefore remaining a string for now.
- Fix type mismatch in `ShouldInclude` with the RemovedVersions file
which included removed versions in the description.
  • Loading branch information
danielrbradley authored Dec 6, 2024
1 parent 5a1321b commit 04edaa3
Show file tree
Hide file tree
Showing 1,286 changed files with 2,031 additions and 2,782 deletions.
15 changes: 15 additions & 0 deletions provider/pkg/collections/maps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package collections

import (
"cmp"
"slices"
)

func OrderedKeys[T cmp.Ordered](m map[T]any) []T {
keys := make([]T, 0, len(m))
for key := range m {
keys = append(keys, key)
}
slices.Sort(keys)
return keys
}
44 changes: 44 additions & 0 deletions provider/pkg/collections/orderableSet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package collections

import (
"cmp"
"slices"
)

type OrderableSet[T cmp.Ordered] struct {
m map[T]struct{}
}

func NewOrderableSet[T cmp.Ordered](values ...T) *OrderableSet[T] {
newSet := &OrderableSet[T]{m: make(map[T]struct{})}
for _, value := range values {
newSet.Add(value)
}
return newSet
}

func (s *OrderableSet[T]) Add(value T) {
s.m[value] = struct{}{}
}

func (s *OrderableSet[T]) Has(value T) bool {
_, ok := s.m[value]
return ok
}

func (s *OrderableSet[T]) Remove(value T) {
delete(s.m, value)
}

func (s *OrderableSet[T]) Count() int {
return len(s.m)
}

func (s *OrderableSet[T]) SortedValues() []T {
values := make([]T, 0, len(s.m))
for value := range s.m {
values = append(values, value)
}
slices.Sort(values)
return values
}
4 changes: 2 additions & 2 deletions provider/pkg/gen/gen_dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ func TestPortalDashboardGen(t *testing.T) {
if err != nil {
t.Fatal(err)
}
providers = openapi.ApplyProvidersTransformations(providers, map[string]map[string]string{
providers = openapi.ApplyProvidersTransformations(providers, openapi.DefaultVersionLock{
"Portal": {
"Dashboard": "2020-09-01-preview",
},
}, map[string]map[string]string{}, map[string][]string{}, map[string][]string{})
}, openapi.DefaultVersionLock{}, nil, nil)
generationResult, err := PulumiSchema(rootDir, providers, versioningStub{}, 2)
if err != nil {
t.Fatal(err)
Expand Down
70 changes: 41 additions & 29 deletions provider/pkg/gen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"sort"
"strings"

Expand Down Expand Up @@ -49,7 +50,7 @@ type ResourceDeprecation struct {
}

type Versioning interface {
ShouldInclude(provider string, version string, typeName, token string) bool
ShouldInclude(provider string, version *openapi.ApiVersion, typeName, token string) bool
GetDeprecation(token string) (ResourceDeprecation, bool)
GetAllVersions(openapi.ProviderName, openapi.ResourceName) []openapi.ApiVersion
}
Expand Down Expand Up @@ -285,19 +286,29 @@ func PulumiSchema(rootDir string, providerMap openapi.AzureProviders, versioning
exampleMap := make(map[string][]resources.AzureAPIExample)
for _, providerName := range providers {
versionMap := providerMap[providerName]
var versions []string
var versions []openapi.SdkVersion
for version := range versionMap {
versions = append(versions, version)
}
sort.Strings(versions)
slices.Sort(versions)

for _, version := range versions {
for _, sdkVersion := range versions {
// Attempt to convert back to an API version for use elsewhere
var apiVersion *openapi.ApiVersion
if sdkVersion != "" {
apiVersionConverted, err := openapi.SdkToApiVersion(sdkVersion)
if err != nil {
return nil, fmt.Errorf("failed to convert SDK version %s back to API version: %v", sdkVersion, err)
}
apiVersion = &apiVersionConverted
}
gen := packageGenerator{
pkg: &pkg,
metadata: &metadata,
provider: providerName,
apiVersion: version,
allApiVersions: versions,
apiVersion: apiVersion,
sdkVersion: sdkVersion,
allSdkVersions: versions,
examples: exampleMap,
versioning: versioning,
caseSensitiveTypes: caseSensitiveTypes,
Expand All @@ -311,16 +322,16 @@ func PulumiSchema(rootDir string, providerMap openapi.AzureProviders, versioning
module := gen.moduleName()
csharpNamespaces[strings.ToLower(providerName)] = providerName
javaPackages[module] = strings.ToLower(providerName)
if version != "" {
csVersion := strings.Title(csharpVersionReplacer.Replace(version))
if sdkVersion != "" {
csVersion := strings.Title(csharpVersionReplacer.Replace(string(sdkVersion)))
csharpNamespaces[module] = fmt.Sprintf("%s.%s", providerName, csVersion)
javaPackages[module] = fmt.Sprintf("%s.%s", strings.ToLower(providerName), version)
javaPackages[module] = fmt.Sprintf("%s.%s", strings.ToLower(providerName), sdkVersion)
}
pythonModuleNames[module] = module
golangImportAliases[filepath.Join(goModuleRepoPath, gen.versionedModuleName())] = strings.ToLower(providerName)

// Populate resources and get invokes.
items := versionMap[version]
items := versionMap[sdkVersion]
var resources []string
for resource := range items.Resources {
resources = append(resources, resource)
Expand Down Expand Up @@ -626,8 +637,9 @@ type packageGenerator struct {
metadata *resources.AzureAPIMetadata
provider openapi.ProviderName
examples map[string][]resources.AzureAPIExample
apiVersion string
allApiVersions []openapi.ApiVersion
apiVersion *openapi.ApiVersion
sdkVersion openapi.SdkVersion
allSdkVersions []openapi.SdkVersion
versioning Versioning
caseSensitiveTypes caseSensitiveTokens
warnings []string
Expand Down Expand Up @@ -769,7 +781,7 @@ func (g *packageGenerator) findResourceVariants(resource *openapi.ResourceSpec)
return result, nil
}

func (g *packageGenerator) makeTypeAlias(alias, apiVersion string) pschema.AliasSpec {
func (g *packageGenerator) makeTypeAlias(alias string, apiVersion openapi.SdkVersion) pschema.AliasSpec {
fqAlias := fmt.Sprintf("%s:%s:%s", g.pkg.Name, g.providerApiToModule(apiVersion), alias)
return pschema.AliasSpec{Type: &fqAlias}
}
Expand Down Expand Up @@ -847,7 +859,7 @@ func (g *packageGenerator) genResourceVariant(apiSpec *openapi.ResourceSpec, res

resourceSpec := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Description: g.formatDescription(resourceResponse.description, resource.typeName, swagger.Info.Version, apiSpec.PreviousVersion, additionalDocs),
Description: g.formatDescription(resourceResponse.description, resource.typeName, openapi.ApiVersion(swagger.Info.Version), apiSpec.PreviousVersion, additionalDocs),
Type: "object",
Properties: resourceResponse.specs,
Required: resourceResponse.requiredSpecs.SortedValues(),
Expand Down Expand Up @@ -962,7 +974,7 @@ func (g *packageGenerator) generateAliases(resource *resourceVariant, typeNameAl
var aliases []pschema.AliasSpec

for _, alias := range typeNameAliases {
aliases = append(aliases, g.makeTypeAlias(alias, g.apiVersion))
aliases = append(aliases, g.makeTypeAlias(alias, g.sdkVersion))
}

// Add an alias for each API version that has the same path in it.
Expand Down Expand Up @@ -1050,7 +1062,7 @@ func (g *packageGenerator) genFunctions(typeName, path string, specParams []spec
}

// Generate the function to get this resource.
functionTok := g.generateTok(typeName, g.apiVersion)
functionTok := g.generateTok(typeName, g.sdkVersion)
if !g.shouldInclude(typeName, functionTok, g.apiVersion) {
return
}
Expand Down Expand Up @@ -1098,31 +1110,31 @@ func (g *packageGenerator) genFunctions(typeName, path string, specParams []spec
g.metadata.Invokes[functionTok] = f
}

// moduleName produces the module name from the provider name and the API version (e.g. (`Compute`, `2020-07-01` => `compute/v20200701`).
// moduleName produces the module name from the provider name and the version e.g. `compute/v20200701`.
func (g *packageGenerator) moduleName() string {
return g.providerApiToModule(g.apiVersion)
return g.providerApiToModule(g.sdkVersion)
}

func (g *packageGenerator) versionedModuleName() string {
versionedModule := strings.ToLower(g.provider) + goModuleVersion
if g.apiVersion == "" {
if g.sdkVersion == "" {
return versionedModule
}
return fmt.Sprintf("%s/%s", versionedModule, g.apiVersion)
return fmt.Sprintf("%s/%s", versionedModule, g.sdkVersion)
}

func (g *packageGenerator) providerApiToModule(apiVersion string) string {
func (g *packageGenerator) providerApiToModule(apiVersion openapi.SdkVersion) string {
if apiVersion == "" {
return strings.ToLower(g.provider)
}
return fmt.Sprintf("%s/%s", strings.ToLower(g.provider), apiVersion)
}

func (g *packageGenerator) generateTok(typeName string, apiVersion string) string {
func (g *packageGenerator) generateTok(typeName string, apiVersion openapi.SdkVersion) string {
return fmt.Sprintf(`%s:%s:%s`, g.pkg.Name, g.providerApiToModule(apiVersion), typeName)
}

func (g *packageGenerator) shouldInclude(typeName, tok, version string) bool {
func (g *packageGenerator) shouldInclude(typeName, tok string, version *openapi.ApiVersion) bool {
return g.versioning.ShouldInclude(g.provider, version, typeName, tok)
}

Expand All @@ -1131,30 +1143,30 @@ func (g *packageGenerator) formatFunctionDescription(op *spec.Operation, typeNam
if op.Description != "" {
desc = op.Description
}
return g.formatDescription(desc, typeName, info.Version, "", nil)
return g.formatDescription(desc, typeName, openapi.ApiVersion(info.Version), "", nil)
}

func (g *packageGenerator) formatDescription(desc string, typeName string, defaultVersion, previousDefaultVersion string, additionalDocs *string) string {
func (g *packageGenerator) formatDescription(desc string, typeName string, defaultVersion, previousDefaultVersion openapi.ApiVersion, additionalDocs *string) string {
var b strings.Builder
b.WriteString(desc)

if g.apiVersion == "" {
if g.sdkVersion == "" {
fmt.Fprintf(&b, "\nAzure REST API version: %s.", defaultVersion)
if previousDefaultVersion != "" {
fmt.Fprintf(&b, " Prior API version in Azure Native 1.x: %s.", previousDefaultVersion)
}

// List other available API versions, if any.
allVersions := g.versioning.GetAllVersions(g.provider, typeName)
includedVersions := []openapi.ApiVersion{}
includedVersions := []string{}
for _, v := range allVersions {
// Don't list the default version twice.
if v == defaultVersion {
continue
}
tok := g.generateTok(typeName, openapi.ApiToSdkVersion(v))
if g.shouldInclude(typeName, tok, v) {
includedVersions = append(includedVersions, v)
if g.shouldInclude(typeName, tok, &v) {
includedVersions = append(includedVersions, string(v))
}
}

Expand Down
16 changes: 8 additions & 8 deletions provider/pkg/gen/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
func TestTypeAliasFormatting(t *testing.T) {
generator := packageGenerator{
pkg: &pschema.PackageSpec{Name: "azure-native"},
apiVersion: "v20220222",
sdkVersion: "v20220222",
provider: "Compute",
}

Expand All @@ -34,12 +34,12 @@ func TestTypeAliasFormatting(t *testing.T) {
var _ Versioning = (*versioningStub)(nil)

type versioningStub struct {
shouldInclude func(provider string, version string, typeName, token string) bool
shouldInclude func(provider string, version *openapi.ApiVersion, typeName, token string) bool
getDeprecations func(token string) (ResourceDeprecation, bool)
getAllVersions func(provider, resource string) []string
getAllVersions func(provider, resource string) []openapi.ApiVersion
}

func (v versioningStub) ShouldInclude(provider string, version string, typeName, token string) bool {
func (v versioningStub) ShouldInclude(provider string, version *openapi.ApiVersion, typeName, token string) bool {
if v.shouldInclude != nil {
return v.shouldInclude(provider, version, typeName, token)
}
Expand All @@ -53,25 +53,25 @@ func (v versioningStub) GetDeprecation(token string) (ResourceDeprecation, bool)
return ResourceDeprecation{}, false
}

func (v versioningStub) GetAllVersions(provider, resource string) []string {
func (v versioningStub) GetAllVersions(provider, resource string) []openapi.ApiVersion {
if v.getAllVersions != nil {
return v.getAllVersions(provider, resource)
}
return []string{}
return []openapi.ApiVersion{}
}

func TestAliases(t *testing.T) {
generator := packageGenerator{
pkg: &pschema.PackageSpec{Name: "azure-native"},
apiVersion: "v20220222",
sdkVersion: "v20220222",
versioning: versioningStub{},
provider: "Insights",
majorVersion: 2,
}

resource := &resourceVariant{
ResourceSpec: &openapi.ResourceSpec{
CompatibleVersions: []string{"v20210111"},
CompatibleVersions: []openapi.SdkVersion{"v20210111"},
},
typeName: "PrivateLinkForAzureAd",
}
Expand Down
11 changes: 4 additions & 7 deletions provider/pkg/openapi/apiVersion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,17 @@ import (

func TestApiVersionToDate(t *testing.T) {
t.Run("simple", func(t *testing.T) {
apiVersion := "2020-01-01"
date, err := ApiVersionToDate(apiVersion)
date, err := ApiVersionToDate("2020-01-01")
assert.NoError(t, err)
actual := date.Format("2006-01-02")
assert.Equal(t, apiVersion, actual)
assert.Equal(t, "2020-01-01", actual)
})

t.Run("preview", func(t *testing.T) {
apiVersion := "2020-01-01-preview"
date, err := ApiVersionToDate(apiVersion)
date, err := ApiVersionToDate("2020-01-01-preview")
assert.NoError(t, err)
expected := "2020-01-01"
actual := date.Format("2006-01-02")
assert.Equal(t, expected, actual)
assert.Equal(t, "2020-01-01", actual)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ func TestAllDefaultStatesConvertable(t *testing.T) {
resourceTokens, found := resourcesByNormalisedPath[pathWithoutVersion]
require.Truef(t, found, "Resource not found in test data: %s", pathWithoutVersion)
for _, resourceToken := range resourceTokens {
var apiVersion string
var apiVersion openapi.ApiVersion
sdkVersionMatch := resourceTokenVersionMatcher.FindStringSubmatch(resourceToken)
if len(sdkVersionMatch) > 1 {
apiVersion, err = openapi.SdkToApiVersion(sdkVersionMatch[1])
apiVersion, err = openapi.SdkToApiVersion(openapi.SdkVersion(sdkVersionMatch[1]))
require.Nil(t, err, "Failed to convert SDK version to API version: %s", sdkVersionMatch[1])
}
defaultState := defaults.GetDefaultResourceState(path, apiVersion)
defaultState := defaults.GetDefaultResourceState(path, string(apiVersion))
if defaultState == nil || defaultState.SkipDelete {
return
}
Expand Down
Loading

0 comments on commit 04edaa3

Please sign in to comment.