Skip to content

feat: add GetX/SetX accessors to generated Go models#160

Open
erikmiller-gusto wants to merge 1 commit into
crossplane:mainfrom
erikmiller-gusto:main
Open

feat: add GetX/SetX accessors to generated Go models#160
erikmiller-gusto wants to merge 1 commit into
crossplane:mainfrom
erikmiller-gusto:main

Conversation

@erikmiller-gusto

@erikmiller-gusto erikmiller-gusto commented Jun 26, 2026

Copy link
Copy Markdown

Add accessor/setter methods to generated Go models

Problem

The Go model generator produces structs that expose only fields:

type XAccountScaffoldSpec struct {
    Parameters *XAccountScaffoldSpecParameters `json:"parameters,omitempty"`
    // ...
}

Because the generated types have no methods, consumers can't abstract over resources with interfaces or write generic code that operates on "any resource with field X". Every field is a pointer (the goRemoveRequired mutator strips required, so oapi-codegen emits everything as optional *T).

What this does

Generates GetX/SetX accessor methods for every field of every generated struct, so consumers can define their own interfaces and satisfy them structurally:

func (o *XAccountScaffold) GetSpec() *XAccountScaffoldSpec  { return o.Spec }
func (o *XAccountScaffold) SetSpec(v *XAccountScaffoldSpec) { o.Spec = v }

// consumer code:
type SpecAccessor interface {
    GetSpec() *v1alpha1.XAccountScaffoldSpec
    SetSpec(*v1alpha1.XAccountScaffoldSpec)
}
var _ SpecAccessor = &v1alpha1.XAccountScaffold{}

Testing

  • Unit test over *string / *[]T / *map[...] / *Struct fields, including alias-skipping.
  • Integration test asserting real CRD models and shared k8s ObjectMeta get accessors.
  • Compile gate: materializes the generated module to disk, adds a consumer that uses an accessor through an interface, and go builds it — proving the output compiles and the goal (interfaces over resources) actually works. Skips gracefully if no Go toolchain is present.

go test ./... → all passing. go build, gofmt, go vet clean.

@erikmiller-gusto erikmiller-gusto requested review from a team, jcogilvie and tampakrap as code owners June 26, 2026 17:05
@erikmiller-gusto erikmiller-gusto requested review from phisco and removed request for a team June 26, 2026 17:05
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@erikmiller-gusto, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 2 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20b15c75-489d-4252-932e-1c370801244c

📥 Commits

Reviewing files that changed from the base of the PR and between f0c8846 and e38b155.

📒 Files selected for processing (12)
  • cmd/crossplane/config/help/config.md
  • cmd/crossplane/config/set.go
  • cmd/crossplane/function/generate.go
  • cmd/crossplane/function/generate_test.go
  • cmd/crossplane/main.go
  • cmd/crossplane/project/build.go
  • cmd/crossplane/project/run.go
  • internal/config/config.go
  • internal/schemas/generator/accessors.go
  • internal/schemas/generator/accessors_test.go
  • internal/schemas/generator/go.go
  • internal/schemas/generator/interface.go
📝 Walkthrough

Walkthrough

The generator now supports optional getter and setter emission for Go models, with config and CLI wiring to toggle it and tests covering generation and compilation.

Changes

Go model accessor feature

Layer / File(s) Summary
Feature flag plumbing
internal/config/config.go, internal/schemas/generator/interface.go, internal/schemas/generator/go.go
A new config flag and generator option are threaded through schema generation, and Go model generation conditionally carries an accessor flag into the code path.
Accessor emitter
internal/schemas/generator/accessors.go
Parses generated Go source, finds struct fields and existing methods, and appends Get<Field> and Set<Field> methods before formatting.
CLI wiring
cmd/crossplane/main.go, cmd/crossplane/config/set.go, cmd/crossplane/config/help/config.md, cmd/crossplane/function/generate.go, cmd/crossplane/function/generate_test.go, cmd/crossplane/project/build.go, cmd/crossplane/project/run.go
The CLI loads and binds config, exposes the new feature key in config help and set commands, and passes the flag into generation commands and their tests.
Accessor validation tests
internal/schemas/generator/accessors_test.go
Adds helpers and tests for default-off behavior, generated accessor presence, compilation of generated output, pointer handling, alias skipping, and duplicate-method avoidance.

Sequence Diagram(s)

sequenceDiagram
  participant Config as internal/config.Config
  participant GenerateCmd as cmd/crossplane/function.generateCmd
  participant ProjectCmd as cmd/crossplane/project.buildCmd
  participant GoGen as internal/schemas/generator.goGenerator
  participant AddAccessors as internal/schemas/generator.addAccessors

  Config->>GenerateCmd: provide GenerateGoModelAccessors
  Config->>ProjectCmd: provide GenerateGoModelAccessors
  GenerateCmd->>GoGen: filter schemas with feature flag
  ProjectCmd->>GoGen: build schema generation with feature flag
  GoGen->>AddAccessors: append accessors when enabled
  AddAccessors-->>GoGen: formatted source or wrapped error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • jcogilvie
  • tampakrap
  • jbw976
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, under 72 characters, and clearly describes the accessor-generation change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Breaking Changes ✅ Passed Only additive cmd/config wiring was added; no public flags/fields were removed, renamed, or made required.
Feature Gate Requirement ✅ Passed PASS: accessor generation is gated by cfg.Features.GenerateGoModelAccessors (default false) and wired through AllLanguages/WithGoModelAccessors in all schema-generation entry points.
Description check ✅ Passed The description clearly matches the changes: it explains adding Get/Set accessors to generated Go models and the associated tests.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/schemas/generator/accessors_test.go (1)

111-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Thanks for the thorough coverage here — would you be open to nudging these toward the repo's table-driven conventions?

These tests read well, and I appreciate the compile gate especially. To align with our test conventions, would it be worth giving the case tables a reason field and running them as subtests via t.Run(name, ...), and using cmp.Diff for the type comparisons in TestAddAccessors (e.g. on the want/got method maps)? That tends to give nicer failure output and matches the rest of the suite.

No action needed if you'd rather defer, but flagging since it's a documented convention. As per path instructions: "Enforce table-driven test structure ... args/want pattern, use cmp.Diff ... Check for proper test case naming and reason fields."

Also applies to: 226-282

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/schemas/generator/accessors_test.go` around lines 111 - 143, The
tests here should be nudged into the repo’s table-driven style: update the cases
in TestGenerateFromCRDIncludesAccessors and the related TestAddAccessors
coverage to use named subtests with t.Run and include a reason field in each
table entry for clearer intent. For the method-map/type assertions in
TestAddAccessors, switch from direct comparisons to cmp.Diff on the want/got
values to improve failure output and match the suite’s conventions. Use the
existing test function names and method-map helpers as the anchor points when
refactoring.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/schemas/generator/accessors_test.go`:
- Around line 175-224: The compile gate in
TestGeneratedModelsCompileWithAccessors is not hermetic because the generated
module path dev.crossplane.io/models must be resolved externally during go
build. Update the generator that writes the materialized go.mod so the generated
module can resolve locally, ideally by adding a replace directive that points
dev.crossplane.io/models to the on-disk output directory, or otherwise make the
offline CI requirement explicit if that’s the intended mechanism. Use the
TestGeneratedModelsCompileWithAccessors flow and the generated go.mod output
from goGenerator{}.GenerateFromCRD as the places to verify the fix.

In `@internal/schemas/generator/accessors.go`:
- Around line 34-39: `addAccessors`/`writeStructAccessors` currently emit
`GetX`/`SetX` methods without checking for existing methods, which can collide
with `oapi-codegen`-generated accessors or union helpers. Update
`writeStructAccessors` to inspect the target type’s existing method set before
generating each accessor, and skip any `GetX`/`SetX` whose name is already
present on that struct. Keep the check localized around `writeStructAccessors`
so the generator remains defensive against `additionalProperties` and union-type
method names like `GetAdditionalProperties`, `As`, or `From`.

---

Nitpick comments:
In `@internal/schemas/generator/accessors_test.go`:
- Around line 111-143: The tests here should be nudged into the repo’s
table-driven style: update the cases in TestGenerateFromCRDIncludesAccessors and
the related TestAddAccessors coverage to use named subtests with t.Run and
include a reason field in each table entry for clearer intent. For the
method-map/type assertions in TestAddAccessors, switch from direct comparisons
to cmp.Diff on the want/got values to improve failure output and match the
suite’s conventions. Use the existing test function names and method-map helpers
as the anchor points when refactoring.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 412e1a53-fb0f-44e9-977e-1b7d63eed6e7

📥 Commits

Reviewing files that changed from the base of the PR and between d0fecff and de804fc.

📒 Files selected for processing (3)
  • internal/schemas/generator/accessors.go
  • internal/schemas/generator/accessors_test.go
  • internal/schemas/generator/go.go

Comment thread internal/schemas/generator/accessors_test.go
Comment thread internal/schemas/generator/accessors.go
@erikmiller-gusto erikmiller-gusto force-pushed the main branch 2 times, most recently from fb2efaf to f0c8846 Compare June 26, 2026 19:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/crossplane/main.go`:
- Around line 129-130: The `build`, `run`, and `generate` command entrypoints
currently assume `cfg` is always injected by Kong, which could panic if they are
invoked programmatically or in tests; add a nil check at the start of each `Run`
method in `cmd/crossplane/project/build.go`, `cmd/crossplane/project/run.go`,
and `cmd/crossplane/function/generate.go`, and return a clear error before any
`cfg.Features` access when `cfg` is nil.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e91df577-976e-4c3f-b795-54476785f9e3

📥 Commits

Reviewing files that changed from the base of the PR and between de804fc and f0c8846.

📒 Files selected for processing (12)
  • cmd/crossplane/config/help/config.md
  • cmd/crossplane/config/set.go
  • cmd/crossplane/function/generate.go
  • cmd/crossplane/function/generate_test.go
  • cmd/crossplane/main.go
  • cmd/crossplane/project/build.go
  • cmd/crossplane/project/run.go
  • internal/config/config.go
  • internal/schemas/generator/accessors.go
  • internal/schemas/generator/accessors_test.go
  • internal/schemas/generator/go.go
  • internal/schemas/generator/interface.go
✅ Files skipped from review due to trivial changes (1)
  • cmd/crossplane/config/help/config.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/schemas/generator/accessors.go

Comment thread cmd/crossplane/main.go
Comment on lines +129 to +130
// Bind the loaded config so commands can read feature flags at runtime.
kong.Bind(cfg),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all Run methods that take *config.Config and any direct (non-kong) callers in tests.
rg -nP --type=go '\)\s*Run\([^)]*\*config\.Config' -C1
echo '--- direct Run callers in tests ---'
rg -nP --type=go '\.Run\(' -g '**/*_test.go' -C2

Repository: crossplane/cli

Length of output: 152


Binding cfg is correct, but adding a nil guard prevents future panics

The search confirms build, run, and generate rely on the kong.Bind(cfg) at line 130, and no direct .Run() calls were found in tests, so the current path is safe.

However, to make these commands robust against future programmatic invocation or test changes where Kong might not inject cfg:

  1. Add a simple nil check at the start of Run methods in cmd/crossplane/project/build.go, cmd/crossplane/project/run.go, and cmd/crossplane/function/generate.go.
  2. Return an error if cfg is nil to avoid a panic on cfg.Features access.

This defensive pattern ensures the command fails gracefully with a clear message rather than crashing silently if called outside the Kong pipeline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/crossplane/main.go` around lines 129 - 130, The `build`, `run`, and
`generate` command entrypoints currently assume `cfg` is always injected by
Kong, which could panic if they are invoked programmatically or in tests; add a
nil check at the start of each `Run` method in
`cmd/crossplane/project/build.go`, `cmd/crossplane/project/run.go`, and
`cmd/crossplane/function/generate.go`, and return a clear error before any
`cfg.Features` access when `cfg` is nil.

Generate pointer-in/pointer-out accessor methods for every field of every
struct in the generated Go models, so consumers can abstract over resources
with interfaces and generics. Folded into generateGo as a single AST
post-process step so it applies uniformly across all generation paths.

Gated behind a new features.generateGoModelAccessors config flag (off by
default), threaded from config through the project build/run and function
generate commands into the Go generator. Skips any GetX/SetX whose name
already exists on a type, so the accessors never collide with methods
oapi-codegen already emits (e.g. union or additionalProperties helpers).

Verified by a compile gate that builds the generated module with a consumer
that uses an accessor through an interface; the gate skips gracefully when
module dependencies cannot be resolved offline.

Signed-off-by: Erik Miller <erik.miller@gusto.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant