diff --git a/pkg/bug_131_test.go b/pkg/bug_131_test.go new file mode 100644 index 0000000..c7687b2 --- /dev/null +++ b/pkg/bug_131_test.go @@ -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") + }) +} diff --git a/pkg/openapi.go b/pkg/openapi.go index 9b430b6..7b0a4be 100644 --- a/pkg/openapi.go +++ b/pkg/openapi.go @@ -23,6 +23,7 @@ import ( const ( componentsSchemaRefPrefix = "#/components/schemas/" + typesSchemaRefPrefix = "#/types/" jsonMimeType = "application/json" parameterLocationPath = "path" pathSeparator = "/" @@ -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{ @@ -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) @@ -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, diff --git a/pkg/testdata/bug_131_openapi.yml b/pkg/testdata/bug_131_openapi.yml new file mode 100644 index 0000000..156e80c --- /dev/null +++ b/pkg/testdata/bug_131_openapi.yml @@ -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"