Skip to content
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
17 changes: 14 additions & 3 deletions internal/audit/plan/synth.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ func (sy synth) value(field string, s *specmodel.Schema, depth int, topLevel boo
return nil, false
}
r := s.Resolved()
// A name must be unique per run and must carry the prefix cleanup matches
// on, so the invented token beats a declared example. An example is a
// value the API accepted once; an API that requires a name to be unique
// refuses it every time after, and an object created under it is invisible
// to the prefix pass and stays in the tenant. An enum, a format or a
// pattern leaves the ordinary priority alone: the token satisfies none of
// those shapes, so a field carrying one is not a field to invent a name
// for.
if NameBearing(field) && len(r.Enum) == 0 && r.Format == "" && r.Pattern == "" {
return sy.nameToken(field), true
}
if r.Example != nil {
return r.Example, true
}
Expand Down Expand Up @@ -139,7 +150,7 @@ func (sy synth) formatValue(format string) (any, bool) {
func (sy synth) typeValue(field string, r *specmodel.Schema, depth int) (any, bool) {
switch {
case r.Type == "string":
if nameBearing(field) {
if NameBearing(field) {
return sy.nameToken(field), true
}
return "sample-" + field, true
Expand Down Expand Up @@ -268,9 +279,9 @@ func (sy synth) nameToken(field string) string {
return sy.prefix + "-" + RunIDToken + "-" + sy.entity + "-" + field
}

// nameBearing reports whether a string field names its object — the
// NameBearing reports whether a string field names its object — the
// fields whose synthesized values must carry the cleanup prefix.
func nameBearing(field string) bool {
func NameBearing(field string) bool {
lf := strings.ToLower(field)
for _, suffix := range []string{"name", "title", "label"} {
if lf == suffix || strings.HasSuffix(lf, suffix) {
Expand Down
42 changes: 42 additions & 0 deletions internal/audit/plan/synth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,45 @@ components:
t.Errorf("minimal body carries the optional self-reference: %#v", body)
}
}

func TestUnit_Plan_AnInventedNameOutranksADeclaredExample(t *testing.T) {
spec := resourceSpec(
"name, gated_name, shaped_name, summary",
` name:
type: string
example: My thing
gated_name:
type: string
example: My thing
enum: [alpha, beta]
shaped_name:
type: string
example: my.thing
pattern: "^[a-z.]+$"
summary:
type: string
example: some words
`)
p := mustDerive(t, loadDoc(t, spec), testConfig(), nil)
body := entityByKey(t, p, "thing").Steps[0].Body

// The example is a value the API took once; a unique name refuses it
// thereafter, and an object created under it never carries the prefix
// cleanup matches on.
if got := body["name"]; got != "tfpfgen-"+RunIDToken+"-thing-name" {
t.Errorf("name = %#v, want the invented token", got)
}
// A field an enum or a pattern constrains keeps the ordinary priority, in
// which a declared example already outranks both: the token satisfies
// neither shape, so such a field is not one to invent a name for.
if got := body["gated_name"]; got != "My thing" {
t.Errorf("gated_name = %#v, want the declared example", got)
}
if got := body["shaped_name"]; got != "my.thing" {
t.Errorf("shaped_name = %#v, want the declared example", got)
}
// A field that names nothing keeps its example.
if got := body["summary"]; got != "some words" {
t.Errorf("summary = %#v, want the declared example", got)
}
}
17 changes: 17 additions & 0 deletions internal/audit/run/adjust_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,23 @@ func TestUnit_Strategize_SynthesisHelpers(t *testing.T) {
if v := synthValue(strategy.SynthHint{Field: "label", Type: "string"}, "ent", "tfpfgen"); v != "tfpfgen-<runid>-ent-label" {
t.Errorf("name-bearing string = %v", v)
}
// A name-bearing field takes the invented token over a declared example:
// this is the path a live run synthesises from, and an API that requires a
// unique name refuses the example every run after the first.
if v := synthValue(strategy.SynthHint{Field: "name", Type: "string", Example: "My thing"},
"ent", "tfpfgen"); v != "tfpfgen-<runid>-ent-name" {
t.Errorf("a name-bearing example = %#v, want the invented token", v)
}
// A field an enum, a format or a pattern constrains is not one to invent a
// name for, so the ordinary priority stands.
if v := synthValue(strategy.SynthHint{Field: "name", Type: "string", Example: "My thing",
Pattern: "^[A-Za-z ]+$"}, "ent", "tfpfgen"); v != "My thing" {
t.Errorf("a constrained name = %#v, want the declared example", v)
}
if v := synthValue(strategy.SynthHint{Field: "name", Type: "string", Format: "email"},
"ent", "tfpfgen"); v != "tfpfgen-<runid>@example.invalid" {
t.Errorf("a formatted name = %#v, want the format-driven value", v)
}
if v := synthValue(strategy.SynthHint{Field: "color", Type: "string"}, "ent", "p"); v != "sample-color" {
t.Errorf("plain string = %v", v)
}
Expand Down
30 changes: 14 additions & 16 deletions internal/audit/run/strategize.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"fmt"
"math/bits"
"strconv"
"strings"

"github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/audit/observe"
"github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/audit/plan"
Expand Down Expand Up @@ -336,7 +335,7 @@ func (r *runner) synthField(ent *entityState, field string) any {
return synthValue(h, ent.plan.Entity, r.opts.NamePrefix)
}
}
if nameBearing(field) {
if plan.NameBearing(field) {
return nameToken(r.opts.NamePrefix, ent.plan.Entity, field)
}
return "sample-" + field
Expand All @@ -346,6 +345,17 @@ func (r *runner) synthField(ent *entityState, field string) any {
// plan package's synthesis: example, then default, then the first enum member,
// then a format-driven value, then a type-driven one.
func synthValue(h strategy.SynthHint, entity, prefix string) any {
// A name must be unique per run and must carry the prefix cleanup matches
// on, so the invented token beats a declared example. An example is a
// value the API accepted once; an API that requires a name to be unique
// refuses it every time after, and an object created under it is invisible
// to the prefix pass and stays in the tenant. An enum, a format or a
// pattern leaves the ordinary priority alone: the token satisfies none of
// those shapes, so a field carrying one is not a field to invent a name
// for.
if plan.NameBearing(h.Field) && len(h.Enum) == 0 && h.Format == "" && h.Pattern == "" {
return nameToken(prefix, entity, h.Field)
}
if h.Example != nil {
return h.Example
}
Expand All @@ -370,12 +380,12 @@ func synthValue(h strategy.SynthHint, entity, prefix string) any {
case "object":
return map[string]any{}
case "string":
if nameBearing(h.Field) {
if plan.NameBearing(h.Field) {
return nameToken(prefix, entity, h.Field)
}
return "sample-" + h.Field
default:
if nameBearing(h.Field) {
if plan.NameBearing(h.Field) {
return nameToken(prefix, entity, h.Field)
}
return "sample-" + h.Field
Expand Down Expand Up @@ -540,18 +550,6 @@ func nameToken(prefix, entity, field string) string {
return prefix + "-" + plan.RunIDToken + "-" + entity + "-" + field
}

// nameBearing reports whether a string field names its object — the fields
// whose synthesised values must carry the cleanup prefix.
func nameBearing(field string) bool {
lf := strings.ToLower(field)
for _, suffix := range []string{"name", "title", "label"} {
if lf == suffix || strings.HasSuffix(lf, suffix) {
return true
}
}
return false
}

// bisectionAllowance is the extra createMaximal attempts worth reserving to
// halve the optional set down to one field, plus the retry that confirms it.
func bisectionAllowance(optional int) int {
Expand Down