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

[docs] Fix generating constructor examples for resources that have numeric enums as input #16223

Merged
merged 1 commit into from
May 30, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
changes:
- type: fix
scope: docs
description: Fix generating constructor examples for resources that have numeric enums as input
50 changes: 34 additions & 16 deletions pkg/codegen/docs/constructor_syntax_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"

"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
Expand Down Expand Up @@ -149,28 +148,47 @@ func (g *constructorSyntaxGenerator) writeValue(
_, _, resourceNameFromToken, _ := pcl.DecomposeToken(valueType.Token, hcl.Range{})
write("%s", camelCase(resourceNameFromToken))
case *schema.EnumType:
cases := make([]string, len(valueType.Elements))
for index, c := range valueType.Elements {
if c.DeprecationMessage != "" {
continue
if valueType.ElementType == schema.NumberType || valueType.ElementType == schema.IntType {
var value interface{}
for _, elem := range valueType.Elements {
if elem.DeprecationMessage != "" {
continue
}

value = elem.Value
break
}

if stringCase, ok := c.Value.(string); ok && stringCase != "" {
cases[index] = stringCase
} else if intCase, ok := c.Value.(int); ok {
cases[index] = strconv.Itoa(intCase)
if value != nil {
write(fmt.Sprintf("%v", value))
} else {
if c.Name != "" {
cases[index] = c.Name
// all of them enum cases deprecated
// choose the first one
write(fmt.Sprintf("%v", valueType.Elements[0].Value))
}
} else {
cases := make([]string, len(valueType.Elements))
for index, c := range valueType.Elements {
if c.DeprecationMessage != "" {
continue
}

if stringCase, ok := c.Value.(string); ok && stringCase != "" {
cases[index] = stringCase
} else {
if c.Name != "" {
cases[index] = c.Name
}
}
}
}

if len(cases) > 0 {
write(fmt.Sprintf("%q", cases[0]))
} else {
write("null")
if len(cases) > 0 {
write(fmt.Sprintf("%q", cases[0]))
} else {
write("null")
}
}

case *schema.UnionType:
if isUnionOfObjects(valueType) && len(valueType.ElementTypes) >= 1 {
writeValue(valueType.ElementTypes[0])
Expand Down
48 changes: 45 additions & 3 deletions pkg/codegen/docs/constructor_syntax_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ func TestConstructorSyntaxGeneratorForSchema(t *testing.T) {
t.Parallel()
pkg := bindTestSchema(t, schema.PackageSpec{
Name: "test",
Types: map[string]schema.ComplexTypeSpec{
"test:index:ExampleEnum": {
ObjectTypeSpec: schema.ObjectTypeSpec{
Type: "string",
},
Enum: []schema.EnumValueSpec{
{Value: "first", Name: "first"},
{Value: "second", Name: "second"},
},
},
"test:index:ExampleNumericEnum": {
ObjectTypeSpec: schema.ObjectTypeSpec{
Type: "integer",
},
Enum: []schema.EnumValueSpec{
{Value: 10, Name: "first"},
{Value: 20, Name: "second"},
},
},
},
Resources: map[string]schema.ResourceSpec{
"test:index:First": {
InputProperties: map[string]schema.PropertySpec{
Expand All @@ -51,6 +71,16 @@ func TestConstructorSyntaxGeneratorForSchema(t *testing.T) {
Type: "boolean",
},
},
"fooEnum": {
TypeSpec: schema.TypeSpec{
Ref: "#/types/test:index:ExampleEnum",
},
},
"fooNumericEnum": {
TypeSpec: schema.TypeSpec{
Ref: "#/types/test:index:ExampleNumericEnum",
},
},
},
},
"test:index:Second": {
Expand Down Expand Up @@ -87,7 +117,9 @@ func TestConstructorSyntaxGeneratorForSchema(t *testing.T) {
var firstResource = new Test.First("firstResource", new()
{
FooBool = false,
FooEnum = Test.ExampleEnum.First,
FooInt = 0,
FooNumericEnum = Test.ExampleNumericEnum.First,
FooString = "string",
});
`)
Expand All @@ -107,7 +139,9 @@ var noInputsResource = new Test.NoInputs("noInputsResource");
equalPrograms(constructorSyntax.typescript, "test:index:First", `
const firstResource = new test.First("firstResource", {
fooBool: false,
fooEnum: test.ExampleEnum.First,
fooInt: 0,
fooNumericEnum: test.ExampleNumericEnum.First,
fooString: "string",
});`)
equalPrograms(constructorSyntax.typescript, "test:index:Second", `
Expand All @@ -122,7 +156,9 @@ const noInputsResource = new test.NoInputs("noInputsResource", {});
equalPrograms(constructorSyntax.python, "test:index:First", `
first_resource = test.First("firstResource",
foo_bool=False,
foo_enum=test.ExampleEnum.FIRST,
foo_int=0,
foo_numeric_enum=test.ExampleNumericEnum.FIRST,
foo_string="string")`)
equalPrograms(constructorSyntax.python, "test:index:Second", `
second_resource = test.Second("secondResource", bar_string="string")`)
Expand All @@ -134,9 +170,11 @@ no_inputs_resource = test.NoInputs("noInputsResource")
assert.Equal(t, expectedResources, len(constructorSyntax.golang.resources))
equalPrograms(constructorSyntax.golang, "test:index:First", `
_, err := test.NewFirst(ctx, "firstResource", &test.FirstArgs{
FooBool: pulumi.Bool(false),
FooInt: pulumi.Int(0),
FooString: pulumi.String("string"),
FooBool: pulumi.Bool(false),
FooEnum: index.ExampleEnumFirst,
FooInt: pulumi.Int(0),
FooNumericEnum: index.ExampleNumericEnumFirst,
FooString: pulumi.String("string"),
})`)

equalPrograms(constructorSyntax.golang, "test:index:Second", `
Expand All @@ -153,7 +191,9 @@ _, err = test.NewNoInputs(ctx, "noInputsResource", nil)
type: test:First
properties:
fooBool: false
fooEnum: first
fooInt: 0
fooNumericEnum: 10
fooString: string
`)

Expand All @@ -172,7 +212,9 @@ properties: {}
equalPrograms(constructorSyntax.java, "test:index:First", `
var firstResource = new First("firstResource", FirstArgs.builder()
.fooBool(false)
.fooEnum("first")
.fooInt(0)
.fooNumericEnum(10)
.fooString("string")
.build());
`)
Expand Down
23 changes: 15 additions & 8 deletions pkg/codegen/dotnet/gen_program_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,12 @@ func enumName(enum *model.EnumType) (string, string) {
et := e.(*schema.EnumType)
def, err := et.PackageReference.Definition()
contract.AssertNoErrorf(err, "error loading definition for package %q", et.PackageReference.Name())
namespaceMap := def.Language["csharp"].(CSharpPackageInfo).Namespaces
var namespaceMap map[string]string
pkgInfo, ok := def.Language["csharp"].(CSharpPackageInfo)
if ok {
namespaceMap = pkgInfo.Namespaces
}

namespace := namespaceName(namespaceMap, components[0])
if components[1] != "" && components[1] != "index" {
namespace += "." + namespaceName(namespaceMap, components[1])
Expand All @@ -406,20 +411,22 @@ func (g *generator) genIntrensic(w io.Writer, from model.Expression, to model.Ty
g.Fgenf(w, "%.v", from)
return
}
var convertFn string
switch {
case to.Type.Equals(model.StringType):
convertFn = fmt.Sprintf("System.Enum.Parse<%s.%s>", pkg, name)
default:

convertFn := func() string {
if to.Type.Equals(model.StringType) {
return fmt.Sprintf("System.Enum.Parse<%s.%s>", pkg, name)
}

panic(fmt.Sprintf(
"Unsafe enum conversions from type %s not implemented yet: %s => %s",
from.Type(), from, to))
}

if isOutput {
g.Fgenf(w, "%.v.Apply(%s)", from, convertFn)
g.Fgenf(w, "%.v.Apply(%s)", from, convertFn())
} else {
diag := pcl.GenEnum(to, from, g.genSafeEnum(w, to), func(from model.Expression) {
g.Fgenf(w, "%s(%v)", convertFn, from)
g.Fgenf(w, "%s(%v)", convertFn(), from)
})
if diag != nil {
g.diagnostics = append(g.diagnostics, diag)
Expand Down
6 changes: 3 additions & 3 deletions pkg/codegen/go/gen_program_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,10 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC
switch {
case to.Type.Equals(model.StringType):
underlyingType = "string"
case to.Type.Equals(model.IntType):
underlyingType = "int"
default:
panic(fmt.Sprintf(
"Unsafe enum conversions from type %s not implemented yet: %s => %s",
from.Type(), from, to))
underlyingType = "float64"
}
pkg, mod, typ, _ := pcl.DecomposeToken(to.Token, to.SyntaxNode().Range())
mod = g.getModOrAlias(pkg, mod, mod)
Expand Down
8 changes: 7 additions & 1 deletion pkg/codegen/pcl/binder_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,13 @@ func EnumMember(t *model.EnumType, value cty.Value) (*schema.Enum, bool) {
case t.Type.Equals(model.IntType):
f, _ := value.AsBigFloat().Int64()
for _, el := range src.Elements {
if el.Value.(int64) == f {
valueInt64, ok := el.Value.(int64)
if ok && valueInt64 == f {
return el, true
}

valueInt32, ok := el.Value.(int32)
if ok && int64(valueInt32) == f {
return el, true
}
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/codegen/python/gen_program_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,9 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC
}
var moduleNameOverrides map[string]string
if pkg, err := enum.(*schema.EnumType).PackageReference.Definition(); err == nil {
moduleNameOverrides = pkg.Language["python"].(PackageInfo).ModuleNameOverrides
if pkgInfo, ok := pkg.Language["python"].(PackageInfo); ok {
moduleNameOverrides = pkgInfo.ModuleNameOverrides
}
}
pkg := strings.ReplaceAll(components[0], "-", "_")
enumName := tokenToName(to.Token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,12 @@ var nurseryResource = new Plant.Tree.V1.Nursery("nurseryResource", new()

```go
example, err := tree.NewNursery(ctx, "nurseryResource", &tree.NurseryArgs{
Varieties: treev1.RubberTreeVarietyArray{
tree.RubberTreeVarietyBurgundy,
},
Sizes: treev1.TreeSizeMap{
"string": tree.TreeSizeSmall,
},
Varieties: treev1.RubberTreeVarietyArray{
tree.RubberTreeVarietyBurgundy,
},
Sizes: treev1.TreeSizeMap{
"string": tree.TreeSizeSmall,
},
})
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,20 @@ The following reference example uses placeholder values for all [input propertie
<pulumi-choosable type="language" values="csharp">

```csharp
Coming soon!
var rubberTreeResource = new Plant.Tree.V1.RubberTree("rubberTreeResource", new()
{
Diameter = Plant.Tree.V1.Diameter.Sixinch,
Type = Plant.Tree.V1.RubberTreeVariety.Burgundy,
Container = new Plant.Inputs.ContainerArgs
{
Size = Plant.ContainerSize.FourInch,
Brightness = Plant.ContainerBrightness.ZeroPointOne,
Color = Plant.ContainerColor.Red,
Material = "string",
},
Farm = Plant.Tree.V1.Farm.Pulumi_Planters_Inc_,
Size = Plant.Tree.V1.TreeSize.Small,
});
```

</pulumi-choosable>
Expand All @@ -243,7 +256,18 @@ Coming soon!
<pulumi-choosable type="language" values="go">

```go
Coming soon!
example, err := tree.NewRubberTree(ctx, "rubberTreeResource", &tree.RubberTreeArgs{
Diameter: tree.DiameterSixinch,
Type: tree.RubberTreeVarietyBurgundy,
Container: &plantprovider.ContainerArgs{
Size: plant.ContainerSizeFourInch,
Brightness: plant.ContainerBrightnessZeroPointOne,
Color: pulumi.String(plant.ContainerColorRed),
Material: pulumi.String("string"),
},
Farm: pulumi.String(tree.Farm_Pulumi_Planters_Inc_),
Size: tree.TreeSizeSmall,
})
```

</pulumi-choosable>
Expand All @@ -255,11 +279,11 @@ Coming soon!

```java
var rubberTreeResource = new RubberTree("rubberTreeResource", RubberTreeArgs.builder()
.diameter("sixinch")
.diameter(6)
.type("Burgundy")
.container(ContainerArgs.builder()
.size("FourInch")
.brightness("ZeroPointOne")
.size(4)
.brightness(0.1)
.color("red")
.material("string")
.build())
Expand All @@ -276,7 +300,17 @@ var rubberTreeResource = new RubberTree("rubberTreeResource", RubberTreeArgs.bui
<pulumi-choosable type="language" values="python">

```python
Coming soon!
rubber_tree_resource = plant.tree.v1.RubberTree("rubberTreeResource",
diameter=plant.tree.v1.Diameter.SIXINCH,
type=plant.tree.v1.RubberTreeVariety.BURGUNDY,
container=plant.ContainerArgs(
size=plant.ContainerSize.FOUR_INCH,
brightness=plant.ContainerBrightness.ZERO_POINT_ONE,
color=plant.ContainerColor.RED,
material="string",
),
farm=plant.tree.v1.Farm.PULUMI_PLANTERS_INC_,
size=plant.tree.v1.TreeSize.SMALL)
```

</pulumi-choosable>
Expand All @@ -287,7 +321,18 @@ Coming soon!
<pulumi-choosable type="language" values="typescript">

```typescript
Coming soon!
const rubberTreeResource = new plant.tree.v1.RubberTree("rubberTreeResource", {
diameter: plant.tree.v1.Diameter.Sixinch,
type: plant.tree.v1.RubberTreeVariety.Burgundy,
container: {
size: plant.ContainerSize.FourInch,
brightness: plant.ContainerBrightness.ZeroPointOne,
color: plant.ContainerColor.Red,
material: "string",
},
farm: plant.tree.v1.Farm.Pulumi_Planters_Inc_,
size: plant.tree.v1.TreeSize.Small,
});
```

</pulumi-choosable>
Expand All @@ -301,11 +346,11 @@ Coming soon!
type: plant:tree/v1:RubberTree
properties:
container:
brightness: ZeroPointOne
brightness: 0.1
color: red
material: string
size: FourInch
diameter: sixinch
size: 4
diameter: 6
farm: Pulumi Planters Inc.
size: small
type: Burgundy
Expand Down