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
37 changes: 37 additions & 0 deletions internal/generate/fluent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,40 @@ func TestUnit_Generate_FluentTestHelperRebasesTheChain(t *testing.T) {
t.Errorf("helper call = %q, want %q", v.Call, want)
}
}

// TestUnit_Generate_ImmutableIsCreateAuthoritative proves an immutable
// attribute reaches the create body and never a split update body: the
// endpoint tests' update models declare a fraction of their create fields,
// and RequiresReplace owns changes to the rest.
func TestUnit_Generate_ImmutableIsCreateAuthoritative(t *testing.T) {
t.Parallel()

bp := fluentPilot(t)
r := &bp.Resources[0]
r.Binding.Body.UpdateRequestType = "models.Tags_API_TagUpdate"
r.Binding.Body.UpdateConstructorExpr = "models.NewTags_API_TagUpdate()"

yes := true
for i := range r.Schema.Attributes {
if r.Schema.Attributes[i].Name == "key" {
r.Schema.Attributes[i].Behaviour.Immutable = &yes
}
}

v, err := Resource(bp, bp.Resources[0], Options{})
if err != nil {
t.Fatalf("Resource: %v", err)
}
if v.Construct.Update == nil {
t.Fatal("a split update body must produce an update target")
}

create := strings.Join(v.Construct.Assignments, "\n")
update := strings.Join(v.Construct.Update.Assignments, "\n")
if !strings.Contains(create, "SetKey(") {
t.Errorf("the create body must keep the immutable field:\n%s", create)
}
if strings.Contains(update, "SetKey(") {
t.Errorf("a split update body must omit the immutable field:\n%s", update)
}
}
11 changes: 10 additions & 1 deletion internal/generate/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@
}

// Resource builds the view for one resource.
func Resource(bp blueprint.Blueprint, r blueprint.Resource, opts Options) (ResourceView, error) {

Check failure on line 392 in internal/generate/render.go

View workflow job for this annotation

GitHub Actions / ✨ Run golangci-lint

cyclomatic complexity 33 of func `Resource` is high (> 30) (gocyclo)
// One import set per emitted file, since Go rejects an unused import and the
// five files need genuinely different sets.
var (
Expand Down Expand Up @@ -480,7 +480,7 @@
// Evidence-derived rules render beside the hand-declared ones: to the framework they
// are the same kind of thing, and a reader of ConfigValidators should meet both.
conditional, _ := conditionalValidators(r, impResource)
v.ConfigValidators = append(cvs, conditional...)

Check failure on line 483 in internal/generate/render.go

View workflow job for this annotation

GitHub Actions / ✨ Run golangci-lint

appendAssign: append result not assigned to the same slice (gocritic)

v.Interfaces = interfaces(r)

Expand Down Expand Up @@ -1189,7 +1189,16 @@
continue
}

if v.Update != nil {
// An immutable attribute is create-authoritative: a split update body
// frequently cannot carry it at all -- the endpoint tests' update
// models declare a fraction of their create fields -- and even where
// it could, sending an unchangeable field on update invites the API to
// refuse what the plan already forbids. RequiresReplace (driven by the
// same Behaviour.Immutable) owns changes; the update body simply omits
// the field. The create body below keeps it.
immutable := a.Behaviour.Immutable != nil && *a.Behaviour.Immutable

if v.Update != nil && !immutable {
updateCall := a.Wire.Expand
if a.Wire.UpdateExpand != nil {
updateCall = a.Wire.UpdateExpand
Expand Down
5 changes: 4 additions & 1 deletion internal/sdkbind/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,10 @@ func verifyWireFields(l *Loader, res blueprint.Resource, ok bodyTypesOK, r *Repo
if !a.Wire.SkipExpand && a.Wire.Expand != nil && request != "" {
checks[request] = "expand"
}
if !a.Wire.SkipExpand && a.Wire.Expand != nil && update != "" {
// An immutable attribute is create-authoritative: the emitter omits it
// from a split update body, so the update model owes it nothing.
immutable := a.Behaviour.Immutable != nil && *a.Behaviour.Immutable
if !a.Wire.SkipExpand && a.Wire.Expand != nil && update != "" && !immutable {
checks[update] = "expand (update body)"
}
if !a.Wire.SkipFlatten && a.Wire.Flatten != nil && response != "" {
Expand Down
Loading