Skip to content

refactor(common): make DatabaseDetails/CacheDetails pointer-only - #1525

Merged
cristim merged 5 commits into
mainfrom
refactor/service-details-pointer-only
Jul 27, 2026
Merged

cristim merged 5 commits into
mainfrom
refactor/service-details-pointer-only

Conversation

@cristim

@cristim cristim commented Jul 27, 2026 •

Copy link
Copy Markdown
Member

Summary

Revives the valuable part of a stale, unshipped refactor found in worktree agent-a2b3490bc2d3f0c08 (~1 month old, never landed). That worktree bundled three ideas; after assessing each against current main, only one turned out to be a genuine win. This PR implements only that part, built fresh against current main (not built from the stale diff).

What's in scope: common.DatabaseDetails / common.CacheDetails were the only ServiceDetails producers in the codebase constructed inconsistently -- Azure's SQL/Redis reservation converters built them as plain values, while AWS, the CSV loader, and the JSON codec (used on the purchase-execution DB round trip) always built pointers. That inconsistency forced 8 separate consumer functions (in cmd/, internal/scheduler, providers/aws/recommendations, pkg/common) to defensively type-switch on both T and *T for the same two types.

  • Commit 1: fix the root cause -- Azure's SQL/Redis converters now construct *DatabaseDetails/*CacheDetails.
  • Commit 2: simplify the 8 consumer type switches down to the pointer-only case now that the value form is unreachable; also corrects a couple of stale comments claiming "the CSV loader constructs values" (it has constructed pointers for a while -- the real source was Azure).
  • Commit 3: update test fixtures to match, dropping the "value type" test cases that existed solely to cover the now-removed branches.

ComputeDetails is intentionally untouched -- GCP's compute-engine client still constructs it as a value, so its dual-handling stays.

What was in the stale worktree but is NOT in this PR (assessed as not worth reviving)

  • provider.ProviderConfig -> provider.Config rename: real (revive stutter) finding, but ~96 occurrences across 18 files for a purely cosmetic rename. Also discovered while assessing this: golangci-lint in CI only lints the root Go module -- pkg/, providers/aws, providers/azure, providers/gcp (separate go.work modules) are never scanned by the "Lint Code" job, so this stutter finding isn't even CI-enforced today. High blast radius for a change with no functional or CI-visible payoff; declined.
  • WriteAuditRecord(record AuditRecord) -> (record *AuditRecord): AuditRecord is ~280 bytes, well under the project's deliberate 1024-byte gocritic hugeParam threshold (see .golangci.yml comment). No lint fires on it today and passing it by value is idiomatic Go at that size. Declined as zero-value churn.

Follow-ups surfaced but out of scope here (not fixed, flagging per project convention)

  • The exact same value/pointer inconsistency exists for ComputeDetails (GCP constructs a value; everyone else a pointer), with the same 2-branch dual-handling in cmd/multi_service_csv.go and cmd/main.go. Could be collapsed the same way in a follow-up.
  • cmd/helpers.go's getEngineFromRecommendation/normalizeEngineName is a near-duplicate of pkg/common/engine.go's EngineFromDetails/NormalizeEngineName (independent of this refactor).
  • CI's golangci-lint step only covers the root module; pkg/, providers/aws, providers/azure, providers/gcp carry real pre-existing lint debt (godot, misspell, a couple of revive stutter findings, gosec is separately covered) that's currently invisible to the "Lint Code" job.

Test plan

  • go build ./... clean in root, pkg/, providers/aws, providers/azure, providers/gcp modules
  • go vet ./... clean in all of the above
  • go test ./... green at the repo root (includes cmd, internal/scheduler, internal/purchase, etc.)
  • go test ./... green in pkg/, providers/aws, providers/azure
  • golangci-lint run --timeout=10m (pinned to CI's v2.10.1) clean at repo root, matching the exact CI invocation
  • gofmt -l clean on all touched files
  • .golangci.yml unchanged

Summary by CodeRabbit

  • Bug Fixes

    • Improved recommendation processing to safely handle missing or nil service details without panics.
    • Standardized database and cache recommendation details for more reliable engine, deployment, filtering, and reporting results.
    • Preserved compute recommendation handling across supported detail formats.
  • Tests

    • Added coverage for nil and typed-nil recommendation details across engine extraction, scheduling, filtering, and version processing.

@cristim cristim added priority/p3 Polish / idea / may never ship type/chore Maintenance / non-user-visible triaged Item has been triaged labels Jul 27, 2026
@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d73d217-7670-4415-9642-42984e3d810e

📥 Commits

Reviewing files that changed from the base of the PR and between 7b18bfd and 1ab2ca1.

📒 Files selected for processing (21)
  • cmd/helpers.go
  • cmd/helpers_test.go
  • cmd/main.go
  • cmd/main_test.go
  • cmd/multi_service_coverage_test.go
  • cmd/multi_service_csv.go
  • cmd/multi_service_csv_test.go
  • cmd/multi_service_engine_versions.go
  • cmd/multi_service_engine_versions_test.go
  • cmd/multi_service_test.go
  • internal/scheduler/scheduler.go
  • internal/scheduler/scheduler_test.go
  • pkg/common/engine.go
  • pkg/common/engine_test.go
  • pkg/common/service_details_codec.go
  • providers/aws/recommendations/coverage.go
  • providers/aws/services/memorydb/client_test.go
  • providers/azure/services/cache/client.go
  • providers/azure/services/cache/client_test.go
  • providers/azure/services/database/client.go
  • providers/azure/services/database/client_test.go
 _________________________________________________________________
< This PR is a classic: 'small change' with 'large consequences'. >
 -----------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/service-details-pointer-only

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

cristim added a commit that referenced this pull request Jul 27, 2026
Adversarial review of #1525's pointer-only collapse found the invariant
("every producer constructs DatabaseDetails/CacheDetails as a pointer")
was violated in providers/aws/services/memorydb/client_test.go, which
built value-typed common.CacheDetails{} literals for Recommendation.Details.
Value receivers on CacheDetails let this compile silently (both T and *T
satisfy ServiceDetails), so nothing caught the mismatch at build time.

The memorydb client doesn't currently type-switch on Details, so this was
inert rather than a live panic/misdispatch, but it violated the invariant
the refactor exists to establish and would silently break the moment any
consumer started asserting *CacheDetails. Switch all ten fixtures to the
pointer form to match every other producer in the codebase.
@cristim
cristim force-pushed the refactor/service-details-pointer-only branch from 688d7ed to 9d6a694 Compare July 27, 2026 17:30
@cristim

cristim commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 46 minutes.

@cristim

cristim commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

Adversarial review of the pointer-only collapse

Reviewed this PR specifically for the failure mode a value-to-pointer change on a
polymorphic Details field invites: a type-switch case that silently stops matching
instead of failing to compile, on a path that ends in a real purchase.

Verification performed

Swept every module (root, pkg/, providers/aws, providers/azure, providers/gcp,
plus the tests/e2e module) for producers and consumers of ServiceDetails:

  • Producers: after this PR the only DatabaseDetails / CacheDetails constructors
    are cmd/multi_service_csv.go (CSV loader), providers/aws/recommendations/parser_services.go,
    providers/azure/services/{cache,database}/client.go, and
    pkg/common/service_details_codec.go (newDetailsForService). All emit pointers. The
    invariant the PR documents holds, with one exception fixed below.
  • Consumers: every remaining type switch / assertion on these two types is
    pointer-only, so no consumer was left matching a shape no producer emits, and none
    lost its only matching case.
  • Money paths: findOfferingID in the RDS and ElastiCache clients assert
    *DatabaseDetails / *CacheDetails and fail loud on !ok (invalid service details for ...). No , ok result is swallowed into a default. Unchanged by this PR.
  • Persistence round trip: encoding/json marshals T and *T identically, and
    DecodeServiceDetailsFor has always unmarshalled into a pointer, so legacy
    purchase_executions.recommendations rows written under the old shape still decode
    to the same typed pointer. internal/purchase/execution.go reconstructs pointers and
    errors out (rather than defaulting) on a decode failure.
  • Aliasing / equality: checked whether pointer Details changes semantics for code
    that copies a Recommendation or compares them. No site mutates Details fields
    through a copied rec (the Savings Plans sizing paths in cmd/helpers.go already
    copy-then-reassign), and Recommendation is never used as a map key or compared with
    ==, so dedup and matching semantics are unaffected.
  • GCP asymmetry: providers/gcp/services/computeengine/client.go still constructs
    and asserts common.ComputeDetails as a value on both sides. This PR does not touch
    it and does not break it.

Findings fixed

1. Comment names GCP as the only value-typed ComputeDetails producer; Azure is one too.
The pointer-invariant comments this PR adds state that "the GCP compute-engine client
still constructs it as a value" as the single remaining exception. The Azure compute
client does the same (providers/azure/services/compute/client.go,
details := common.ComputeDetails{...} assigned straight into Recommendation.Details).
This is load-bearing rather than cosmetic: acting on the comment would mean deleting
case common.ComputeDetails as dead after a GCP change, which silently blanks the
Engine/Platform column for every Azure VM row instead of failing to compile. That is
precisely the failure mode this PR exists to close for DatabaseDetails / CacheDetails.
Corrected in pkg/common/service_details_codec.go, cmd/main.go,
cmd/multi_service_csv.go, and cmd/multi_service_csv_test.go.

2. detailsFromSQLSKU godoc still said it returns a value.
This PR changed the signature to *common.DatabaseDetails but left the godoc reading
"parses an Azure SQL SKU string into a common.DatabaseDetails value".

3. MemoryDB test fixtures still built value-typed CacheDetails.
providers/aws/services/memorydb/client_test.go had ten
Details: common.CacheDetails{...} literals, the last violation of the invariant this
PR documents. The MemoryDB client never type-asserts Details (it reads
rec.ResourceType), so this was inert rather than a live mis-dispatch, but leaving it
in place keeps a working example of the exact construction the invariant forbids.

4. Typed-nil Details panics four engine-extraction helpers.
pkg/common.EngineFromDetails, internal/scheduler.extractEngine,
cmd.getEngineFromRecommendation, and cmd.adjustRecommendationForExcludedVersions
dispatch on *DatabaseDetails / *CacheDetails and read a field with no pointer
check. Their details == nil preamble only catches an untyped nil: once the
interface carries a type, a (*common.DatabaseDetails)(nil) passes that check, reaches
the switch, and panics on the field read. EngineFromDetails is called from
common.Matches for every recommendation/commitment pair and
scheduler.extractEngine feeds the persisted engine column and the recommendation ID,
so a panic there aborts a whole matching or collection run rather than skipping one row.

Not reachable from today's producers, but the sibling helpers already guard exactly
this way (extractDeployment and extractEngine in cmd/multi_service_csv.go,
rdsEngineDeploymentFromRec in the AWS coverage package, extractEngineLabel in
cmd/main.go), and multi_service_csv_test.go on this very branch already pins the
typed-nil case for two of them. These four were the inconsistent ones, and the PR's own
pointer-only invariant is what makes the guard the correct shape. Guards added, with
regression tests covering the typed-nil form for all four helpers, each verified to
panic against the pre-fix code.

Dismissed with justification

  • Nil guards on applyEngineFallback (internal/purchase/execution.go): reached
    only with the pointer DecodeServiceDetailsFor just returned, which is never nil on
    that branch. No guard needed.
  • internal/api/handler_recommendations.go asserting *common.ComputeDetails
    only
    : it operates on the output of DecodeServiceDetailsFor, which always returns
    pointers, not on live provider recs. Correct as written.
  • Pointer aliasing between a Recommendation and its copies: real semantic change
    (a value Details used to deep-copy with the struct), but no current call site
    mutates Details fields through a copy, so it is latent rather than a defect. Not
    worth a speculative deep-copy helper in this PR.

Verification

  • go build ./... and go test ./... -count=1 green in all five modules: root 5949
    tests / 38 packages, pkg 742 / 11, providers/aws 1179 / 12, providers/azure
    720 / 14, providers/gcp 268 / 5.
  • golangci-lint run at the CI-pinned v2.10.1: 0 issues. (exit 0) on the root
    module, plus go vet ./... clean.
  • gocyclo -over 10 -ignore "_test\.go" .: no output, exit 0.
  • Each of the four new typed-nil regression tests confirmed to FAIL (nil pointer
    dereference) against the pre-fix code and PASS after, by temporarily reverting the
    guards.

Note: CodeRabbit hit its fair-usage limit before reviewing this PR at all, so this is a
first review rather than a re-review.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

@cristim Thanks for the thorough adversarial pass—especially the producer/consumer sweep and typed-nil regression coverage. I’ll perform a full review of the PR’s current state.

✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 39 minutes.

cristim added 5 commits July 27, 2026 20:22
Azure's SQL and Cache reservation converters were the only
ServiceDetails producers in the codebase that populated
Recommendation.Details with a value struct instead of a pointer.
Every other producer (AWS, the CSV loader, the JSON codec used on
the purchase-execution round trip) already constructs a pointer, so
this was pure inconsistency rather than an intentional variant.

Align Azure with the rest of the codebase so DatabaseDetails/
CacheDetails are always pointers regardless of provider or code
path. Consumers still handle both forms defensively; that cleanup
follows in the next commit.
…cases

Every producer of Recommendation.Details now constructs DatabaseDetails/
CacheDetails as a pointer (previous commit), matching what the JSON
codec has always reconstructed on the purchase-execution round trip.
The value-typed cases in the type switches that dispatched on Details
were the only thing papering over the old inconsistency; they are now
unreachable.

Simplify the switches in getEngineFromRecommendation (cmd/helpers.go),
extractEngineLabel (cmd/main.go), extractDeployment/extractEngine
(cmd/multi_service_csv.go), adjustRecommendationForExcludedVersions
(cmd/multi_service_engine_versions.go), extractEngine
(internal/scheduler/scheduler.go), EngineFromDetails
(pkg/common/engine.go), and rdsEngineDeploymentFromRec
(providers/aws/recommendations/coverage.go) to only handle the pointer
form. Two switches collapsed to a single case; rewrote those as plain
type assertions per gocritic's singleCaseSwitch.

Also corrects stale comments on a few of these functions that claimed
"the CSV-loader path constructs values" -- the CSV loader has
constructed pointers for a while; the real (now-fixed) source of
value-typed Details was the Azure SQL/Cache converters.

ComputeDetails is untouched: the GCP compute-engine client still
constructs it as a value, so consumers that handle ComputeDetails keep
accepting both forms.
…tails

Update test fixtures across cmd and internal/scheduler to construct
DatabaseDetails/CacheDetails as pointers, matching the new pointer-only
invariant. Removes the "value type" test cases in
TestGetEngineFromRecommendation, TestExtractDeployment, and
TestExtractEngine that existed solely to cover the now-removed
value-typed dispatch branches; the pointer-typed cases already in each
table give equivalent coverage of the real (only) code path.
Adversarial review of #1525's pointer-only collapse found the invariant
("every producer constructs DatabaseDetails/CacheDetails as a pointer")
was violated in providers/aws/services/memorydb/client_test.go, which
built value-typed common.CacheDetails{} literals for Recommendation.Details.
Value receivers on CacheDetails let this compile silently (both T and *T
satisfy ServiceDetails), so nothing caught the mismatch at build time.

The memorydb client doesn't currently type-switch on Details, so this was
inert rather than a live panic/misdispatch, but it violated the invariant
the refactor exists to establish and would silently break the moment any
consumer started asserting *CacheDetails. Switch all ten fixtures to the
pointer form to match every other producer in the codebase.
The `details == nil` check on ServiceDetails only catches an untyped nil;
a (*DatabaseDetails)(nil) or (*CacheDetails)(nil) stored in the interface
is non-nil at that check and reaches the type switch, where a bare field
read would panic. Every dispatch site already guards each case with its
own nil check, but the guard read as plausible dead code without a
comment explaining why, and none of the call sites had a regression test
pinning the behavior.

Add TestEngineFromDetails (new, common package had no test for this
function), TestExtractEngine in scheduler, and typed-nil cases to the
existing TestGetEngineFromRecommendation / TestExtractEngine (cmd) /
TestAdjustRecommendationForExcludedVersions_TypedNilDetails suites.
Also correct the ComputeDetails doc comment: the Azure compute client
constructs it as a value alongside the GCP compute-engine client, not
GCP alone.
@cristim
cristim force-pushed the refactor/service-details-pointer-only branch from 9d6a694 to 1ab2ca1 Compare July 27, 2026 18:28
@cristim

cristim commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

Rebased onto main (7b18bfde, which carries #1529's grpc bump to v1.82.1 clearing GO-2026-6061). Clean rebase, no conflicts; head is now 1ab2ca14. The previous head's only red checks were Security Scanning (govulncheck, repo-wide dependency debt) and the CI Success aggregate that gates on it, so this rebase is expected to clear both. Nothing was suppressed or excluded to get there.

main moved twice under this branch (#1516, #1529), so I re-ran the producer and consumer inventories on the rebased tree rather than assuming they still held. They are byte-identical to the pre-rebase run, with only line-number drift:

  • 14 non-test producers of Database/Cache/ComputeDetails. All 7 DatabaseDetails/CacheDetails sites construct pointers. The only value-typed constructions remaining anywhere are ComputeDetails at providers/azure/services/compute/client.go:786 and providers/gcp/services/computeengine/client.go:1189, both deliberate and both still matched by the value cases at cmd/main.go:299 and cmd/multi_service_csv.go:423.
  • 17 non-test type-switch cases and 20 non-test type assertions over ServiceDetails across all five modules. Every DatabaseDetails/CacheDetails site is pointer-only; no consumer matches a shape no producer emits, and none lost its only matching case. No new type switch or Details producer landed in feat(api): owner-token compare-and-clear for collection in-flight marker (closes #261) #1516 or fix(deps): bump grpc to clear govulncheck advisory #1529.
  • The four typed-nil guards are intact post-rebase (cmd/helpers.go:709, internal/scheduler/scheduler.go:1262, cmd/multi_service_engine_versions.go:437, pkg/common/engine.go:57).

Re-verified locally on the rebased tree: go build ./... green in all five modules, go vet ./... clean, gocyclo -over 10 -ignore "_test\.go" . silent (exit 0), and golangci-lint at the CI-pinned v2.10.1 reports 0 issues.

Note for the record: CodeRabbit has not yet produced a substantive review of this PR. Its walkthrough comment still carries the rate limited by coderabbit.ai marker despite being edited in place at 17:38:04Z, no comment on this PR carries an Actionable comments posted marker, and the CodeRabbit check reports pass / "Review rate limited". Requesting a full review of the rebased head rather than an incremental one, since a throttled incremental pass silently skips commits and reports clean.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

@cristim Understood. I’ll perform a full review of the rebased head (1ab2ca14), rather than an incremental pass.

✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 2 seconds.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@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.

🧹 Nitpick comments (1)
internal/scheduler/scheduler.go (1)

1247-1269: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Split these oversized Go files before extending them further.

  • internal/scheduler/scheduler.go#L1247-L1269: extract focused scheduler helpers into an existing appropriate package/file.
  • internal/scheduler/scheduler_test.go#L2346-L2380: move focused helper tests into dedicated existing test files.
  • cmd/multi_service_csv_test.go#L526-L541: split CSV extraction tests by behavior.
  • cmd/main_test.go#L417-L419: split purchase-ID tests into focused test files.
  • cmd/multi_service_coverage_test.go#L742-L745: split coverage/engine-version tests by concern.

As per coding guidelines, “keep files under 500 lines.”

🤖 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/scheduler/scheduler.go` around lines 1247 - 1269, Split the
oversized files into focused existing packages or test files, keeping each file
under 500 lines: move extractEngine and related scheduler helpers from
internal/scheduler/scheduler.go:1247-1269; move the focused helper tests from
internal/scheduler/scheduler_test.go:2346-2380; separate CSV extraction behavior
tests in cmd/multi_service_csv_test.go:526-541; move purchase-ID tests from
cmd/main_test.go:417-419 into focused test files; and separate coverage tests
from engine-version tests in cmd/multi_service_coverage_test.go:742-745,
preserving behavior and updating package-local references as needed.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@internal/scheduler/scheduler.go`:
- Around line 1247-1269: Split the oversized files into focused existing
packages or test files, keeping each file under 500 lines: move extractEngine
and related scheduler helpers from internal/scheduler/scheduler.go:1247-1269;
move the focused helper tests from
internal/scheduler/scheduler_test.go:2346-2380; separate CSV extraction behavior
tests in cmd/multi_service_csv_test.go:526-541; move purchase-ID tests from
cmd/main_test.go:417-419 into focused test files; and separate coverage tests
from engine-version tests in cmd/multi_service_coverage_test.go:742-745,
preserving behavior and updating package-local references as needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d73d217-7670-4415-9642-42984e3d810e

📥 Commits

Reviewing files that changed from the base of the PR and between 7b18bfd and 1ab2ca1.

📒 Files selected for processing (21)
  • cmd/helpers.go
  • cmd/helpers_test.go
  • cmd/main.go
  • cmd/main_test.go
  • cmd/multi_service_coverage_test.go
  • cmd/multi_service_csv.go
  • cmd/multi_service_csv_test.go
  • cmd/multi_service_engine_versions.go
  • cmd/multi_service_engine_versions_test.go
  • cmd/multi_service_test.go
  • internal/scheduler/scheduler.go
  • internal/scheduler/scheduler_test.go
  • pkg/common/engine.go
  • pkg/common/engine_test.go
  • pkg/common/service_details_codec.go
  • providers/aws/recommendations/coverage.go
  • providers/aws/services/memorydb/client_test.go
  • providers/azure/services/cache/client.go
  • providers/azure/services/cache/client_test.go
  • providers/azure/services/database/client.go
  • providers/azure/services/database/client_test.go

@cristim
cristim merged commit 101f099 into main Jul 27, 2026
19 checks passed
@cristim
cristim deleted the refactor/service-details-pointer-only branch July 27, 2026 19:05
cristim added a commit that referenced this pull request Sep 27, 2026
* fix(azure): construct DatabaseDetails/CacheDetails as pointers

Azure's SQL and Cache reservation converters were the only
ServiceDetails producers in the codebase that populated
Recommendation.Details with a value struct instead of a pointer.
Every other producer (AWS, the CSV loader, the JSON codec used on
the purchase-execution round trip) already constructs a pointer, so
this was pure inconsistency rather than an intentional variant.

Align Azure with the rest of the codebase so DatabaseDetails/
CacheDetails are always pointers regardless of provider or code
path. Consumers still handle both forms defensively; that cleanup
follows in the next commit.

* refactor(common): drop dead value-typed DatabaseDetails/CacheDetails cases

Every producer of Recommendation.Details now constructs DatabaseDetails/
CacheDetails as a pointer (previous commit), matching what the JSON
codec has always reconstructed on the purchase-execution round trip.
The value-typed cases in the type switches that dispatched on Details
were the only thing papering over the old inconsistency; they are now
unreachable.

Simplify the switches in getEngineFromRecommendation (cmd/helpers.go),
extractEngineLabel (cmd/main.go), extractDeployment/extractEngine
(cmd/multi_service_csv.go), adjustRecommendationForExcludedVersions
(cmd/multi_service_engine_versions.go), extractEngine
(internal/scheduler/scheduler.go), EngineFromDetails
(pkg/common/engine.go), and rdsEngineDeploymentFromRec
(providers/aws/recommendations/coverage.go) to only handle the pointer
form. Two switches collapsed to a single case; rewrote those as plain
type assertions per gocritic's singleCaseSwitch.

Also corrects stale comments on a few of these functions that claimed
"the CSV-loader path constructs values" -- the CSV loader has
constructed pointers for a while; the real (now-fixed) source of
value-typed Details was the Azure SQL/Cache converters.

ComputeDetails is untouched: the GCP compute-engine client still
constructs it as a value, so consumers that handle ComputeDetails keep
accepting both forms.

* test(common): update fixtures to pointer-only DatabaseDetails/CacheDetails

Update test fixtures across cmd and internal/scheduler to construct
DatabaseDetails/CacheDetails as pointers, matching the new pointer-only
invariant. Removes the "value type" test cases in
TestGetEngineFromRecommendation, TestExtractDeployment, and
TestExtractEngine that existed solely to cover the now-removed
value-typed dispatch branches; the pointer-typed cases already in each
table give equivalent coverage of the real (only) code path.

* fix(aws): construct memorydb test fixtures' CacheDetails as pointers

Adversarial review of #1525's pointer-only collapse found the invariant
("every producer constructs DatabaseDetails/CacheDetails as a pointer")
was violated in providers/aws/services/memorydb/client_test.go, which
built value-typed common.CacheDetails{} literals for Recommendation.Details.
Value receivers on CacheDetails let this compile silently (both T and *T
satisfy ServiceDetails), so nothing caught the mismatch at build time.

The memorydb client doesn't currently type-switch on Details, so this was
inert rather than a live panic/misdispatch, but it violated the invariant
the refactor exists to establish and would silently break the moment any
consumer started asserting *CacheDetails. Switch all ten fixtures to the
pointer form to match every other producer in the codebase.

* test(common): pin typed-nil safety regression bar for Details dispatch

The `details == nil` check on ServiceDetails only catches an untyped nil;
a (*DatabaseDetails)(nil) or (*CacheDetails)(nil) stored in the interface
is non-nil at that check and reaches the type switch, where a bare field
read would panic. Every dispatch site already guards each case with its
own nil check, but the guard read as plausible dead code without a
comment explaining why, and none of the call sites had a regression test
pinning the behavior.

Add TestEngineFromDetails (new, common package had no test for this
function), TestExtractEngine in scheduler, and typed-nil cases to the
existing TestGetEngineFromRecommendation / TestExtractEngine (cmd) /
TestAdjustRecommendationForExcludedVersions_TypedNilDetails suites.
Also correct the ComputeDetails doc comment: the Azure compute client
constructs it as a value alongside the GCP compute-engine client, not
GCP alone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority/p3 Polish / idea / may never ship triaged Item has been triaged type/chore Maintenance / non-user-visible

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant