[codegen] Add type name generation tests. (#7470)
The inputs and expected outputs for the tests are encoded using a schema. Each property present in the schema forms a testcase; the expected outputs for each language are stored in each property's `Language` field with the language name "test". Expected outputs can be regenerated using `PULUMI_ACCEPT`.
This commit is contained in:
parent
8f000c0ca1
commit
d07b325138
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGeneratePackage(t *testing.T) {
|
||||
|
@ -56,3 +57,17 @@ func TestGenerateType(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTypeNames(t *testing.T) {
|
||||
test.TestTypeNameCodegen(t, "dotnet", func(pkg *schema.Package) test.TypeNameGeneratorFunc {
|
||||
modules, _, err := generateModuleContextMap("test", pkg)
|
||||
require.NoError(t, err)
|
||||
|
||||
root, ok := modules[""]
|
||||
require.True(t, ok)
|
||||
|
||||
return func(t schema.Type) string {
|
||||
return root.typeString(t, "", false, false, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -60,6 +60,26 @@ func TestGeneratePackage(t *testing.T) {
|
|||
test.TestSDKCodegen(t, "go", generatePackage)
|
||||
}
|
||||
|
||||
func TestGenerateTypeNames(t *testing.T) {
|
||||
test.TestTypeNameCodegen(t, "go", func(pkg *schema.Package) test.TypeNameGeneratorFunc {
|
||||
err := pkg.ImportLanguages(map[string]schema.Language{"go": Importer})
|
||||
require.NoError(t, err)
|
||||
|
||||
var goPkgInfo GoPackageInfo
|
||||
if goInfo, ok := pkg.Language["go"].(GoPackageInfo); ok {
|
||||
goPkgInfo = goInfo
|
||||
}
|
||||
packages := generatePackageContextMap("test", pkg, goPkgInfo)
|
||||
|
||||
root, ok := packages[""]
|
||||
require.True(t, ok)
|
||||
|
||||
return func(t schema.Type) string {
|
||||
return root.typeString(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type mocks int
|
||||
|
||||
func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
|
||||
|
|
429
pkg/codegen/internal/test/testdata/types.json
vendored
Normal file
429
pkg/codegen/internal/test/testdata/types.json
vendored
Normal file
|
@ -0,0 +1,429 @@
|
|||
{
|
||||
"name": "typetests",
|
||||
"version": "0.0.1",
|
||||
"meta": {
|
||||
"moduleFormat": "(.*)"
|
||||
},
|
||||
"config": {},
|
||||
"types": {
|
||||
"typetests::primitives": {
|
||||
"description": "Tests name generation for the primitive schame types. See https://pkg.go.dev/github.com/pulumi/pulumi/pkg/v3/codegen/schema#Type for a complete list",
|
||||
"properties": {
|
||||
"any": {
|
||||
"$ref": "pulumi.json#/Any",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<object>?",
|
||||
"plain": "object?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.Input",
|
||||
"plain": "interface{}"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "any | undefined",
|
||||
"plain": "any | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[Any]",
|
||||
"plain": "Optional[Any]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"$ref": "pulumi.json#/Archive",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<Archive>?",
|
||||
"plain": "Archive?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.ArchiveInput",
|
||||
"plain": "pulumi.Archive"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<pulumi.asset.Archive> | undefined",
|
||||
"plain": "pulumi.asset.Archive | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[pulumi.Input[pulumi.Archive]]",
|
||||
"plain": "Optional[pulumi.Archive]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"asset": {
|
||||
"$ref": "pulumi.json#/Asset",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<AssetOrArchive>?",
|
||||
"plain": "AssetOrArchive?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.AssetOrArchiveInput",
|
||||
"plain": "pulumi.AssetOrArchive"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<pulumi.asset.Asset | pulumi.asset.Archive> | undefined",
|
||||
"plain": "pulumi.asset.Asset | pulumi.asset.Archive | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[pulumi.Input[Union[pulumi.Asset, pulumi.Archive]]]",
|
||||
"plain": "Optional[Union[pulumi.Asset, pulumi.Archive]]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"boolean": {
|
||||
"type": "boolean",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<bool>?",
|
||||
"plain": "bool?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.BoolPtrInput",
|
||||
"plain": "*bool"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<boolean> | undefined",
|
||||
"plain": "boolean | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[pulumi.Input[bool]]",
|
||||
"plain": "Optional[bool]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"integer": {
|
||||
"type": "integer",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<int>?",
|
||||
"plain": "int?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.IntPtrInput",
|
||||
"plain": "*int"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<number> | undefined",
|
||||
"plain": "number | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[pulumi.Input[int]]",
|
||||
"plain": "Optional[int]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"$ref": "pulumi.json#/Json",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "InputJson?",
|
||||
"plain": "System.Text.Json.JsonElement?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.Input",
|
||||
"plain": "interface{}"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "any | undefined",
|
||||
"plain": "any | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[Any]",
|
||||
"plain": "Optional[Any]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"type": "number",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<double>?",
|
||||
"plain": "double?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.Float64PtrInput",
|
||||
"plain": "*float64"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<number> | undefined",
|
||||
"plain": "number | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[pulumi.Input[float]]",
|
||||
"plain": "Optional[float]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredAny": {
|
||||
"$ref": "pulumi.json#/Any",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<object>",
|
||||
"plain": "object"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.Input",
|
||||
"plain": "interface{}"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "any",
|
||||
"plain": "any"
|
||||
},
|
||||
"python": {
|
||||
"input": "Any",
|
||||
"plain": "Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredArchive": {
|
||||
"$ref": "pulumi.json#/Archive",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<Archive>",
|
||||
"plain": "Archive"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.ArchiveInput",
|
||||
"plain": "pulumi.Archive"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<pulumi.asset.Archive>",
|
||||
"plain": "pulumi.asset.Archive"
|
||||
},
|
||||
"python": {
|
||||
"input": "pulumi.Input[pulumi.Archive]",
|
||||
"plain": "pulumi.Archive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredAsset": {
|
||||
"$ref": "pulumi.json#/Asset",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<AssetOrArchive>",
|
||||
"plain": "AssetOrArchive"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.AssetOrArchiveInput",
|
||||
"plain": "pulumi.AssetOrArchive"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<pulumi.asset.Asset | pulumi.asset.Archive>",
|
||||
"plain": "pulumi.asset.Asset | pulumi.asset.Archive"
|
||||
},
|
||||
"python": {
|
||||
"input": "pulumi.Input[Union[pulumi.Asset, pulumi.Archive]]",
|
||||
"plain": "Union[pulumi.Asset, pulumi.Archive]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredBoolean": {
|
||||
"type": "boolean",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<bool>",
|
||||
"plain": "bool"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.BoolInput",
|
||||
"plain": "bool"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<boolean>",
|
||||
"plain": "boolean"
|
||||
},
|
||||
"python": {
|
||||
"input": "pulumi.Input[bool]",
|
||||
"plain": "bool"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredInteger": {
|
||||
"type": "integer",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<int>",
|
||||
"plain": "int"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.IntInput",
|
||||
"plain": "int"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<number>",
|
||||
"plain": "number"
|
||||
},
|
||||
"python": {
|
||||
"input": "pulumi.Input[int]",
|
||||
"plain": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredJson": {
|
||||
"$ref": "pulumi.json#/Json",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "InputJson",
|
||||
"plain": "System.Text.Json.JsonElement"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.Input",
|
||||
"plain": "interface{}"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "any",
|
||||
"plain": "any"
|
||||
},
|
||||
"python": {
|
||||
"input": "Any",
|
||||
"plain": "Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredNumber": {
|
||||
"type": "number",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<double>",
|
||||
"plain": "double"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.Float64Input",
|
||||
"plain": "float64"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<number>",
|
||||
"plain": "number"
|
||||
},
|
||||
"python": {
|
||||
"input": "pulumi.Input[float]",
|
||||
"plain": "float"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredString": {
|
||||
"type": "string",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<string>",
|
||||
"plain": "string"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.StringInput",
|
||||
"plain": "string"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<string>",
|
||||
"plain": "string"
|
||||
},
|
||||
"python": {
|
||||
"input": "pulumi.Input[str]",
|
||||
"plain": "str"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string": {
|
||||
"type": "string",
|
||||
"language": {
|
||||
"test": {
|
||||
"expected": {
|
||||
"dotnet": {
|
||||
"input": "Input<string>?",
|
||||
"plain": "string?"
|
||||
},
|
||||
"go": {
|
||||
"input": "pulumi.StringPtrInput",
|
||||
"plain": "*string"
|
||||
},
|
||||
"nodejs": {
|
||||
"input": "pulumi.Input<string> | undefined",
|
||||
"plain": "string | undefined"
|
||||
},
|
||||
"python": {
|
||||
"input": "Optional[pulumi.Input[str]]",
|
||||
"plain": "Optional[str]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"requiredAny",
|
||||
"requiredArchive",
|
||||
"requiredAsset",
|
||||
"requiredBoolean",
|
||||
"requiredInteger",
|
||||
"requiredJson",
|
||||
"requiredNumber",
|
||||
"requiredString"
|
||||
]
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
196
pkg/codegen/internal/test/type_driver.go
Normal file
196
pkg/codegen/internal/test/type_driver.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type typeTestCase struct {
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
}
|
||||
|
||||
type typeTestImporter int
|
||||
|
||||
func (typeTestImporter) ImportDefaultSpec(def *schema.DefaultValue, bytes json.RawMessage) (interface{}, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (typeTestImporter) ImportPropertySpec(property *schema.Property, bytes json.RawMessage) (interface{}, error) {
|
||||
var test typeTestCase
|
||||
if err := json.Unmarshal([]byte(bytes), &test); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &test, nil
|
||||
}
|
||||
|
||||
func (typeTestImporter) ImportObjectTypeSpec(object *schema.ObjectType, bytes json.RawMessage) (interface{}, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (typeTestImporter) ImportResourceSpec(resource *schema.Resource, bytes json.RawMessage) (interface{}, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (typeTestImporter) ImportFunctionSpec(function *schema.Function, bytes json.RawMessage) (interface{}, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (typeTestImporter) ImportPackageSpec(pkg *schema.Package, bytes json.RawMessage) (interface{}, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
type NewTypeNameGeneratorFunc func(pkg *schema.Package) TypeNameGeneratorFunc
|
||||
|
||||
type TypeNameGeneratorFunc func(t schema.Type) string
|
||||
|
||||
func TestTypeNameCodegen(t *testing.T, language string, newTypeNameGenerator NewTypeNameGeneratorFunc) {
|
||||
// Read in, decode, and import the schema.
|
||||
schemaBytes, err := os.ReadFile(filepath.FromSlash("../internal/test/testdata/types.json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var pkgSpec schema.PackageSpec
|
||||
err = json.Unmarshal(schemaBytes, &pkgSpec)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, err := schema.ImportSpec(pkgSpec, map[string]schema.Language{"test": typeTestImporter(0)})
|
||||
require.NoError(t, err)
|
||||
|
||||
typeName := newTypeNameGenerator(pkg)
|
||||
|
||||
if os.Getenv("PULUMI_ACCEPT") == "" {
|
||||
runTests := func(where string, props []*schema.Property, inputShape bool) {
|
||||
for _, p := range props {
|
||||
if testCase, ok := p.Language["test"].(*typeTestCase); ok {
|
||||
if expected, ok := testCase.Expected[language]; ok {
|
||||
typ := p.Type
|
||||
t.Run(where+"/"+p.Name, func(t *testing.T) {
|
||||
var expectedName string
|
||||
switch expected := expected.(type) {
|
||||
case string:
|
||||
expectedName = expected
|
||||
case map[string]interface{}:
|
||||
if inputShape {
|
||||
expectedName = expected["input"].(string)
|
||||
} else {
|
||||
expectedName = expected["plain"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedName, typeName(typ))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runTests("#/config", pkg.Config, false)
|
||||
|
||||
runTests("#/provider/properties", pkg.Provider.Properties, false)
|
||||
runTests("#/provider/inputProperties", pkg.Provider.InputProperties, false)
|
||||
if pkg.Provider.StateInputs != nil {
|
||||
runTests("#/provider/stateInputs/properties", pkg.Provider.StateInputs.Properties, false)
|
||||
}
|
||||
|
||||
for _, typ := range pkg.Types {
|
||||
if o, ok := typ.(*schema.ObjectType); ok {
|
||||
if o.IsInputShape() {
|
||||
continue
|
||||
}
|
||||
|
||||
runTests("#/types/"+o.Token+"/properties", o.Properties, false)
|
||||
runTests("#/types/"+o.InputShape.Token+"/properties", o.InputShape.Properties, true)
|
||||
}
|
||||
}
|
||||
for _, r := range pkg.Resources {
|
||||
runTests("#/resources/"+r.Token+"/properties", r.Properties, false)
|
||||
runTests("#/resources/"+r.Token+"/inputProperties", r.InputProperties, false)
|
||||
if r.StateInputs != nil {
|
||||
runTests("#/resources/"+r.Token+"/properties", r.StateInputs.Properties, false)
|
||||
}
|
||||
}
|
||||
for _, f := range pkg.Functions {
|
||||
if f.Inputs != nil {
|
||||
runTests("/functions/"+f.Token+"/inputs/properties", f.Inputs.Properties, false)
|
||||
}
|
||||
if f.Outputs != nil {
|
||||
runTests("/functions/"+f.Token+"/outputs/properties", f.Outputs.Properties, false)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
updateTests := func(props []*schema.Property) {
|
||||
for _, p := range props {
|
||||
testCase, _ := p.Language["test"].(*typeTestCase)
|
||||
if testCase == nil {
|
||||
testCase = &typeTestCase{}
|
||||
p.Language["test"] = testCase
|
||||
}
|
||||
if testCase.Expected == nil {
|
||||
testCase.Expected = map[string]interface{}{}
|
||||
}
|
||||
testCase.Expected[language] = typeName(p.Type)
|
||||
}
|
||||
}
|
||||
|
||||
updateTests(pkg.Config)
|
||||
|
||||
updateTests(pkg.Provider.Properties)
|
||||
updateTests(pkg.Provider.InputProperties)
|
||||
if pkg.Provider.StateInputs != nil {
|
||||
updateTests(pkg.Provider.StateInputs.Properties)
|
||||
}
|
||||
|
||||
for _, typ := range pkg.Types {
|
||||
if o, ok := typ.(*schema.ObjectType); ok {
|
||||
if o.IsInputShape() {
|
||||
continue
|
||||
}
|
||||
|
||||
updateTests(o.Properties)
|
||||
updateTests(o.InputShape.Properties)
|
||||
|
||||
for i, p := range o.Properties {
|
||||
testCase := p.Language["test"].(*typeTestCase)
|
||||
plain := testCase.Expected[language].(string)
|
||||
input := o.InputShape.Properties[i].Language["test"].(*typeTestCase).Expected[language].(string)
|
||||
testCase.Expected[language] = map[string]interface{}{
|
||||
"plain": plain,
|
||||
"input": input,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range pkg.Resources {
|
||||
updateTests(r.Properties)
|
||||
updateTests(r.InputProperties)
|
||||
if r.StateInputs != nil {
|
||||
updateTests(r.StateInputs.Properties)
|
||||
}
|
||||
}
|
||||
for _, f := range pkg.Functions {
|
||||
if f.Inputs != nil {
|
||||
updateTests(f.Inputs.Properties)
|
||||
}
|
||||
if f.Outputs != nil {
|
||||
updateTests(f.Outputs.Properties)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Create(filepath.FromSlash("../internal/test/testdata/types.json"))
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
encoder := json.NewEncoder(f)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
err = encoder.Encode(pkg)
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -5,8 +5,32 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test"
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGeneratePackage(t *testing.T) {
|
||||
test.TestSDKCodegen(t, "nodejs", GeneratePackage)
|
||||
}
|
||||
|
||||
func TestGenerateTypeNames(t *testing.T) {
|
||||
test.TestTypeNameCodegen(t, "nodejs", func(pkg *schema.Package) test.TypeNameGeneratorFunc {
|
||||
// Decode node-specific info
|
||||
err := pkg.ImportLanguages(map[string]schema.Language{"nodejs": Importer})
|
||||
require.NoError(t, err)
|
||||
|
||||
info, _ := pkg.Language["nodejs"].(NodePackageInfo)
|
||||
|
||||
modules, info, err := generateModuleContextMap("test", pkg, info, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg.Language["nodejs"] = info
|
||||
|
||||
root, ok := modules[""]
|
||||
require.True(t, ok)
|
||||
|
||||
return func(t schema.Type) string {
|
||||
return root.typeString(t, false, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test"
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var pathTests = []struct {
|
||||
|
@ -34,3 +36,23 @@ func TestRelPathToRelImport(t *testing.T) {
|
|||
func TestGeneratePackage(t *testing.T) {
|
||||
test.TestSDKCodegen(t, "python", GeneratePackage)
|
||||
}
|
||||
|
||||
func TestGenerateTypeNames(t *testing.T) {
|
||||
test.TestTypeNameCodegen(t, "python", func(pkg *schema.Package) test.TypeNameGeneratorFunc {
|
||||
// Decode python-specific info
|
||||
err := pkg.ImportLanguages(map[string]schema.Language{"python": Importer})
|
||||
require.NoError(t, err)
|
||||
|
||||
info, _ := pkg.Language["python"].(PackageInfo)
|
||||
|
||||
modules, err := generateModuleContextMap("test", pkg, info, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
root, ok := modules[""]
|
||||
require.True(t, ok)
|
||||
|
||||
return func(t schema.Type) string {
|
||||
return root.typeString(t, false, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
|
@ -697,6 +698,371 @@ func (pkg *Package) GetType(token string) (Type, bool) {
|
|||
return t, ok
|
||||
}
|
||||
|
||||
func (pkg *Package) MarshalJSON() (bytes []byte, err error) {
|
||||
version := ""
|
||||
if pkg.Version != nil {
|
||||
version = pkg.Version.String()
|
||||
}
|
||||
|
||||
var metadata *MetadataSpec
|
||||
if pkg.moduleFormat != nil {
|
||||
metadata = &MetadataSpec{ModuleFormat: pkg.moduleFormat.String()}
|
||||
}
|
||||
|
||||
spec := PackageSpec{
|
||||
Name: pkg.Name,
|
||||
Version: version,
|
||||
Description: pkg.Description,
|
||||
Keywords: pkg.Keywords,
|
||||
Homepage: pkg.Homepage,
|
||||
License: pkg.License,
|
||||
Attribution: pkg.Attribution,
|
||||
Repository: pkg.Repository,
|
||||
LogoURL: pkg.LogoURL,
|
||||
PluginDownloadURL: pkg.PluginDownloadURL,
|
||||
Meta: metadata,
|
||||
Types: map[string]ComplexTypeSpec{},
|
||||
Resources: map[string]ResourceSpec{},
|
||||
Functions: map[string]FunctionSpec{},
|
||||
}
|
||||
|
||||
spec.Config.Required, spec.Config.Variables, err = pkg.marshalProperties(pkg.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling package config: %w", err)
|
||||
}
|
||||
|
||||
spec.Provider, err = pkg.marshalResource(pkg.Provider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling provider: %w", err)
|
||||
}
|
||||
|
||||
for _, t := range pkg.Types {
|
||||
switch t := t.(type) {
|
||||
case *ObjectType:
|
||||
if t.IsInputShape() {
|
||||
continue
|
||||
}
|
||||
|
||||
o, err := pkg.marshalObject(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling type '%v': %w", t.Token, err)
|
||||
}
|
||||
spec.Types[t.Token] = o
|
||||
case *EnumType:
|
||||
spec.Types[t.Token] = pkg.marshalEnum(t)
|
||||
}
|
||||
}
|
||||
|
||||
for _, res := range pkg.Resources {
|
||||
r, err := pkg.marshalResource(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling resource '%v': %w", res.Token, err)
|
||||
}
|
||||
spec.Resources[res.Token] = r
|
||||
}
|
||||
|
||||
for _, fn := range pkg.Functions {
|
||||
f, err := pkg.marshalFunction(fn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling function '%v': %w", fn.Token, err)
|
||||
}
|
||||
spec.Functions[fn.Token] = f
|
||||
}
|
||||
|
||||
return jsonMarshal(spec)
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalObjectData(
|
||||
comment string, properties []*Property, language map[string]interface{}) (ObjectTypeSpec, error) {
|
||||
|
||||
required, props, err := pkg.marshalProperties(properties)
|
||||
if err != nil {
|
||||
return ObjectTypeSpec{}, err
|
||||
}
|
||||
|
||||
lang, err := marshalLanguage(language)
|
||||
if err != nil {
|
||||
return ObjectTypeSpec{}, err
|
||||
}
|
||||
|
||||
return ObjectTypeSpec{
|
||||
Description: comment,
|
||||
Properties: props,
|
||||
Type: "object",
|
||||
Required: required,
|
||||
Language: lang,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalObject(t *ObjectType) (ComplexTypeSpec, error) {
|
||||
data, err := pkg.marshalObjectData(t.Comment, t.Properties, t.Language)
|
||||
if err != nil {
|
||||
return ComplexTypeSpec{}, err
|
||||
}
|
||||
return ComplexTypeSpec{ObjectTypeSpec: data}, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalEnum(t *EnumType) ComplexTypeSpec {
|
||||
values := make([]EnumValueSpec, len(t.Elements))
|
||||
for i, el := range t.Elements {
|
||||
values[i] = EnumValueSpec{
|
||||
Name: el.Name,
|
||||
Description: el.Comment,
|
||||
Value: el.Value,
|
||||
DeprecationMessage: el.DeprecationMessage,
|
||||
}
|
||||
}
|
||||
|
||||
return ComplexTypeSpec{
|
||||
ObjectTypeSpec: ObjectTypeSpec{Type: pkg.marshalType(t.ElementType).Type},
|
||||
Enum: values,
|
||||
}
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalResource(r *Resource) (ResourceSpec, error) {
|
||||
object, err := pkg.marshalObjectData(r.Comment, r.Properties, r.Language)
|
||||
if err != nil {
|
||||
return ResourceSpec{}, fmt.Errorf("marshaling properties: %w", err)
|
||||
}
|
||||
|
||||
requiredInputs, inputs, err := pkg.marshalProperties(r.InputProperties)
|
||||
if err != nil {
|
||||
return ResourceSpec{}, fmt.Errorf("marshaling input properties: %w", err)
|
||||
}
|
||||
|
||||
var stateInputs *ObjectTypeSpec
|
||||
if r.StateInputs != nil {
|
||||
o, err := pkg.marshalObject(r.StateInputs)
|
||||
if err != nil {
|
||||
return ResourceSpec{}, fmt.Errorf("marshaling state inputs: %w", err)
|
||||
}
|
||||
stateInputs = &o.ObjectTypeSpec
|
||||
}
|
||||
|
||||
var aliases []AliasSpec
|
||||
for _, a := range r.Aliases {
|
||||
aliases = append(aliases, AliasSpec{
|
||||
Name: a.Name,
|
||||
Project: a.Project,
|
||||
Type: a.Type,
|
||||
})
|
||||
}
|
||||
|
||||
var methods map[string]string
|
||||
if len(r.Methods) != 0 {
|
||||
methods = map[string]string{}
|
||||
for _, m := range r.Methods {
|
||||
methods[m.Name] = m.Function.Token
|
||||
}
|
||||
}
|
||||
|
||||
return ResourceSpec{
|
||||
ObjectTypeSpec: object,
|
||||
InputProperties: inputs,
|
||||
RequiredInputs: requiredInputs,
|
||||
StateInputs: stateInputs,
|
||||
Aliases: aliases,
|
||||
DeprecationMessage: r.DeprecationMessage,
|
||||
Language: object.Language,
|
||||
IsComponent: r.IsComponent,
|
||||
Methods: methods,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalFunction(f *Function) (FunctionSpec, error) {
|
||||
var inputs *ObjectTypeSpec
|
||||
if f.Inputs != nil {
|
||||
ins, err := pkg.marshalObject(f.Inputs)
|
||||
if err != nil {
|
||||
return FunctionSpec{}, fmt.Errorf("marshaling inputs: %w", err)
|
||||
}
|
||||
inputs = &ins.ObjectTypeSpec
|
||||
}
|
||||
|
||||
var outputs *ObjectTypeSpec
|
||||
if f.Outputs != nil {
|
||||
outs, err := pkg.marshalObject(f.Outputs)
|
||||
if err != nil {
|
||||
return FunctionSpec{}, fmt.Errorf("marshaloutg outputs: %w", err)
|
||||
}
|
||||
outputs = &outs.ObjectTypeSpec
|
||||
}
|
||||
|
||||
lang, err := marshalLanguage(f.Language)
|
||||
if err != nil {
|
||||
return FunctionSpec{}, err
|
||||
}
|
||||
|
||||
return FunctionSpec{
|
||||
Description: f.Comment,
|
||||
Inputs: inputs,
|
||||
Outputs: outputs,
|
||||
Language: lang,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalProperties(props []*Property) (required []string, specs map[string]PropertySpec, err error) {
|
||||
if len(props) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
specs = make(map[string]PropertySpec, len(props))
|
||||
for _, p := range props {
|
||||
typ := p.Type
|
||||
if t, optional := typ.(*OptionalType); optional {
|
||||
typ = t.ElementType
|
||||
} else {
|
||||
required = append(required, p.Name)
|
||||
}
|
||||
|
||||
var defaultValue interface{}
|
||||
var defaultSpec *DefaultSpec
|
||||
if p.DefaultValue != nil {
|
||||
defaultValue = p.DefaultValue.Value
|
||||
if len(p.DefaultValue.Environment) != 0 || len(p.DefaultValue.Language) != 0 {
|
||||
lang, err := marshalLanguage(p.DefaultValue.Language)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("property '%v': %w", p.Name, err)
|
||||
}
|
||||
|
||||
defaultSpec = &DefaultSpec{
|
||||
Environment: p.DefaultValue.Environment,
|
||||
Language: lang,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lang, err := marshalLanguage(p.Language)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("property '%v': %w", p.Name, err)
|
||||
}
|
||||
|
||||
specs[p.Name] = PropertySpec{
|
||||
TypeSpec: pkg.marshalType(typ),
|
||||
Description: p.Comment,
|
||||
Const: p.ConstValue,
|
||||
Default: defaultValue,
|
||||
DefaultInfo: defaultSpec,
|
||||
DeprecationMessage: p.DeprecationMessage,
|
||||
Language: lang,
|
||||
Secret: p.Secret,
|
||||
}
|
||||
}
|
||||
return required, specs, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalType(t Type) TypeSpec {
|
||||
switch t := t.(type) {
|
||||
case *InputType:
|
||||
el := pkg.marshalType(t.ElementType)
|
||||
el.Plain = false
|
||||
return el
|
||||
case *ArrayType:
|
||||
el := pkg.marshalType(t.ElementType)
|
||||
return TypeSpec{
|
||||
Type: "array",
|
||||
Items: &el,
|
||||
Plain: true,
|
||||
}
|
||||
case *MapType:
|
||||
el := pkg.marshalType(t.ElementType)
|
||||
return TypeSpec{
|
||||
Type: "object",
|
||||
AdditionalProperties: &el,
|
||||
Plain: true,
|
||||
}
|
||||
case *UnionType:
|
||||
oneOf := make([]TypeSpec, len(t.ElementTypes))
|
||||
for i, el := range t.ElementTypes {
|
||||
oneOf[i] = pkg.marshalType(el)
|
||||
}
|
||||
|
||||
defaultType := ""
|
||||
if t.DefaultType != nil {
|
||||
defaultType = pkg.marshalType(t.DefaultType).Type
|
||||
}
|
||||
|
||||
var discriminator *DiscriminatorSpec
|
||||
if t.Discriminator != "" {
|
||||
discriminator = &DiscriminatorSpec{
|
||||
PropertyName: t.Discriminator,
|
||||
Mapping: t.Mapping,
|
||||
}
|
||||
}
|
||||
|
||||
return TypeSpec{
|
||||
Type: defaultType,
|
||||
OneOf: oneOf,
|
||||
Discriminator: discriminator,
|
||||
Plain: true,
|
||||
}
|
||||
case *ObjectType:
|
||||
return TypeSpec{Ref: pkg.marshalTypeRef(t.Package, "types", t.Token)}
|
||||
case *EnumType:
|
||||
return TypeSpec{Ref: pkg.marshalTypeRef(t.Package, "types", t.Token)}
|
||||
case *ResourceType:
|
||||
return TypeSpec{Ref: pkg.marshalTypeRef(t.Resource.Package, "resources", t.Token)}
|
||||
case *TokenType:
|
||||
var defaultType string
|
||||
if t.UnderlyingType != nil {
|
||||
defaultType = pkg.marshalType(t.UnderlyingType).Type
|
||||
}
|
||||
|
||||
return TypeSpec{
|
||||
Type: defaultType,
|
||||
Ref: t.Token,
|
||||
}
|
||||
default:
|
||||
switch t {
|
||||
case BoolType:
|
||||
return TypeSpec{Type: "boolean"}
|
||||
case StringType:
|
||||
return TypeSpec{Type: "string"}
|
||||
case IntType:
|
||||
return TypeSpec{Type: "integer"}
|
||||
case NumberType:
|
||||
return TypeSpec{Type: "number"}
|
||||
case AnyType:
|
||||
return TypeSpec{Ref: "pulumi.json#/Any"}
|
||||
case ArchiveType:
|
||||
return TypeSpec{Ref: "pulumi.json#/Archive"}
|
||||
case AssetType:
|
||||
return TypeSpec{Ref: "pulumi.json#/Asset"}
|
||||
case JSONType:
|
||||
return TypeSpec{Ref: "pulumi.json#/Json"}
|
||||
default:
|
||||
panic(fmt.Errorf("unexepcted type %v (%T)", t, t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pkg *Package) marshalTypeRef(container *Package, section, token string) string {
|
||||
token = url.PathEscape(token)
|
||||
|
||||
if container == pkg {
|
||||
return fmt.Sprintf("#/%s/%s", section, token)
|
||||
}
|
||||
|
||||
// TODO(schema): this isn't quite right--it doesn't handle schemas sourced from URLs--but it's good enough for now.
|
||||
return fmt.Sprintf("/%s/%v/schema.json#/%s/%s", container.Name, container.Version, section, token)
|
||||
}
|
||||
|
||||
func marshalLanguage(lang map[string]interface{}) (map[string]json.RawMessage, error) {
|
||||
if len(lang) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := map[string]json.RawMessage{}
|
||||
for name, data := range lang {
|
||||
bytes, err := jsonMarshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling %v language data: %w", name, err)
|
||||
}
|
||||
result[name] = json.RawMessage(bytes)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TypeSpec is the serializable form of a reference to a type.
|
||||
type TypeSpec struct {
|
||||
// Type is the primitive or composite type, if any. May be "bool", "integer", "number", "string", "array", or
|
||||
|
@ -784,7 +1150,7 @@ type ComplexTypeSpec struct {
|
|||
ObjectTypeSpec
|
||||
|
||||
// Enum, if present, is the list of possible values for an enum type.
|
||||
Enum []*EnumValueSpec `json:"enum,omitempty"`
|
||||
Enum []EnumValueSpec `json:"enum,omitempty"`
|
||||
}
|
||||
|
||||
// EnumValuesSpec is the serializable form of the values metadata associated with an enum type.
|
||||
|
@ -1642,7 +2008,7 @@ func (t *types) bindEnumTypeDetails(enum *EnumType, token string, spec ComplexTy
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *types) bindEnumValues(values []*EnumValueSpec, typ Type) ([]*Enum, error) {
|
||||
func (t *types) bindEnumValues(values []EnumValueSpec, typ Type) ([]*Enum, error) {
|
||||
var enums []*Enum
|
||||
|
||||
errorMessage := func(val interface{}, expectedType string) error {
|
||||
|
@ -1977,3 +2343,14 @@ func bindFunctions(specs map[string]FunctionSpec, types *types) ([]*Function, ma
|
|||
|
||||
return functions, functionTable, nil
|
||||
}
|
||||
|
||||
func jsonMarshal(v interface{}) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
enc := json.NewEncoder(&b)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue