From 927e9d4a69d641d86027d4a25455ca80b9e496a2 Mon Sep 17 00:00:00 2001 From: aq17 Date: Thu, 1 Dec 2022 14:41:24 -0800 Subject: [PATCH] Improve error msg for invalid enum values on convert --- ...invalid-enum-values-on-pulumi-convert.yaml | 4 ++ pkg/codegen/dotnet/gen_program_expressions.go | 5 ++- pkg/codegen/go/gen_program_expressions.go | 5 ++- pkg/codegen/nodejs/gen_program_expressions.go | 5 ++- pkg/codegen/pcl/binder_schema.go | 44 ++++++++++++++++++- pkg/codegen/pcl/binder_schema_test.go | 34 ++++++++++++++ pkg/codegen/python/gen_program_expressions.go | 5 ++- 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 changelog/pending/20221201--programgen--improve-error-message-for-invalid-enum-values-on-pulumi-convert.yaml diff --git a/changelog/pending/20221201--programgen--improve-error-message-for-invalid-enum-values-on-pulumi-convert.yaml b/changelog/pending/20221201--programgen--improve-error-message-for-invalid-enum-values-on-pulumi-convert.yaml new file mode 100644 index 000000000000..1b8b163cc48a --- /dev/null +++ b/changelog/pending/20221201--programgen--improve-error-message-for-invalid-enum-values-on-pulumi-convert.yaml @@ -0,0 +1,4 @@ +changes: +- type: fix + scope: programgen + description: Improve error message for invalid enum values on `pulumi convert`. diff --git a/pkg/codegen/dotnet/gen_program_expressions.go b/pkg/codegen/dotnet/gen_program_expressions.go index 9530697148ac..4f9f88cdb081 100644 --- a/pkg/codegen/dotnet/gen_program_expressions.go +++ b/pkg/codegen/dotnet/gen_program_expressions.go @@ -335,9 +335,12 @@ func (g *generator) genIntrensic(w io.Writer, from model.Expression, to model.Ty if isOutput { g.Fgenf(w, "%.v.Apply(%s)", from, convertFn) } else { - pcl.GenEnum(to, from, g.genSafeEnum(w, to), func(from model.Expression) { + diag := pcl.GenEnum(to, from, g.genSafeEnum(w, to), func(from model.Expression) { g.Fgenf(w, "%s(%v)", convertFn, from) }) + if diag != nil { + g.diagnostics = append(g.diagnostics, diag) + } } default: g.Fgenf(w, "%.v", from) // <- probably wrong w.r.t. precedence diff --git a/pkg/codegen/go/gen_program_expressions.go b/pkg/codegen/go/gen_program_expressions.go index a116d3eb3332..6660137df39d 100644 --- a/pkg/codegen/go/gen_program_expressions.go +++ b/pkg/codegen/go/gen_program_expressions.go @@ -205,9 +205,12 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC from, enumTag, underlyingType) return } - pcl.GenEnum(to, from, g.genSafeEnum(w, to), func(from model.Expression) { + diag := pcl.GenEnum(to, from, g.genSafeEnum(w, to), func(from model.Expression) { g.Fgenf(w, "%s(%v)", enumTag, from) }) + if diag != nil { + g.diagnostics = append(g.diagnostics, diag) + } return } switch arg := from.(type) { diff --git a/pkg/codegen/nodejs/gen_program_expressions.go b/pkg/codegen/nodejs/gen_program_expressions.go index 94f9bc9a8438..c4b142f6c195 100644 --- a/pkg/codegen/nodejs/gen_program_expressions.go +++ b/pkg/codegen/nodejs/gen_program_expressions.go @@ -336,13 +336,16 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC if isOutput { g.Fgenf(w, "%.v.apply((x) => %s[x])", from, enum) } else { - pcl.GenEnum(to, from, func(member *schema.Enum) { + diag := pcl.GenEnum(to, from, func(member *schema.Enum) { memberTag, err := enumMemberName(tokenToName(to.Token), member) contract.AssertNoErrorf(err, "Failed to get member name on enum '%s'", enum) g.Fgenf(w, "%s.%s", enum, memberTag) }, func(from model.Expression) { g.Fgenf(w, "%s[%.v]", enum, from) }) + if diag != nil { + g.diagnostics = append(g.diagnostics, diag) + } } } else { g.Fgenf(w, "%v", from) diff --git a/pkg/codegen/pcl/binder_schema.go b/pkg/codegen/pcl/binder_schema.go index 3f0867a9b76d..2d0b7d2df44c 100644 --- a/pkg/codegen/pcl/binder_schema.go +++ b/pkg/codegen/pcl/binder_schema.go @@ -16,6 +16,7 @@ package pcl import ( "fmt" + "strings" "sync" "github.com/blang/semver" @@ -549,7 +550,7 @@ func GenEnum( from model.Expression, safeEnum func(member *schema.Enum), unsafeEnum func(from model.Expression), -) { +) *hcl.Diagnostic { known := cty.NilVal if from, ok := from.(*model.TemplateExpression); ok && len(from.Parts) == 1 { if from, ok := from.Parts[0].(*model.LiteralValueExpression); ok { @@ -566,8 +567,47 @@ func GenEnum( contract.Assertf(ok, "We have determined %s is a safe enum, which we define as "+ "being able to calculate a member for", t) - safeEnum(member) + if member != nil { + safeEnum(member) + } else { + unsafeEnum(from) + knownVal := strings.Split(strings.Split(known.GoString(), "(")[1], ")")[0] + diag := &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("%v is not a valid value of the enum \"%v\"", knownVal, t.Token), + } + if members := enumMemberValues(t); len(members) > 0 { + diag.Detail = fmt.Sprintf("Valid members are %v", listToString(members)) + } + return diag + } } else { unsafeEnum(from) } + return nil +} + +func enumMemberValues(t *model.EnumType) []interface{} { + srcBase, ok := GetSchemaForType(t) + if !ok { + return nil + } + src := srcBase.(*schema.EnumType) + members := make([]interface{}, len(src.Elements)) + for i, el := range src.Elements { + members[i] = el.Value + } + return members +} + +func listToString(l []interface{}) string { + vals := "" + for i, v := range l { + if i == 0 { + vals = fmt.Sprintf("\"%v\"", v) + } else { + vals = fmt.Sprintf("%s, \"%v\"", vals, v) + } + } + return vals } diff --git a/pkg/codegen/pcl/binder_schema_test.go b/pkg/codegen/pcl/binder_schema_test.go index d3abf853ea4a..b3e1d3e31a87 100644 --- a/pkg/codegen/pcl/binder_schema_test.go +++ b/pkg/codegen/pcl/binder_schema_test.go @@ -4,9 +4,12 @@ import ( "path/filepath" "testing" + "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/pkg/v3/codegen/testing/utils" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" + "github.com/stretchr/testify/assert" + "github.com/zclconf/go-cty/cty" ) var testdataPath = filepath.Join("..", "testing", "test", "testdata") @@ -19,3 +22,34 @@ func BenchmarkLoadPackage(b *testing.B) { contract.AssertNoError(err) } } + +func TestGenEnum(t *testing.T) { + t.Parallel() + enum := &model.EnumType{ + Elements: []cty.Value{ + cty.StringVal("foo"), + cty.StringVal("bar"), + }, + Type: model.StringType, + Token: "my:enum", + Annotations: []interface{}{ + enumSchemaType{ + Type: &schema.EnumType{Elements: []*schema.Enum{{Value: "foo"}, {Value: "bar"}}}, + }, + }, + } + safeEnumFunc := func(member *schema.Enum) {} + unsafeEnumFunc := func(from model.Expression) {} + + d := GenEnum(enum, &model.LiteralValueExpression{ + Value: cty.StringVal("foo"), + }, safeEnumFunc, unsafeEnumFunc) + assert.Nil(t, d) + + d = GenEnum(enum, &model.LiteralValueExpression{ + Value: cty.StringVal("Bar"), + }, safeEnumFunc, unsafeEnumFunc) + assert.Equal(t, d.Summary, `"Bar" is not a valid value of the enum "my:enum"`) + assert.Equal(t, d.Detail, `Valid members are "foo", "bar"`) + +} diff --git a/pkg/codegen/python/gen_program_expressions.go b/pkg/codegen/python/gen_program_expressions.go index 7a2cb9a3698d..fc12aec67287 100644 --- a/pkg/codegen/python/gen_program_expressions.go +++ b/pkg/codegen/python/gen_program_expressions.go @@ -247,7 +247,7 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC if isOutput { g.Fgenf(w, "%.v.apply(lambda x: %s.%s(x))", from, pkg, enumName) } else { - pcl.GenEnum(to, from, func(member *schema.Enum) { + diag := pcl.GenEnum(to, from, func(member *schema.Enum) { tag := member.Name if tag == "" { tag = fmt.Sprintf("%v", member.Value) @@ -258,6 +258,9 @@ func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionC }, func(from model.Expression) { g.Fgenf(w, "%s.%s(%.v)", pkg, enumName, from) }) + if diag != nil { + g.diagnostics = append(g.diagnostics, diag) + } } default: switch arg := from.(type) {