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

Fix name collisions between list funcs and their return types due to allOf usage #132

Merged
merged 4 commits into from
Apr 25, 2024
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
43 changes: 43 additions & 0 deletions pkg/bug_131_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package pkg

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestBug131(t *testing.T) {
mustReadTestOpenAPIDoc(t, filepath.Join("testdata", "bug_131_openapi.yml"))

openAPICtx := &OpenAPIContext{
Doc: *testOpenAPIDoc,
Pkg: &testPulumiPkg,
}

csharpNamespaces := map[string]string{
"": "Provider",
}

providerMetadata, _, err := openAPICtx.GatherResourcesFromAPI(csharpNamespaces)
assert.Nil(t, err)
assert.NotNil(t, providerMetadata)

t.Run("Renamed", func(t *testing.T) {
funcSpec, ok := testPulumiPkg.Functions[packageName+":actions/v2:listActions"]
assert.True(t, ok, "Expected to find function listActions in the Pulumi package spec")

assert.NotEmpty(t, funcSpec.Outputs.Properties["items"].Ref)
assert.Equal(t, funcSpec.Outputs.Properties["items"].Ref, "#/types/fake-package:actions/v2:ListActionsItems")
})

// Test that a response type without an allOf definition in its
// response type is named with the suffix Properties.
t.Run("PropertiesResponseType", func(t *testing.T) {
funcSpec, ok := testPulumiPkg.Functions[packageName+":actions2/v2:listActions2"]
assert.True(t, ok, "Expected to find function listActions2 in the Pulumi package spec")

assert.NotEmpty(t, funcSpec.Outputs.Properties["items"].Ref)
assert.Equal(t, funcSpec.Outputs.Properties["items"].Ref, "#/types/fake-package:actions2/v2:ListActions2Properties")
})
}
32 changes: 27 additions & 5 deletions pkg/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

const (
componentsSchemaRefPrefix = "#/components/schemas/"
typesSchemaRefPrefix = "#/types/"
jsonMimeType = "application/json"
parameterLocationPath = "path"
pathSeparator = "/"
Expand Down Expand Up @@ -458,6 +459,30 @@ func (o *OpenAPIContext) genListFunc(pathItem openapi3.PathItem, returnTypeSchem
return nil, errors.Wrap(err, "generating property type spec for response schema")
}

// Rename the output type if it's the same as the func name.
// This can happen if the response type schema uses an allOf
// definition because there is no single authoritative type
// to use as the name of the resulting type, so the resulting
// type is named using the parent name, which would be the
// function name in this case.
if outputPropType.Ref != "" {
actualTypeTok := strings.TrimPrefix(outputPropType.Ref, typesSchemaRefPrefix)
tokParts := strings.Split(actualTypeTok, ":")
actualTypeName := tokParts[2]

if strings.EqualFold(actualTypeName, funcName) {
newTypeName := actualTypeName + "Items"
outputType := funcPkgCtx.pkg.Types[actualTypeTok]
tokParts[2] = newTypeName
newTypeTok := strings.Join(tokParts, ":")
funcPkgCtx.pkg.Types[newTypeTok] = outputType

delete(funcPkgCtx.pkg.Types, actualTypeTok)

outputPropType.Ref = typesSchemaRefPrefix + newTypeTok
}
}

return &pschema.FunctionSpec{
Description: pathItem.Description,
Inputs: &pschema.ObjectTypeSpec{
Expand Down Expand Up @@ -912,10 +937,6 @@ func (ctx *resourceContext) propertyTypeSpec(parentName string, propSchema opena
typName := ToPascalCase(schemaName)
tok := fmt.Sprintf("%s:%s:%s", ctx.pkg.Name, ctx.mod, typName)

if schemaName == "sql_mode" {
glog.Info("")
}

typeSchema, ok := ctx.openapiComponents.Schemas[schemaName]
if !ok {
return nil, false, errors.Errorf("definition %s not found in resource %s", schemaName, parentName)
Expand Down Expand Up @@ -1022,7 +1043,8 @@ func (ctx *resourceContext) propertyTypeSpec(parentName string, propSchema opena
return nil, false, errors.Wrap(err, "generating properties from allOf schema definition")
}

tok := fmt.Sprintf("%s:%s:%s", ctx.pkg.Name, ctx.mod, ToPascalCase(parentName))
typName := ToPascalCase(parentName)
tok := fmt.Sprintf("%s:%s:%s", ctx.pkg.Name, ctx.mod, typName)
ctx.pkg.Types[tok] = pschema.ComplexTypeSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Description: propSchema.Value.Description,
Expand Down
113 changes: 113 additions & 0 deletions pkg/testdata/bug_131_openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
openapi: 3.1.0
info:
title: Fake API
version: "2.0"
servers:
- url: https://api.fake.com
description: production

components:
schemas:
meta_properties:
type: object
description: Information about the response itself.
properties:
total:
description: Number of objects returned by the request.
type: integer
example: 1
meta:
type: object
properties:
meta:
allOf:
- $ref: "#/components/schemas/meta_properties"
- required:
- total
required:
- meta
action:
type: object
properties:
id:
type: integer
description: >-
A unique numeric ID that can be used to identify and reference an
action.
example: 36804636

parameters:
per_page:
in: query
name: per_page
required: false
description: Number of items returned per page
schema:
type: integer
minimum: 1
default: 20
maximum: 200
example: 2
page:
in: query
name: page
required: false
description: Which 'page' of paginated results to return.
schema:
type: integer
minimum: 1
default: 1
example: 1

responses:
actions:
description: >-
The results will be returned as a JSON object with an actions key. This
will be set to an array filled with action objects containing the
standard action attributes
content:
application/json:
schema:
allOf:
- type: object
properties:
actions:
type: array
items:
$ref: "#/components/schemas/action"
- $ref: "#/components/schemas/meta"
actions2:
description: >-
The results will be returned as a JSON object with an actions key. This
will be set to an array filled with action objects containing the
standard action attributes
content:
application/json:
schema:
type: object
properties:
actions:
type: array
items:
$ref: "#/components/schemas/action"

paths:
/v2/actions:
get:
operationId: actions_list
parameters:
- $ref: "#/components/parameters/per_page"
- $ref: "#/components/parameters/page"
responses:
"200":
$ref: "#/components/responses/actions"

/v2/actions2:
get:
operationId: actions2_list
parameters:
- $ref: "#/components/parameters/per_page"
- $ref: "#/components/parameters/page"
responses:
"200":
$ref: "#/components/responses/actions2"
Loading