Skip to content

Conversation

@sergenyalcin
Copy link
Member

@sergenyalcin sergenyalcin commented Jan 8, 2026

Description of your changes

Overview

This PR introduces an automated system for detecting and handling schema changes in CRD API versions across upjet-based providers. When Terraform provider schemas evolve (field additions, deletions, or type changes), CRD API versions must adapt, creating potential data loss during upgrades/downgrades. This system automatically generates and registers conversion functions to preserve data integrity across API version transitions.

Problem Statement

When a Terraform provider introduces schema changes (e.g., string field becomes number, or a field is added/removed), upjet-based Crossplane providers face several challenges:

  1. Data Loss During Upgrades: Fields added in v1beta2 are silently dropped when controllers run v1beta1
  2. Type Conversion Failures: Direct field copying fails when types change between versions
  3. Manual Conversion Burden: Developers must manually write conversion functions for every schema change
  4. Singleton List Conversions: Existing singleton list conversion system breaks when fields have type changes

Previously, handling these scenarios required:

  • Manually detecting schema changes by comparing CRD versions
  • Writing custom conversion functions for each schema change
  • Manually excluding type-changed fields from identity converters
  • Testing all version transition paths

This manual process is error-prone, time-consuming, and doesn't scale across 100+ resources in a provider.

Solution Architecture

Controller Version Architecture

Important Context: This system includes two types of conversion logic:

  1. CRD Conversion Webhook Logic (Permanent)

    • Type converters (string↔number, string↔bool)
    • Field addition/deletion handlers via CRD conversion webhooks
    • Registered by RegisterAutoConversions()
    • Handles conversions between API versions (e.g., v1beta1 ↔ v1beta2)
    • This is the core permanent architecture
  2. Runtime Annotation Handling (Temporary - Being Deprecated)

    • Handles version mismatches between controller reconcile version and API storage version
    • Functions: mergeAnnotationFieldsWithSpec(), moveTFStateValuesToAnnotation(), mergeAnnotationFieldsWithStatus()
    • Only needed when controller reconciles on an older API version than the CRD storage version
    • With this PR, providers update all controller versions to match the latest API version
    • ControllerReconcileVersion configuration is deprecated and will be removed in future upjet releases
    • Once removed, the runtime annotation handling will also be removed

Migration Strategy: As part of adopting this system, providers update controller reconcile versions to match the latest CRD API version (see provider-upjet-azuread example). This eliminates the need for runtime annotation handling going forward.

High-Level Flow

┌─────────────────────────────────────────────────────────────────────┐
│ Build Time (Code Generation)                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. Generate Go APIs (upjet)                                        │
│         ↓                                                           │
│  2. Generate CRDs (controller-gen)                                  │
│         ↓                                                           │
│  3. Analyze CRD Schema Changes (schemadiff tool)                    │
│         ↓                                                           │
│  4. Generate crd-schema-changes.json                                │
│         ↓                                                           │
│  5. Embed JSON in provider binary                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Runtime (Provider Startup)                                          │
├─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│  1. pc.ConfigureResources()                                             │
│         ↓                                                               │
│  2. ExcludeTypeChangesFromIdentity(pc, crdSchemaChanges)                │
│         → Populates IdentityConversionExcludePaths                      │
│         → Prevents type-changed fields from failing identity conversion │
│         ↓                                                               │
│  3. Provider-specific transformations (e.g., bumpVersionsWithLists)     │
│         → Uses IdentityConversionExcludePaths                           │
│         ↓                                                               │
│  4. RegisterAutoConversions(pc, crdSchemaChanges)                       │
│         → Registers field addition/deletion handlers                    │
│         → Registers type conversion functions                           │
│         → Skipped during code generation (generationProvider flag)      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Runtime (CRD Conversion Webhook) - PERMANENT                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  User applies v1beta2 resource                                      │
│         ↓                                                           │
│  Webhook converts to v1beta1 (if necessary/requested)               │
│         ↓                                                           │
│  Registered conversions execute in order:                           │
│    1. Identity conversion (skips excluded paths)                    │
│    2. Type conversions (string↔number, string↔bool)                 │
│    3. Field preservation (store/restore via annotations)            │
│         ↓                                                           │
│  Controller reconciles on v1beta2                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Runtime (Version Mismatch Handling) - TEMPORARY/DEPRECATED          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ONLY ACTIVE IF: ControllerReconcileVersion ≠ API Version           │
│  (This scenario is being phased out)                                │
│         ↓                                                           │
│  Terraform parameters merged with annotation values                 │
│         ↓                                                           │
│  After Terraform operation, status fields moved to annotations      │
│  if not present in older API version                                │
│                                                                     │
│  [ControllerReconcileVersion is deprecated, will be removed]        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Components

1. schemadiff Tool (cmd/schemadiff/main.go)

  • CLI tool that analyzes CRD files to detect schema changes between API versions
  • Uses upbound/uptest/pkg/crdschema library (now official-providers-ci)
  • Outputs JSON mapping {group}/{kind} → list of changes per version transition
  • Runs during go generate after CRD generation

Example Output:

{
  "ec2.aws.upbound.io/VPC": {
    "versions": {
      "v1beta2": {
        "oldVersion": "v1beta1",
        "newVersion": "v1beta2",
        "changes": [
          {
            "path": "spec.forProvider.maxSize",
            "changeType": "type_changed",
            "oldValue": "string",
            "newValue": "number"
          },
          {
            "path": "spec.forProvider.newField",
            "changeType": "field_added"
          }
        ]
      }
    }
  }
}

2. Auto-Registration Functions (pkg/config/conversion.go)

ExcludeTypeChangesFromIdentity()

  • Scans JSON for type_changed entries
  • Adds affected field paths to r.AutoConversionRegistrationOptions.IdentityConversionExcludePaths
  • Why this matters: Identity conversion copies fields as-is. Type-changed fields would fail (can't assign number to string). Must be excluded BEFORE singleton list conversion runs.
  • Timing: Must run BEFORE bumpVersionsWithEmbeddedLists() (or similar version bumping)

RegisterAutoConversions()

  • Scans JSON for all three change types and registers appropriate converters:
    • Field additions: Store/restore via internal.upjet.crossplane.io/field-conversions annotation
    • Field deletions: Store/restore via same annotation (symmetric with additions)
    • Type changes: Register bidirectional type converters (string↔int, string↔float, string↔bool)
  • Timing: Must run AFTER version bumping (uses the finalized conversion list)
  • Skipped during generation: Checks generationProvider flag (no need for conversions during code gen)

3. Conversion Implementations (pkg/config/conversion/conversions.go)

newlyIntroducedFieldConverter

  • Handles fields that exist in some versions but not others
  • ToAnnotation mode: Copies field value from spec/status into annotation (when converting to older version)
  • FromAnnotation mode: Restores field value from annotation to spec/status (when converting to newer version)

fieldTypeConverter

  • Handles primitive type conversions:
    • IntToString / StringToInt: int64 ↔ string
    • FloatToString / StringToFloat: float64 ↔ string
    • BoolToString / StringToBool: bool ↔ "true"/"false"
  • Uses Go's strconv package for conversion
  • Bidirectional: registers both directions for each type change

4. Runtime Annotation Handling (Temporary - Being Deprecated) (pkg/controller/annotation_conversions.go)

Purpose: These functions handle version mismatches between ControllerReconcileVersion and the API version. With this PR, providers update controller versions to match API versions, making these functions inactive.

mergeAnnotationFieldsWithSpec()

  • When active: Controller reconcile version < API version (e.g., controller on v1beta1, CRD on v1beta2)
  • Called during Create/Update operations (in getExtendedParameters())
  • Reads internal.upjet.crossplane.io/field-conversions annotation
  • Merges spec.forProvider.* and spec.initProvider.* values back into parameter maps
  • Ensures Terraform sees complete parameter set even if controller runs older API version
  • Checks ControllerReconcileVersion == Version and returns early if equal (most common case)

moveTFStateValuesToAnnotation()

  • When active: Controller reconcile version < API version
  • Called after Terraform operations return state
  • Compares TF state fields against CRD status.atProvider schema
  • For fields in TF state but NOT in status (controller running old version), stores values in annotation
  • Only updates annotation if values changed (prevents unnecessary writes)
  • Requires manual configuration via r.TfStatusConversionPaths (only needed during version mismatch)
  • Checks ControllerReconcileVersion == Version and returns early if equal

mergeAnnotationFieldsWithStatus()

  • When active: Controller reconcile version < API version
  • Called during Connect() to reconstruct full Terraform state
  • Reads annotation and merges status.atProvider.* values back into status map
  • Ensures Terraform operation tracker sees complete state
  • Checks ControllerReconcileVersion == Version and returns early if equal

Deprecation Timeline:

  • ControllerReconcileVersion field marked as deprecated
  • Will be removed in future major upjet release
  • Runtime annotation handling functions will be removed at the same time
  • CRD conversion webhook logic (Component terrajet to upjet conversion #3) remains permanent

Design Decision: Why schemadiff Runs in Provider Generation Pipeline

Decision: The schemadiff tool is invoked in the provider's generate/generate.go file, not within upjet's code generation logic.

Architectural Constraint:

Upjet's architecture has a clear separation of concerns:

  1. Upjet's responsibility: Generate Go API types (structs, interfaces, controller scaffolding)
  2. Provider's responsibility: Run controller-gen to convert Go types into CRDs
  3. schemadiff's requirement: Needs CRD YAML files to exist before it can analyze them

This creates a dependency chain that prevents integration into upjet:

Upjet generates Go types

Provider runs controller-gen (generates CRDs from Go types)

Provider runs schemadiff (analyzes generated CRDs)

Provider embeds JSON (makes analysis available at runtime)

Why schemadiff can't run earlier:

  • Upjet doesn't generate CRDs—it generates Go types
  • CRD generation happens via controller-gen in the provider's pipeline
  • schemadiff needs actual CRD YAML files (with OpenAPI schemas) to compare versions
  • These files only exist after controller-gen runs

Trade-off accepted: Providers must add two lines to their generate/generate.go file (go:generate directive + embed statement). This manual step is necessary due to the architectural boundary between upjet and CRD generation tooling.

The current design respects existing architectural boundaries while providing maximum flexibility to providers.


Key Changes

New Files

File Lines Purpose
cmd/schemadiff/main.go 128 CLI tool for CRD schema analysis
pkg/config/conversion.go 260 Auto-registration orchestration
pkg/controller/annotation_conversions.go 338 Runtime annotation merge logic
tests/conversion/integration_test.go 1071 Integration test suite
pkg/config/conversion_test.go 1252 Unit test suite

Modified Files

File Changes Purpose
pkg/config/conversion/conversions.go +363 lines Type/field conversion implementations
pkg/controller/external_tfpluginsdk.go Modified Integration with annotation merge
pkg/controller/external_tfpluginfw.go Modified Integration with annotation merge
pkg/config/resource.go +116 lines New config options

Configuration API Additions

Resource.AutoConversionRegistrationOptions

type AutoConversionRegistrationOptions struct {
    // Skip automatic conversion registration entirely
    SkipAutoRegistration bool

    // Exclude specific paths from auto-registration
    AutoRegisterExcludePaths []string

    // Internal: populated by ExcludeTypeChangesFromIdentity
    IdentityConversionExcludePaths []string
}

Resource.TfStatusConversionPaths

// List of status.atProvider.* paths that should be moved to/from annotations
// when controller runs older API version than CRD storage version
TfStatusConversionPaths []string

Design Decisions & Trade-offs

1. Number→String Defaults to Integer Conversion

Decision: When auto-detecting number→string type changes, default to int64 conversion.

Rationale:

  • Cannot access old Terraform schema to determine if field was int or float
  • Majority of number fields in Terraform are integers
  • Providers have escape hatch: AutoRegisterExcludePaths for manual registration

Limitation: Float fields (e.g., 3.14159) will be truncated to "3" if not manually handled.

Mitigation: Will be documented prominently in README and inline comments. Developers can exclude and manually register float conversions.

2. Function Call Ordering is Manual

Decision: Keep ExcludeTypeChangesFromIdentity() and RegisterAutoConversions() as separate functions.

Required Order:

pc.ConfigureResources()
ExcludeTypeChangesFromIdentity(pc, crdSchemaChanges)  // Must be first
bumpVersionsWithEmbeddedLists(pc)                      // Provider-specific
RegisterAutoConversions(pc, crdSchemaChanges)          // Must be last

Rationale:

  • bumpVersionsWithEmbeddedLists() is provider-specific logic (not in upjet)
  • Must run between the two upjet functions
  • Encapsulating would require upjet to know about provider version bumping strategies

Limitation: Order is not enforced by type system.

Mitigation:

  • Comprehensive documentation in PR and README
  • Code comments at call sites explaining ordering requirement
  • Integration tests verify correct ordering

3. TfStatusConversionPaths Requires Manual Configuration (Temporary Feature)

Decision: Auto-detect and handle spec.* fields automatically, but require manual configuration for status.atProvider.* fields.

Rationale:

  • Status field handling only applies when ControllerReconcileVersion ≠ Version (controller on older API version)
  • With this PR, all providers update controllers to latest API version, making this feature inactive
  • ControllerReconcileVersion is deprecated and will be removed in future upjet releases
  • Status field conversions will be removed along with ControllerReconcileVersion
  • The complexity of auto-detecting status field needs doesn't justify the temporary use case
  • Manual configuration makes the temporary nature explicit

Current Status:

  • Feature implemented but inactive in providers following the new pattern
  • Existing for backwards compatibility during migration period
  • No need to configure for new provider integrations

Legacy Usage (if needed during migration):

r.TfStatusConversionPaths = []string{
    "status.atProvider.newField",
}

Deprecation Path:

  1. Providers update controller versions to match API versions (this PR)
  2. ControllerReconcileVersion field marked deprecated
  3. Future major upjet release removes ControllerReconcileVersion and TfStatusConversionPaths
  4. Runtime annotation handling code removed

Provider Integration Guide

Step 1: Update Controller Versions to Match API Versions

Suggested First Step: Ensure all resources reconcile on their latest API version. This will decrease runtime complexity.

In your provider-specific version bumping logic (e.g., bumpVersionsWithEmbeddedLists()):

func bumpVersionsWithEmbeddedLists(pc *ujconfig.Provider) {
    for name, r := range pc.Resources {
        if slices.Contains(strings.Split(strings.TrimSpace(oldSingletonListAPIs), "\n"), name) {
            r.Version = "v1beta2"
            r.SetCRDStorageVersion("v1beta1")

            // IMPORTANT: Update controller version to match API version
            r.ControllerReconcileVersion = r.Version  // ← Set to latest version

            // Configure conversions...
            excludePaths := append(r.CRDListConversionPaths(), r.AutoConversionRegistrationOptions.IdentityConversionExcludePaths...)
            r.Conversions = []conversion.Conversion{
                conversion.NewIdentityConversionExpandPaths(conversion.AllVersions, conversion.AllVersions, conversion.DefaultPathPrefixes(), excludePaths...),
                conversion.NewSingletonListConversion("v1beta1", "v1beta2", conversion.DefaultPathPrefixes(), r.CRDListConversionPaths(), conversion.ToEmbeddedObject),
                conversion.NewSingletonListConversion("v1beta2", "v1beta1", conversion.DefaultPathPrefixes(), r.CRDListConversionPaths(), conversion.ToSingletonList),
            }
        }
        // Add TerraformConversions for all resources (not just bumped ones)
        r.TerraformConversions = []ujconfig.TerraformConversion{
            ujconfig.NewTFSingletonConversion(),
        }
    }
}

Why this matters:

  • Eliminates version mismatch between controller and API
  • Disables runtime annotation handling (temporary deprecated feature)
  • Ensures controllers see the latest field definitions
  • See provider-upjet-azuread example for reference

Step 2: Add go:generate Directive

In generate/generate.go, after CRD generation:

//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=../apis/... crd:allowDangerousTypes=true,crdVersions=v1 output:artifacts:config=../package/crds

// Generate CRD schema diff JSON file
//go:generate go run github.com/crossplane/upjet/v2/cmd/schemadiff -i ../package/crds -o ../config/crd-schema-changes.json

Step 3: Embed JSON in Provider Config

In config/provider.go:

//go:embed crd-schema-changes.json
var crdSchemaChanges []byte

Step 4: Integrate During Provider Construction

In GetProvider():

func GetProvider(ctx context.Context, generationProvider bool) (*ujconfig.Provider, error) {
    // ... existing provider setup

    pc.ConfigureResources()

    // Step 1: Exclude type-changed fields from identity conversion
    if err := ujconfig.ExcludeTypeChangesFromIdentity(pc, crdSchemaChanges); err != nil {
        return nil, errors.Wrap(err, "cannot exclude type changes from identity conversion")
    }

    // Step 2: Provider-specific version bumping (includes controller version updates from Step 1)
    bumpVersionsWithEmbeddedLists(pc)

    // Step 3: Register automatic conversions (skip during generation)
    if !generationProvider {
        if err := ujconfig.RegisterAutoConversions(pc, crdSchemaChanges); err != nil {
            return nil, errors.Wrap(err, "cannot register auto conversions")
        }
    }

    return pc, nil
}

Step 5: (Optional) Exclude Specific Paths for Custom Conversions

Only needed for float fields or custom conversion logic:

// In resource-specific config (e.g., config/ec2/config.go)
pc.Resources["aws_instance"].AutoConversionRegistrationOptions.AutoRegisterExcludePaths = []string{
    "spec.forProvider.customField",  // Will write custom conversion
    "spec.forProvider.floatField",   // Number→string but field is actually float
}

Step 6: (Legacy) Configure Status Field Preservation

Not needed for new integrations. This step only applies if maintaining backwards compatibility with ControllerReconcileVersion ≠ Version.

Click to expand legacy configuration (deprecated)
// LEGACY: Only if controller runs older API version than CRD storage version
// With Step 1 completed, this is inactive and will be removed in future
pc.Resources["aws_vpc"].TfStatusConversionPaths = []string{
    "status.atProvider.newField",
}

Supported Conversion Types

1. Field Additions

Scenario: Field exists in v1beta2 but not in v1beta1

Behavior:

  • v1beta2 → v1beta1: Store field value in annotation
  • v1beta1 → v1beta2: Restore field value from annotation

Annotation Key: internal.upjet.crossplane.io/field-conversions

2. Field Deletions

Scenario: Field exists in v1beta1 but not in v1beta2

Behavior: Same as field additions (symmetric)

3. Type Changes: String ↔ Number

Scenario: Field changes from string to number (or vice versa)

String → Number:

  • Consults Terraform schema to determine int64 vs float64
  • Registers appropriate converter (StringToInt or StringToFloat)

Number → String:

  • Defaults to int64 → string
  • Limitation: If field was actually float, precision loss occurs
  • Solution: Use AutoRegisterExcludePaths and register manual FloatToString converter

4. Type Changes: String ↔ Boolean

Scenario: Field changes from string to boolean (or vice versa)

Behavior:

  • String → Boolean: Parses "true"/"false" using strconv.ParseBool
  • Boolean → String: Formats to "true"/"false"

Testing

Unit Tests (pkg/config/conversion_test.go)

Test Coverage:

  • Valid JSON parsing with multiple resources and version transitions
  • Invalid JSON handling (malformed, unknown change types, missing fields)
  • Type change detection and exclusion path population
  • All type conversion modes (int, float, bool)
  • Edge cases (missing Terraform schema, empty paths, etc.)

Example Test Cases:

  • TestRegisterAutoConversions_ValidJSON_MultipleResources
  • TestRegisterAutoConversions_TypeChange_MissingTerraformSchema
  • TestExcludeTypeChangesFromIdentity_ValidJSON
  • TestRegisterAutoConversions_InvalidJSON_Malformed

Integration Tests (tests/conversion/integration_test.go)

Test Coverage:

  • End-to-end type conversions (string↔int, string↔float, string↔bool)
  • Field addition/deletion across version transitions
  • Annotation storage and retrieval
  • Multiple version hops (v1beta1 → v1beta2 → v1beta1)
  • Nested field conversions
  • List and map field handling

Test Resources:

  • Custom test CRD with configurable fields
  • Mock Terraform resources
  • Test JSON files in tests/testdata/

Example Test Cases:

  • TestConversionIntegration_StringToInt
  • TestConversionIntegration_BoolToString
  • TestConversionIntegration_FieldAddition
  • TestConversionIntegration_MultipleChanges

Known Limitations & Considerations

1. Float Precision in Number→String Conversions

Impact: Float fields converting to string will lose precision if not manually configured.

Workaround: Use AutoRegisterExcludePaths and register manual conversion:

r.AutoConversionRegistrationOptions.AutoRegisterExcludePaths = []string{
    "spec.forProvider.floatField",
}
r.Conversions = append(r.Conversions,
    conversion.NewFieldTypeConversion("v1beta1", "v1beta2",
        "spec.forProvider.floatField", conversion.FloatToString))

2. Function Call Ordering

Impact: Incorrect order causes runtime conversion failures.

Workaround: Follow documented order strictly. Integration tests will catch violations.

3. Annotation Size Limits

Impact: Kubernetes annotations limited to 256KB total per resource.

Unlikely Scenario: Would require many large fields to be added/removed simultaneously.

Monitoring: System doesn't enforce limit currently (future enhancement).

4. Status Field Conversions Require Manual Config (Deprecated Feature)

Impact: Status fields not automatically preserved during version transitions when ControllerReconcileVersion ≠ Version.

Current Status:

  • Feature is inactive in providers following the new pattern (controller version = API version)
  • Only relevant for legacy configurations
  • Will be removed when ControllerReconcileVersion is removed in future upjet release

Legacy Workaround (if needed): Configure TfStatusConversionPaths explicitly.

Recommended: Update controllers to latest API version (Integration Guide Step 1) to avoid this entirely.


Note: Depends on upbound/official-providers-ci#245

I have:

  • Read and followed Upjet's contribution process.
  • Run make reviewable to ensure this PR is ready for review.
  • Added backport release-x.y labels to auto-backport this PR if necessary.

How has this code been tested

Tested in the provider-upjet-azuread: https://github.com/sergenyalcin/provider-upjet-azuread/tree/conversion-functions-demo. This branch consumes these changes and creates a toy problem for applications.Application resource for testing. This contains field_addition and field_deletion scenario.

@sergenyalcin sergenyalcin marked this pull request as ready for review January 8, 2026 13:51
@sergenyalcin sergenyalcin changed the title Integrate breaking change detection and add some helpers for registering the conversion functions automatically Integrate schema change detection and add some helpers for registering the conversion functions automatically Jan 9, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

Adds CRD schema breaking-change detection and auto-conversion: a schemadiff CLI that emits per-CRD change reports, config package functions to register auto-conversions and exclude type-changed fields from identity copying, resource-level opt-outs/options, documentation, test fixtures, and wide unit/integration test coverage.

Changes

Cohort / File(s) Summary
CLI Tool
\cmd/schemadiff/main.go``
New command that enumerates YAML CRD files, computes per-CRD self-diffs (Upjet extensions disabled), converts diffs to ChangeReport objects, aggregates non-empty reports keyed by "group/kind", and writes a JSON output file (0600).
Core Conversion Logic
\pkg/config/conversion.go`, `pkg/config/provider.go``
New APIs: RegisterAutoConversions(pc *Provider, crdSchemaChanges []byte) error and ExcludeTypeChangesFromIdentity(pc *Provider, crdSchemaChanges []byte) error. Adds provider path constants and implements parsing of ChangeReports, path normalization, exclusion handling, and registration of bidirectional conversions for field_added/field_deleted/type_changed.
Resource Config
\pkg/config/resource.go``
Adds AutoConversionRegistrationOptions and a new AutoConversionRegistrationOptions field on Resource with SkipAutoRegistration, IdentityConversionExcludePaths, and AutoRegisterExcludePaths.
Unit Tests & Helpers
\pkg/config/conversion_test.go`, `pkg/config/testing_helpers.go``
Large unit test suite covering registration, exclusions, type mappings, edge cases; test helpers to construct test Provider/Resource instances and load fixtures.
Test Fixtures (valid/invalid)
\pkg/config/testdata/valid/`, `pkg/config/testdata/invalid/``
Many new JSON fixtures (valid and invalid) exercising field additions/deletions, type changes (string↔number, string↔bool, bool↔string), multi-version flows, deeply nested paths, and malformed/missing/unsupported cases.
Integration Tests & Test Resource
\tests/conversion/integration_test.go`, `tests/conversion/test_resource.go`, `tests/testdata/*``
New level-2 integration test exercising roundtrip conversions, annotation recovery, and multiple-change scenarios; includes a TestResource implementation used by tests and several integration fixtures.
Documentation
\docs/breaking-change-detection.md`, `docs/README.md``
Adds a comprehensive design & usage document for breaking-change detection and auto-conversion; README updated to link the new doc.
Dependencies / Tooling
\go.mod``
Bumps Go version and updates several dependencies (testify, goldmark, and various indirect modules).
Testdata Licenses
\pkg/config/testdata/**/.license`, `tests/testdata/.license``
SPDX license header files added alongside new testdata.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as schemadiff CLI
    participant FS as File System
    participant Differ as SchemaDiffer
    participant Writer as JSON Writer

    User->>CLI: run with --crd-dir, --out
    CLI->>FS: list YAML/YML files
    FS-->>CLI: file list
    loop per CRD file
        CLI->>Differ: create self-diff (Upjet ext disabled)
        Differ-->>CLI: raw diffs
        CLI->>CLI: convert raw diffs -> ChangeReport
        CLI->>CLI: if non-empty aggregate into map[group/kind]=ChangeReport
    end
    CLI->>Writer: marshal aggregated map to JSON
    Writer->>FS: write output file (0600)
    FS-->>User: success / error
Loading
sequenceDiagram
    participant Provider
    participant Config as config package
    participant Parser as ChangeReport Parser
    participant Registry as Conversion Registry
    participant Resource as Resource Config

    Provider->>Config: ExcludeTypeChangesFromIdentity(crdSchemaChanges)
    Config->>Parser: parse change reports
    Parser-->>Config: structured change map
    loop per resource
        Config->>Resource: add trimmed paths to IdentityConversionExcludePaths
    end

    Provider->>Config: RegisterAutoConversions(crdSchemaChanges)
    Config->>Parser: parse change reports
    loop per resource and change
        alt field_added / field_deleted
            Config->>Registry: register bidirectional copy conversions
        else type_changed
            Config->>Config: determine conversion direction (string↔number, string↔bool)
            Config->>Registry: register conversion funcs and identity exclusions
        end
    end
    Registry-->>Provider: conversions registered
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title exceeds 72 characters (109 chars) and while descriptive about adding conversion helpers, it's vague about the schemadiff tool integration aspect. Consider a more concise title under 72 characters that captures both schema detection and auto-conversion registration, such as: 'Add automated CRD schema change detection and conversion registration'
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the system architecture, components, design decisions, and provider integration guide.
Configuration Api Breaking Changes ✅ Passed Pull request makes only additive changes to Configuration API in pkg/config/** with new public functions and types, causing no breaking changes to existing exports.
Generated Code Manual Edits ✅ Passed PR contains no modifications to generated code files matching zz_*.go pattern; all changes are legitimate new source files or non-generated code additions.
Template Breaking Changes ✅ Passed This PR makes no modifications to controller template files; all changes are confined to configuration, conversion logic, testing, and documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


🧹 Recent nitpick comments
docs/README.md (1)

43-43: Suggestion: Wrap the line at 100 columns per coding guidelines.

The new documentation entry is clear and helpful—thank you for adding it! However, the line exceeds the 100-column limit specified in the coding guidelines. While links can extend beyond 100 characters for readability, the descriptive text after the link could be wrapped to align with the project's Markdown formatting standards.

📝 Proposed formatting to wrap at 100 columns
-- [Breaking Change Detection and Auto-Conversion](breaking-change-detection.md) - Automatically handle CRD schema breaking changes (field additions/deletions, type changes).
+- [Breaking Change Detection and Auto-Conversion](breaking-change-detection.md) -
+  Automatically handle CRD schema breaking changes (field additions/deletions, type changes).

As per coding guidelines, Markdown files should be wrapped at 100 columns for consistency.

pkg/config/conversion.go (1)

115-119: Consider using errors package for consistency.

I notice this uses fmt.Errorf while the rest of the file uses errors.Wrap from github.com/pkg/errors. Would you mind aligning this with the codebase convention? This helps maintain consistent error handling patterns.

Suggested change
 							default:
 								// Schema type is neither int nor float (e.g., TypeBool, TypeString)
 								// This indicates a mismatch between CRD schema and Terraform schema
-								return fmt.Errorf("unsupported type for number conversion in field %q: got Terraform schema type %q", change.Path, sch.Type)
+								return errors.Errorf("unsupported type for number conversion in field %q: got Terraform schema type %q", change.Path, sch.Type)
pkg/config/testing_helpers.go (2)

29-32: Clarification: purpose of unexported wrapper functions?

I see each exported helper has a corresponding unexported wrapper (e.g., newTestProviderNewTestProvider). These wrappers appear to be simple pass-throughs without additional logic.

Could you help me understand the rationale here? Are these intended for future extension, or is there a specific pattern being followed? If they're not serving a distinct purpose, removing them would reduce maintenance surface.

Also applies to: 51-54, 68-71, 85-88, 102-105, 119-122, 158-161


145-156: Unreachable code after require.NoError and missing nolint explanation

Two observations here:

  1. Line 155 is unreachable since require.NoError on line 154 will call t.FailNow() if err != nil, halting test execution immediately.

  2. Per coding guidelines, nolint directives should include "specific linter names and explanations." The //nolint:gosec on line 148 could benefit from a brief comment explaining why it's safe.

Suggested improvement
 	var data []byte
 	var err error
 	for _, path := range paths {
-		data, err = os.ReadFile(path) //nolint:gosec
+		data, err = os.ReadFile(path) //nolint:gosec // test fixture paths are constructed internally, not from user input
 		if err == nil {
 			return data
 		}
 	}
 
 	require.NoError(t, err, "failed to load test fixture: %s", relativePath)
-	return data
+	return nil // unreachable; require.NoError calls t.FailNow on error

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3e6d6a and 538dd34.

⛔ Files ignored due to path filters (56)
  • go.mod is excluded by none and included by none
  • go.sum is excluded by !**/*.sum and included by none
  • pkg/config/testdata/invalid/malformed.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/malformed.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/missing-change-type.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/missing-change-type.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-missing-values.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-missing-values.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-same-values.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-same-values.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-unsupported.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/type-change-unsupported.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/unknown-change-type.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/invalid/unknown-change-type.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/deeply-nested.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/deeply-nested.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/empty.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/empty.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/evolving-field.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/evolving-field.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/exclude-multiple-paths.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/exclude-multiple-paths.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/field-addition.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/field-addition.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/field-deletion.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/field-deletion.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/multiple-changes.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/multiple-changes.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/multiple-resources.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/multiple-resources.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/multiple-type-changes.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/multiple-type-changes.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/no-changes.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/no-changes.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/three-versions.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/three-versions.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-bool-to-string.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-bool-to-string.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-number-to-string.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-number-to-string.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-string-to-bool.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-string-to-bool.json.license is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-string-to-number.json is excluded by !**/testdata/** and included by none
  • pkg/config/testdata/valid/type-change-string-to-number.json.license is excluded by !**/testdata/** and included by none
  • tests/testdata/bool-to-string.json is excluded by !**/testdata/** and included by none
  • tests/testdata/bool-to-string.json.license is excluded by !**/testdata/** and included by none
  • tests/testdata/multiple-changes.json is excluded by !**/testdata/** and included by none
  • tests/testdata/multiple-changes.json.license is excluded by !**/testdata/** and included by none
  • tests/testdata/number-to-string.json is excluded by !**/testdata/** and included by none
  • tests/testdata/number-to-string.json.license is excluded by !**/testdata/** and included by none
  • tests/testdata/string-to-bool.json is excluded by !**/testdata/** and included by none
  • tests/testdata/string-to-bool.json.license is excluded by !**/testdata/** and included by none
  • tests/testdata/string-to-number.json is excluded by !**/testdata/** and included by none
  • tests/testdata/string-to-number.json.license is excluded by !**/testdata/** and included by none
📒 Files selected for processing (10)
  • cmd/schemadiff/main.go
  • docs/README.md
  • docs/breaking-change-detection.md
  • pkg/config/conversion.go
  • pkg/config/conversion_test.go
  • pkg/config/provider.go
  • pkg/config/resource.go
  • pkg/config/testing_helpers.go
  • tests/conversion/integration_test.go
  • tests/conversion/test_resource.go
✅ Files skipped from review due to trivial changes (1)
  • docs/breaking-change-detection.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/config/provider.go
🧰 Additional context used
📓 Path-based instructions (5)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Do not use any type throughout codebase - use concrete types or type parameters instead
Use pointer types for optional fields in generated structs
Avoid type aliases in favor of explicit types
Use github.com/pkg/errors for error wrapping with context
Return errors from functions instead of panicking, except for impossible states
Wrap errors with context using patterns like: errors.Wrap(err, "cannot configure resource")
Avoid circular dependencies between packages

Files:

  • pkg/config/conversion_test.go
  • pkg/config/conversion.go
  • pkg/config/resource.go
  • pkg/config/testing_helpers.go
  • tests/conversion/integration_test.go
  • cmd/schemadiff/main.go
  • tests/conversion/test_resource.go

⚙️ CodeRabbit configuration file

**/*.go: Enforce Upjet/Crossplane patterns: Use crossplane-runtime/v2/pkg/errors
for wrapping. Check variable naming (short for local scope, descriptive
for wider scope). Ensure 'return early' pattern. Verify error scoping
(declare in conditionals when possible). For nolint directives, require
specific linter names and explanations. CRITICAL: Ensure all error
messages are meaningful to provider developers (the end users of Upjet),
not just Upjet maintainers - include context about what failed in the
generation or configuration process and suggest next steps when possible.

Files:

  • pkg/config/conversion_test.go
  • pkg/config/conversion.go
  • pkg/config/resource.go
  • pkg/config/testing_helpers.go
  • tests/conversion/integration_test.go
  • cmd/schemadiff/main.go
  • tests/conversion/test_resource.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*_test.go: Use standard Go testing only - no Ginkgo, Testify, or third-party test frameworks
Strongly prefer table-driven tests
Place test files in the same package as the code under test using *_test.go naming

Files:

  • pkg/config/conversion_test.go
  • tests/conversion/integration_test.go

⚙️ CodeRabbit configuration file

**/*_test.go: Enforce table-driven test structure: PascalCase test names (no
underscores), args/want pattern, use cmp.Diff with
cmpopts.EquateErrors() for error testing. Check for proper test case
naming and reason fields. Ensure no third-party test frameworks (no
Ginkgo, Gomega, Testify).

Files:

  • pkg/config/conversion_test.go
  • tests/conversion/integration_test.go
pkg/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Public API packages should be organized under pkg/ directory

Files:

  • pkg/config/conversion_test.go
  • pkg/config/conversion.go
  • pkg/config/resource.go
  • pkg/config/testing_helpers.go
**/pkg/config/**

⚙️ CodeRabbit configuration file

**/pkg/config/**: This package defines the configuration API used by provider developers.
Changes here directly impact all downstream providers. Review for:
CRITICAL backward compatibility (breaking changes require major version
bump), clear documentation with examples, sensible defaults, and
validation of configuration options. External name configurations are
required for all resources - verify this is enforced. Check that
Reference configurations follow established patterns.

Files:

  • pkg/config/conversion_test.go
  • pkg/config/conversion.go
  • pkg/config/resource.go
  • pkg/config/testing_helpers.go
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Ensure Markdown files are wrapped at 100 columns for consistency and
readability. Lines can be longer if it makes links more readable, but
otherwise should wrap at 100 characters. Check for proper heading
structure, clear language, and that documentation is helpful for users.

Files:

  • docs/README.md
🧠 Learnings (4)
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*_test.go : Strongly prefer table-driven tests

Applied to files:

  • pkg/config/conversion_test.go
  • pkg/config/testing_helpers.go
  • tests/conversion/integration_test.go
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*_test.go : Place test files in the same package as the code under test using `*_test.go` naming

Applied to files:

  • pkg/config/conversion_test.go
  • pkg/config/testing_helpers.go
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*_test.go : Use standard Go testing only - no Ginkgo, Testify, or third-party test frameworks

Applied to files:

  • pkg/config/conversion_test.go
  • pkg/config/testing_helpers.go
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Use interface-based design for extensibility with `resource.Terraformed` and `controller.ExternalClient` interfaces

Applied to files:

  • tests/conversion/test_resource.go
🧬 Code graph analysis (2)
pkg/config/conversion.go (3)
pkg/config/resource.go (2)
  • AutoConversionRegistrationOptions (692-739)
  • Resource (440-674)
pkg/config/conversion/conversions.go (9)
  • NewNewlyIntroducedFieldConversion (536-542)
  • ToAnnotation (43-43)
  • NewFieldTypeConversion (694-700)
  • StringToFloat (77-77)
  • FloatToString (75-75)
  • StringToInt (69-69)
  • IntToString (67-67)
  • StringToBool (73-73)
  • BoolToString (71-71)
pkg/config/common.go (1)
  • GetSchema (155-179)
tests/conversion/test_resource.go (1)
pkg/resource/interfaces.go (1)
  • Terraformed (45-52)
🔇 Additional comments (23)
cmd/schemadiff/main.go (2)

30-99: LGTM - Well-structured CLI tool!

The main function logic is clean and well-documented. I appreciate the thorough inline comments explaining each step (e.g., why EnableUpjetExtensions=false, the key format for jsonData). The error handling with kingpin.FatalIfError is consistent throughout.


101-128: LGTM - Clean helper function.

The listYAMLFiles function handles the common case well: filtering directories, case-insensitive extension matching, and proper path construction. Nice touch pre-allocating the slice capacity.

pkg/config/resource.go (1)

668-739: LGTM - Well-documented public API addition!

Thank you for the thorough documentation on AutoConversionRegistrationOptions. I particularly appreciate:

  • Clear "Use this when" guidance for each field
  • The explicit note that IdentityConversionExcludePaths is auto-populated
  • The example showing how to manually register conversions after excluding paths

The zero-value defaults make this backward compatible with existing configurations. Nice work!

pkg/config/conversion.go (3)

86-136: LGTM - Type conversion logic is well-reasoned.

The type change handling covers the common conversion scenarios well. I appreciate the documentation noting that number→string defaults to integer and that operators should exclude and manually register for float fields. The exhaustive switch nolint is justified here.

Could you clarify one thing? For type change combinations that aren't handled (e.g., array→string), the code silently skips them. Is this intentional, and should there be a log warning to help operators debug unexpected behavior?


163-190: LGTM - Clean identity exclusion logic.

The function correctly identifies only type-changed fields for exclusion from identity conversion. The deduplication map prevents duplicate entries when the same field changes across multiple version transitions. Good separation of concerns from RegisterAutoConversions.


192-249: LGTM - Helper functions are well-designed.

The helper functions are focused and testable:

  • parseChangeReports: Clean JSON parsing with proper error wrapping
  • trimPathPrefix: Handles all three CRD path prefixes correctly
  • setIdentityConversionExcludePath: Properly deduplicates using the provided map
  • isExcludedPath: Simple exact-match semantics
pkg/config/conversion_test.go (3)

14-480: LGTM - Comprehensive table-driven tests!

This test suite is thorough and well-structured. I particularly appreciate:

  • The reason field documenting expected behavior for each case
  • Coverage of edge cases like nil TerraformResource, malformed JSON, unknown change types
  • The setupResource pattern allowing flexible resource configuration per test

Great job following the table-driven test pattern!


482-554: LGTM - Good multi-resource coverage.

This test validates that the registration correctly handles multiple resources with different configurations. The nested table-driven verification is clean.


556-1249: LGTM - Excellent test coverage across all functions.

The remaining tests provide solid coverage:

  • TestExcludeTypeChangesFromIdentity: Validates deduplication and interaction with AutoRegisterExcludePaths
  • TestIsExcludedPath: Confirms exact-match semantics and case sensitivity
  • TestSetIdentityConversionExcludePath: Tests path trimming for all three prefixes
  • TestIntegrationBothFunctions: Validates both correct and reversed call order
  • TestTrimPathPrefix: Covers all prefix patterns

Thank you for the thorough test suite!

tests/conversion/integration_test.go (4)

146-334: LGTM - Thorough field addition/deletion integration tests!

The Field Addition test suite covers the key scenarios:

  • Restoring field values from annotations (v1alpha1→v1beta1)
  • Storing field values to annotations (v1beta1→v1alpha1)
  • Full roundtrip data preservation

I appreciate the explicit verification of both the parameters AND annotations in each direction.


340-794: LGTM - Comprehensive type conversion tests!

The type conversion tests cover all supported conversion paths:

  • String↔Number (both directions with int handling)
  • String↔Bool (both true and false cases)
  • Number↔String
  • Bool↔String

The numeric type handling in assertions (checking for float64, int64, and int) is pragmatic given JSON unmarshaling behavior. Nice work!


800-1079: LGTM - Multiple changes test suite validates complex scenarios!

This suite validates that multiple simultaneous changes (type conversions + field additions) work correctly together. The roundtrip test at the end is particularly valuable for ensuring no data loss through the full conversion cycle.


34-141: Well-structured integration test setup!

The centralized setup that creates all resources and registers conversions once is clean and mirrors real-world provider initialization. The fixture merging approach is creative for testing multiple resource types in a single test run.

One clarifying question: I see _ = fake.AddToScheme(scheme) discards the error return. Is this intentional because AddToScheme can't fail in this context, or should it be checked?

pkg/config/testing_helpers.go (4)

16-27: LGTM - Clean test provider factory

Nice use of t.Helper() for proper test stack traces. The minimal provider setup is well-suited for focused testing.


34-49: LGTM - Flexible test resource factory

Good design allowing nil schema for simple cases while supporting custom schemas when needed. The explicit initialization of AutoConversionRegistrationOptions is a nice touch for clarity.


56-122: LGTM - Type-specific resource factories

These helpers nicely cover the main Terraform schema types needed for conversion testing. The documentation clearly states each helper's intended use case, which is helpful.


7-14: Consider using standard library testing instead of testify in helper utilities

I notice this helper file imports github.com/stretchr/testify/require. While the coding guidelines specifically target test files (*_test.go), it might be worth aligning helper utilities with the same principle of avoiding third-party test frameworks for consistency.

The single use case here—require.NoError() for error handling in LoadTestFixture—could easily use the standard library instead (e.g., t.Fatalf() or returning the error to callers). Would you be open to using standard library testing utilities throughout, including helper files?

tests/conversion/test_resource.go (6)

138-141: Parameter shouldMergeInitProvider is unused

I notice GetMergedParameters ignores the shouldMergeInitProvider parameter. This is likely fine for test purposes since GetInitParameters returns nil anyway, but wanted to confirm this is intentional.

If tests ever need to verify init-provider merging behavior, this would need updating. Thanks for keeping the implementation minimal!


16-28: Well-structured test resource definition

The struct design nicely mirrors real generated resources with the embedded TypeMeta/ObjectMeta and the TerraformResourceType field for test flexibility. The json:"-" tag on TerraformResourceType is a good choice to exclude it from serialization.


30-46: Good use of map types for flexible test parameters

Using map[string]interface{} for TestResourceParameters and TestResourceObservation provides the flexibility needed to test various field types and structures. This makes sense for a test fixture even though production code would use concrete types.


48-49: LGTM - Interface compliance assertion

Nice compile-time check ensuring TestResource properly implements resource.Terraformed.


51-170: LGTM - Comprehensive Terraformed interface implementation

All the required interface methods are implemented cleanly. The GetID method's defensive type assertions (lines 114-119) are a nice touch for robustness with the map-based AtProvider.


171-185: The DeepCopyObject implementation does perform a shallow copy of map values, which is standard for maps in Go. However, looking at how ForProvider and AtProvider are actually used in the integration tests, they only contain scalar values (strings), not nested structures. The shallow copy poses no risk of shared mutable state in these test scenarios.

For a test fixture, the simple implementation is appropriate and intentional. No changes needed here!

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@cmd/schemadiff/main.go`:
- Around line 35-37: The error message in the validation for out (the nil/empty
check using kingpin.Fatalf) is incomplete; update the kingpin.Fatalf call in
cmd/schemadiff/main.go (the block that checks if out == nil || *out == "") to a
clear, grammatically complete message such as "output directory is required" or
"must specify output directory" so the error describes what is required.

In `@docs/breaking-change-detection.md`:
- Line 20: The TOC contains a broken link to "#troubleshooting" which has no
matching "## Troubleshooting" heading; either add a "## Troubleshooting" section
with relevant troubleshooting content to the document (so the TOC link resolves)
or remove the "[Troubleshooting](`#troubleshooting`)" entry from the TOC; look for
the literal TOC line "- [Troubleshooting](`#troubleshooting`)" and the expected
heading "## Troubleshooting" when making the change.

In `@go.mod`:
- Line 64: The go.mod currently pulls in github.com/getkin/kin-openapi v0.108.0
(indirect) which is vulnerable; identify which direct module in your dependency
graph depends on github.com/getkin/kin-openapi (use go mod graph or go list -m
all to locate the parent module), then bump that direct dependency to a release
that itself requires kin-openapi v0.131.0 or later (use go get
<direct-module>@<fixed-version>), run go mod tidy to update go.mod/go.sum, and
verify that github.com/getkin/kin-openapi is now recorded at v0.131.0+ as an
indirect dependency.

In `@pkg/config/testdata/valid/type-change-bool-to-string.json.license`:
- Around line 1-3: Update the SPDX copyright year from 2026 to 2025 in the
SPDX-FileCopyrightText line (the line containing "2026 The Crossplane Authors
<https://crossplane.io>") so it reads "2025 The Crossplane Authors
<https://crossplane.io>" ensuring the SPDX-License-Identifier line remains
unchanged.

In `@tests/conversion/integration_test.go`:
- Around line 95-126: The json.Unmarshal calls into data1..data6 ignore returned
errors, so parse failures get swallowed; update each json.Unmarshal(fixtureX,
&dataX) to capture its error and fail the test on error (e.g., if err :=
json.Unmarshal(...); err != nil { t.Fatalf("unmarshal fixtureX: %v", err) }) so
invalid fixtures cause the test to fail before merging into merged and
marshalling mergedJSON.
🧹 Nitpick comments (6)
pkg/config/conversion.go (2)

118-118: Use errors.Errorf instead of fmt.Errorf for consistency.

Per coding guidelines, use github.com/pkg/errors for error handling. Since crossplane-runtime's errors package wraps pkg/errors, use errors.Errorf for consistency with the rest of the codebase.

♻️ Suggested fix
-							return fmt.Errorf("unsupported type for number conversion in field %q: got Terraform schema type %q", change.Path, sch.Type)
+							return errors.Errorf("unsupported type for number conversion in field %q: got Terraform schema type %q", change.Path, sch.Type)

Also, you can remove "fmt" from the imports since it would no longer be used.


86-136: Consider logging unhandled type change combinations.

The type change handling covers string↔number and string↔boolean, but other combinations (e.g., number↔boolean, integer↔array) are silently skipped. Consider adding a log warning for unhandled combinations to aid debugging when conversions don't work as expected.

♻️ Example implementation
 					case change.OldValue == "boolean" && change.NewValue == "string":
 						// Boolean → String: true/false → "true"/"false"
 						r.Conversions = append(r.Conversions, conversion.NewFieldTypeConversion(cv.OldVersion, cv.NewVersion, change.Path, conversion.BoolToString),
 							conversion.NewFieldTypeConversion(cv.NewVersion, cv.OldVersion, change.Path, conversion.StringToBool))
+					default:
+						// Log unhandled type change combinations for debugging
+						// Consider using a logger if available in Provider context
 					}
docs/breaking-change-detection.md (1)

39-44: Add language specifier to code blocks.

Several fenced code blocks in this document lack language specifiers, which affects syntax highlighting and linting. For the architecture diagram at lines 41-44 and similar text blocks, consider using text or plaintext as the language identifier.

Example fix for the architecture block
 **Architecture:**
-```
+```text
 Build Time:  CRDs → schemadiff tool → JSON manifest
 Runtime:     JSON → Auto-registration → Conversion functions → Webhooks
</details>

</blockquote></details>
<details>
<summary>pkg/config/testing_helpers.go (2)</summary><blockquote>

`145-156`: **Unreachable return statement after `require.NoError`.**

If all file reads fail, `require.NoError` at line 154 will call `t.FailNow()` and stop test execution. Line 155 is unreachable in the error path. Consider restructuring for clarity.


<details>
<summary>Proposed refactor</summary>

```diff
 	var data []byte
 	var err error
 	for _, path := range paths {
 		data, err = os.ReadFile(path) //nolint:gosec
 		if err == nil {
 			return data
 		}
 	}
 
-	require.NoError(t, err, "failed to load test fixture: %s", relativePath)
-	return data
+	require.FailNow(t, "failed to load test fixture: %s (tried paths: %v)", relativePath, paths)
+	return nil // unreachable, satisfies compiler

29-32: Consider removing redundant unexported wrappers.

Each exported helper has a corresponding unexported wrapper that simply calls the exported version. If these are solely for internal use within the same package, the wrappers add no value since package-internal code can call the exported functions directly. If the intent is to maintain a distinction, a comment explaining the rationale would help future maintainers.

Also applies to: 51-54, 68-71, 85-88, 102-105, 119-122, 158-161

tests/conversion/test_resource.go (1)

171-185: DeepCopyObject performs shallow copy of map values and omits embedded spec/status fields.

The map iteration copies references to values rather than deep-copying nested structures. Additionally, Spec.ResourceSpec and Status.ResourceStatus embedded fields are not copied.

For current test scenarios using simple scalar values, this may be sufficient. However, if tests evolve to use nested maps/slices or rely on embedded Crossplane fields, this could cause unexpected aliasing issues.

♻️ More complete DeepCopyObject (optional)
 func (tr *TestResource) DeepCopyObject() runtime.Object {
 	out := &TestResource{}
 	out.TypeMeta = tr.TypeMeta
 	out.ObjectMeta = *tr.ObjectMeta.DeepCopy()
+	out.Spec.ResourceSpec = *tr.Spec.ResourceSpec.DeepCopy()
 	out.Spec.ForProvider = make(TestResourceParameters)
 	for k, v := range tr.Spec.ForProvider {
-		out.Spec.ForProvider[k] = v
+		out.Spec.ForProvider[k] = deepCopyValue(v)
 	}
+	out.Status.ResourceStatus = *tr.Status.ResourceStatus.DeepCopy()
 	out.Status.AtProvider = make(TestResourceObservation)
 	for k, v := range tr.Status.AtProvider {
-		out.Status.AtProvider[k] = v
+		out.Status.AtProvider[k] = deepCopyValue(v)
 	}
+	out.TerraformResourceType = tr.TerraformResourceType
 	return out
 }
+
+func deepCopyValue(v interface{}) interface{} {
+	// For test purposes, simple values are sufficient
+	// Add nested map/slice handling if needed
+	return v
+}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f30a24b and d3e6d6a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (65)
  • cmd/schemadiff/main.go
  • docs/README.md
  • docs/breaking-change-detection.md
  • go.mod
  • pkg/config/conversion.go
  • pkg/config/conversion_test.go
  • pkg/config/provider.go
  • pkg/config/resource.go
  • pkg/config/testdata/invalid/malformed.json
  • pkg/config/testdata/invalid/malformed.json.license
  • pkg/config/testdata/invalid/missing-change-type.json
  • pkg/config/testdata/invalid/missing-change-type.json.license
  • pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json
  • pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json.license
  • pkg/config/testdata/invalid/type-change-missing-values.json
  • pkg/config/testdata/invalid/type-change-missing-values.json.license
  • pkg/config/testdata/invalid/type-change-same-values.json
  • pkg/config/testdata/invalid/type-change-same-values.json.license
  • pkg/config/testdata/invalid/type-change-unsupported.json
  • pkg/config/testdata/invalid/type-change-unsupported.json.license
  • pkg/config/testdata/invalid/unknown-change-type.json
  • pkg/config/testdata/invalid/unknown-change-type.json.license
  • pkg/config/testdata/valid/deeply-nested.json
  • pkg/config/testdata/valid/deeply-nested.json.license
  • pkg/config/testdata/valid/empty.json
  • pkg/config/testdata/valid/empty.json.license
  • pkg/config/testdata/valid/evolving-field.json
  • pkg/config/testdata/valid/evolving-field.json.license
  • pkg/config/testdata/valid/exclude-multiple-paths.json
  • pkg/config/testdata/valid/exclude-multiple-paths.json.license
  • pkg/config/testdata/valid/field-addition.json
  • pkg/config/testdata/valid/field-addition.json.license
  • pkg/config/testdata/valid/field-deletion.json
  • pkg/config/testdata/valid/field-deletion.json.license
  • pkg/config/testdata/valid/multiple-changes.json
  • pkg/config/testdata/valid/multiple-changes.json.license
  • pkg/config/testdata/valid/multiple-resources.json
  • pkg/config/testdata/valid/multiple-resources.json.license
  • pkg/config/testdata/valid/multiple-type-changes.json
  • pkg/config/testdata/valid/multiple-type-changes.json.license
  • pkg/config/testdata/valid/no-changes.json
  • pkg/config/testdata/valid/no-changes.json.license
  • pkg/config/testdata/valid/three-versions.json
  • pkg/config/testdata/valid/three-versions.json.license
  • pkg/config/testdata/valid/type-change-bool-to-string.json
  • pkg/config/testdata/valid/type-change-bool-to-string.json.license
  • pkg/config/testdata/valid/type-change-number-to-string.json
  • pkg/config/testdata/valid/type-change-number-to-string.json.license
  • pkg/config/testdata/valid/type-change-string-to-bool.json
  • pkg/config/testdata/valid/type-change-string-to-bool.json.license
  • pkg/config/testdata/valid/type-change-string-to-number.json
  • pkg/config/testdata/valid/type-change-string-to-number.json.license
  • pkg/config/testing_helpers.go
  • tests/conversion/integration_test.go
  • tests/conversion/test_resource.go
  • tests/testdata/bool-to-string.json
  • tests/testdata/bool-to-string.json.license
  • tests/testdata/multiple-changes.json
  • tests/testdata/multiple-changes.json.license
  • tests/testdata/number-to-string.json
  • tests/testdata/number-to-string.json.license
  • tests/testdata/string-to-bool.json
  • tests/testdata/string-to-bool.json.license
  • tests/testdata/string-to-number.json
  • tests/testdata/string-to-number.json.license
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Do not use any type throughout codebase - use concrete types or type parameters instead
Use pointer types for optional fields in generated structs
Avoid type aliases in favor of explicit types
Use github.com/pkg/errors for error wrapping with context
Return errors from functions instead of panicking, except for impossible states
Wrap errors with context using patterns like: errors.Wrap(err, "cannot configure resource")
Avoid circular dependencies between packages

Files:

  • pkg/config/resource.go
  • pkg/config/conversion_test.go
  • tests/conversion/integration_test.go
  • pkg/config/conversion.go
  • pkg/config/provider.go
  • cmd/schemadiff/main.go
  • tests/conversion/test_resource.go
  • pkg/config/testing_helpers.go
pkg/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Public API packages should be organized under pkg/ directory

Files:

  • pkg/config/resource.go
  • pkg/config/conversion_test.go
  • pkg/config/conversion.go
  • pkg/config/provider.go
  • pkg/config/testing_helpers.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*_test.go: Use standard Go testing only - no Ginkgo, Testify, or third-party test frameworks
Strongly prefer table-driven tests
Place test files in the same package as the code under test using *_test.go naming

Files:

  • pkg/config/conversion_test.go
  • tests/conversion/integration_test.go
go.mod

📄 CodeRabbit inference engine (CLAUDE.md)

go.mod: Module path must be github.com/crossplane/upjet/v2
When testing in providers, add replace github.com/crossplane/upjet/v2 => ../upjet to provider's go.mod
Run make modules.check to verify go.mod/go.sum are tidy before committing

Files:

  • go.mod
🧠 Learnings (9)
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Use Kubernetes-native patterns via crossplane-runtime

Applied to files:

  • tests/testdata/multiple-changes.json.license
  • tests/testdata/string-to-number.json.license
  • pkg/config/testdata/valid/field-deletion.json.license
  • pkg/config/testdata/valid/multiple-resources.json.license
  • pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json.license
  • pkg/config/testdata/valid/empty.json.license
  • pkg/config/testdata/valid/type-change-string-to-number.json.license
  • tests/testdata/string-to-bool.json.license
  • pkg/config/testdata/valid/multiple-type-changes.json.license
  • pkg/config/testdata/invalid/unknown-change-type.json.license
  • pkg/config/testdata/invalid/malformed.json.license
  • pkg/config/testdata/valid/no-changes.json.license
  • tests/testdata/number-to-string.json.license
  • pkg/config/testdata/valid/field-addition.json.license
  • pkg/config/testdata/invalid/missing-change-type.json.license
  • pkg/config/testdata/invalid/type-change-same-values.json.license
  • pkg/config/testdata/invalid/type-change-unsupported.json.license
  • pkg/config/testdata/valid/exclude-multiple-paths.json.license
  • pkg/config/testdata/valid/three-versions.json.license
  • pkg/config/testdata/valid/type-change-string-to-bool.json.license
  • pkg/config/testdata/valid/type-change-bool-to-string.json.license
  • pkg/config/testdata/invalid/type-change-missing-values.json.license
  • pkg/config/testdata/valid/evolving-field.json.license
  • tests/testdata/bool-to-string.json.license
  • pkg/config/testdata/valid/deeply-nested.json.license
  • pkg/config/testdata/valid/type-change-number-to-string.json.license
  • pkg/config/testdata/valid/multiple-changes.json.license
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*_test.go : Strongly prefer table-driven tests

Applied to files:

  • pkg/config/conversion_test.go
  • tests/conversion/integration_test.go
  • tests/conversion/test_resource.go
  • pkg/config/testing_helpers.go
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*_test.go : Place test files in the same package as the code under test using `*_test.go` naming

Applied to files:

  • pkg/config/conversion_test.go
  • pkg/config/testing_helpers.go
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Use interface-based design for extensibility with `resource.Terraformed` and `controller.ExternalClient` interfaces

Applied to files:

  • tests/conversion/test_resource.go
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to go.mod : Run `make modules.check` to verify go.mod/go.sum are tidy before committing

Applied to files:

  • go.mod
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to go.mod : When testing in providers, add `replace github.com/crossplane/upjet/v2 => ../upjet` to provider's go.mod

Applied to files:

  • go.mod
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to go.mod : Module path must be `github.com/crossplane/upjet/v2`

Applied to files:

  • go.mod
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*_test.go : Use standard Go testing only - no Ginkgo, Testify, or third-party test frameworks

Applied to files:

  • go.mod
📚 Learning: 2026-01-13T21:29:32.867Z
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Applies to **/*.go : Avoid circular dependencies between packages

Applied to files:

  • go.mod
🧬 Code graph analysis (5)
pkg/config/conversion_test.go (2)
pkg/config/resource.go (2)
  • Resource (440-674)
  • AutoConversionRegistrationOptions (692-739)
pkg/config/conversion.go (2)
  • RegisterAutoConversions (51-142)
  • ExcludeTypeChangesFromIdentity (163-190)
tests/conversion/integration_test.go (5)
pkg/config/testing_helpers.go (6)
  • NewTestProvider (18-27)
  • NewTestResource (37-49)
  • NewTestResourceWithIntField (59-66)
  • NewTestResourceWithStringField (93-100)
  • NewTestResourceWithBoolField (110-117)
  • LoadTestFixture (129-156)
pkg/resource/fake/terraformed.go (2)
  • Kind (242-242)
  • AddToScheme (251-251)
pkg/controller/conversion/registry.go (1)
  • RegisterConversions (80-92)
tests/conversion/test_resource.go (3)
  • TestResource (18-28)
  • TestResourceSpec (31-34)
  • TestResourceParameters (37-37)
pkg/controller/conversion/functions.go (1)
  • RoundTrip (102-104)
pkg/config/conversion.go (2)
pkg/config/resource.go (2)
  • AutoConversionRegistrationOptions (692-739)
  • Resource (440-674)
pkg/config/common.go (1)
  • GetSchema (155-179)
tests/conversion/test_resource.go (1)
pkg/config/resource.go (1)
  • Reference (209-236)
pkg/config/testing_helpers.go (2)
pkg/config/provider.go (1)
  • Provider (79-188)
pkg/config/resource.go (2)
  • Resource (440-674)
  • AutoConversionRegistrationOptions (692-739)
🪛 Biome (2.1.2)
pkg/config/testdata/invalid/malformed.json

[error] 12-12: expected , but instead found // Missing comma - malformed JSON

Remove // Missing comma - malformed JSON

(parse)


[error] 13-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-19: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🪛 markdownlint-cli2 (0.18.1)
docs/breaking-change-detection.md

20-20: Link fragments should be valid

(MD051, link-fragments)


41-41: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


169-169: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


186-186: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


191-191: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


196-196: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


330-330: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


539-539: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


555-555: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 OSV Scanner (2.3.1)
go.mod

[HIGH] 64-64: github.com/getkin/kin-openapi 0.108.0: Improper Handling of Highly Compressed Data (Data Amplification) in github.com/getkin/kin-openapi/openapi3filter

(GO-2025-3533)


[HIGH] 64-64: github.com/getkin/kin-openapi 0.108.0: Improper Handling of Highly Compressed Data (Data Amplification) in github.com/getkin/kin-openapi/openapi3filter

(GHSA-wq9g-9vfc-cfq9)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: check-diff
🔇 Additional comments (81)
pkg/config/testdata/valid/evolving-field.json (1)

1-32: LGTM!

Well-structured test data that covers an important multi-version type evolution scenario. The progressive type changes (string → number → boolean) across three API versions effectively tests the conversion chain handling in the auto-conversion system.

pkg/config/testdata/valid/evolving-field.json.license (1)

1-3: LGTM!

Standard SPDX license header following the REUSE specification for companion license files. This correctly provides Apache-2.0 licensing metadata for the associated JSON test data file.

pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json.license (1)

1-3: LGTM! Standard SPDX license header.

The SPDX license header is correctly formatted with proper copyright attribution and Apache-2.0 license identifier, appropriate for Crossplane test data files.

pkg/config/testdata/valid/multiple-resources.json.license (1)

1-3: LGTM! Standard SPDX license header.

The SPDX license header is correctly formatted with proper copyright attribution and Apache-2.0 license identifier.

pkg/config/testdata/valid/field-deletion.json.license (1)

1-3: LGTM! Standard SPDX license header.

The SPDX license header is correctly formatted with proper copyright attribution and Apache-2.0 license identifier.

pkg/config/testdata/invalid/unknown-change-type.json.license (1)

1-3: LGTM! Standard SPDX license header.

The SPDX license header is correctly formatted with proper copyright attribution and Apache-2.0 license identifier.

pkg/config/testdata/valid/multiple-changes.json.license (1)

1-3: LGTM! Standard SPDX license header.

The SPDX license header is correctly formatted with proper copyright attribution and Apache-2.0 license identifier.

pkg/config/testdata/valid/empty.json (1)

1-1: LGTM!

Valid empty JSON object, appropriate for testing edge cases in the schema diff/conversion logic.

pkg/config/testdata/valid/type-change-string-to-bool.json.license (1)

1-3: LGTM!

Standard SPDX license headers correctly formatted for the test data file.

pkg/config/testdata/valid/type-change-number-to-string.json.license (1)

1-3: LGTM!

Standard SPDX license headers correctly formatted.

tests/testdata/string-to-number.json.license (1)

1-3: LGTM!

Standard SPDX license headers correctly formatted.

tests/testdata/multiple-changes.json.license (1)

1-3: LGTM!

Standard SPDX license headers correctly formatted.

tests/testdata/string-to-bool.json.license (1)

1-3: LGTM!

Standard SPDX license metadata following project conventions.

pkg/config/testdata/valid/no-changes.json.license (1)

1-3: LGTM!

Standard SPDX license metadata following project conventions.

tests/testdata/bool-to-string.json.license (1)

1-3: LGTM!

Standard SPDX license metadata following project conventions.

pkg/config/testdata/valid/type-change-string-to-number.json.license (1)

1-3: LGTM!

Standard SPDX license metadata following project conventions.

pkg/config/testdata/valid/type-change-string-to-bool.json (1)

1-19: LGTM!

Well-structured test fixture for the string-to-boolean type conversion scenario. The JSON schema is consistent with other type-change test fixtures in the PR, with correct pathParts decomposition and appropriate changeType designation.

pkg/config/testdata/invalid/missing-change-type.json.license (1)

1-3: LGTM!

Standard SPDX license header for the companion JSON test data file. Follows the REUSE specification pattern appropriately.

pkg/config/testdata/valid/empty.json.license (1)

1-3: LGTM!

License header is consistent with other testdata license files in this PR.

pkg/config/testdata/valid/exclude-multiple-paths.json (1)

1-27: LGTM!

Well-structured test fixture for validating multiple path exclusion scenarios. The JSON structure is consistent with the schema change detection format, and the pathParts arrays correctly decompose the dot-notation paths.

pkg/config/testdata/valid/exclude-multiple-paths.json.license (1)

1-3: LGTM!

License header follows the established pattern for testdata files.

pkg/config/testdata/invalid/type-change-same-values.json.license (1)

1-3: LGTM!

License header is consistent with the project's testdata licensing approach.

tests/testdata/string-to-bool.json (1)

1-19: LGTM!

The test fixture correctly defines the string-to-boolean type change scenario with proper structure. The pathParts array accurately reflects the dot-separated path, and all required fields (path, pathParts, changeType, oldValue, newValue) are present and consistent with other type-change fixtures in the test suite.

pkg/config/testdata/valid/multiple-type-changes.json.license (1)

1-3: LGTM!

Standard SPDX license header consistent with other testdata license files in the repository.

tests/testdata/string-to-number.json (1)

1-19: LGTM!

The test fixture correctly defines the string-to-number type change scenario. Structure and field values are consistent with the established testdata pattern.

pkg/config/testdata/invalid/type-change-missing-values.json (1)

1-17: LGTM!

This invalid test fixture correctly omits oldValue and newValue fields for a type_changed entry, which should trigger validation errors in the conversion logic. Well-structured negative test case for error handling coverage.

pkg/config/testdata/invalid/type-change-missing-values.json.license (1)

1-3: LGTM!

Standard SPDX license header consistent with the repository's testdata licensing pattern.

tests/testdata/number-to-string.json (1)

1-19: LGTM!

Well-structured test fixture for the number-to-string type conversion scenario. The resource name is descriptive, the JSON structure is valid, and the pathParts array correctly matches the dot-notation path. This complements the reverse string-to-number.json fixture for comprehensive bidirectional testing.

tests/testdata/number-to-string.json.license (1)

1-3: LGTM!

Standard SPDX license header with correct Apache-2.0 identifier, consistent with the project's licensing.

pkg/config/testdata/invalid/unknown-change-type.json (1)

1-17: LGTM!

Correctly structured test fixture for validating the handling of unknown/unsupported change types. The changeType: "field_modified" is intentionally invalid (valid types are field_added, field_deleted, type_changed), and the file is appropriately placed in the invalid/ directory.

pkg/config/testdata/valid/no-changes.json (1)

1-11: LGTM!

Good edge case test fixture for validating that version migrations with no recorded changes are handled gracefully. The empty changes array is a valid scenario that should be supported.

pkg/config/testdata/valid/field-deletion.json (1)

1-17: LGTM!

Well-structured test fixture for the field deletion scenario. The changeType: "field_deleted" is valid, and the pathParts array correctly corresponds to the dot-notation path. This complements the field-addition.json fixture for comprehensive coverage of field lifecycle changes.

pkg/config/testdata/valid/field-addition.json (1)

1-17: LGTM!

The test data structure is well-formed with consistent path and pathParts values. The change type field_added correctly describes the scenario being tested.

pkg/config/testdata/valid/field-addition.json.license (1)

1-3: LGTM!

Standard SPDX license header following project conventions.

pkg/config/provider.go (1)

23-27: LGTM!

The path constants are appropriately scoped as package-private and correctly represent the standard Crossplane resource paths for forProvider, initProvider, and atProvider sections. This provides a centralized location for these commonly-used path strings.

pkg/config/testdata/valid/type-change-number-to-string.json (1)

1-19: LGTM!

The test data structure is well-formed and correctly captures the type change scenario from number to string. The path and pathParts values are consistent, and the oldValue/newValue fields properly document the type transition.

pkg/config/testdata/invalid/missing-change-type.json (1)

1-16: LGTM!

This is appropriate negative test data for validating error handling when the required changeType field is missing. The file is correctly placed in the invalid directory and will help ensure proper validation of change specifications.

pkg/config/testdata/invalid/type-change-bool-schema-mismatch.json (1)

1-20: LGTM!

Well-structured test data for validating the schema mismatch scenario. The comment clearly documents why this case is invalid (TF schema expects TypeBool but the change describes string→number).

pkg/config/testdata/invalid/type-change-same-values.json (1)

1-19: LGTM!

Good test case for validating that type_changed entries with identical oldValue and newValue are rejected. The semantic invalidity is clear.

pkg/config/testdata/valid/type-change-string-to-number.json (1)

1-19: LGTM!

Valid test data for the string-to-number type conversion scenario, which is one of the supported primitive type conversions mentioned in the PR objectives.

pkg/config/testdata/invalid/malformed.json (1)

1-19: LGTM!

Intentionally malformed JSON test data for exercising error handling during parsing. The static analysis errors (missing comma, invalid comment syntax) are expected and by design.

pkg/config/testdata/invalid/malformed.json.license (1)

1-3: LGTM!

Standard SPDX license header consistent with other testdata license files in the repository.

tests/testdata/multiple-changes.json (1)

1-33: LGTM - Well-structured test data for multiple change types.

The JSON structure is valid and covers a good combination of change scenarios (field addition and two type changes). The resource name MultiChangeResource appropriately distinguishes this from other test fixtures.

Note: The AI summary mentions a field_deleted change for spec.forProvider.oldField, but this change is not present in the actual file. The file only contains field_added and two type_changed entries.

pkg/config/testdata/invalid/type-change-unsupported.json (1)

1-19: LGTM - Appropriate invalid test case for unsupported type conversion.

This correctly tests the arraystring type change scenario, which is outside the supported primitive conversions (string↔int/float/bool) mentioned in the PR objectives.

pkg/config/testdata/invalid/type-change-unsupported.json.license (1)

1-3: LGTM - Standard SPDX license header.

pkg/config/testdata/valid/multiple-changes.json (1)

1-29: LGTM - Comprehensive test data covering all change types.

This fixture appropriately covers all three change types (field_added, field_deleted, type_changed) in a single resource version transition, providing good test coverage for the change detection logic.

pkg/config/testdata/valid/three-versions.json (1)

1-40: LGTM - Excellent test case for multi-version conversion chains.

This fixture tests an important scenario where a field (newField) is added in one version transition and then deleted in the subsequent transition. This validates that the conversion system correctly handles cumulative changes across multiple API version upgrades.

pkg/config/testdata/valid/three-versions.json.license (1)

1-3: LGTM!

Standard SPDX license metadata for the accompanying test data file.

pkg/config/testdata/valid/multiple-resources.json (1)

1-80: LGTM!

Comprehensive test data covering the key conversion scenarios:

  • Field additions (ResourceOne)
  • Type changes with string→number (ResourceTwo) and string→boolean (ResourceFour)
  • Field deletions (ResourceThree)
  • Multiple changes per resource (ResourceFour)
  • Empty changes array edge case (ResourceFive)
  • Multiple API groups (test.example.io, other.example.io)

This provides good coverage for testing the auto-conversion registration logic.

pkg/config/resource.go (1)

668-739: LGTM!

Well-designed configuration struct with:

  • Clear opt-out mechanism via SkipAutoRegistration
  • Separation of concerns between identity conversion exclusions (auto-populated) and manual exclusions (AutoRegisterExcludePaths)
  • Thorough documentation with usage examples and technical notes

The design appropriately allows resources to opt out completely or exclude specific paths from automatic conversion registration.

pkg/config/conversion.go (4)

7-17: LGTM!

Clean import organization with proper use of the errors package from crossplane-runtime per coding guidelines.


192-248: LGTM!

Helper functions are well-implemented:

  • parseChangeReports: Proper JSON unmarshaling with error wrapping
  • setIdentityConversionExcludePath: Correct deduplication using map, handles empty paths
  • isExcludedPath: Simple and clear linear search

144-190: LGTM!

Well-documented function with clear timing requirements. The deduplication logic using a map prevents duplicate exclusion paths across multiple version transitions.


209-222: The path constants are properly defined in pkg/config/provider.go (lines 24-26) and are accessible to the trimPathPrefix function. No issues found.

go.mod (1)

30-30: Testify usage complies with coding guidelines.

Testify is imported only in pkg/config/testing_helpers.go, not in any *_test.go files. The coding guideline restricts third-party test frameworks like Testify to test files only. Since testify is used exclusively in a helper/utility file to provide testing utilities, this dependency is compliant and necessary.

cmd/schemadiff/main.go (2)

27-99: Main function implementation looks correct.

The overall flow is well-structured: enumerate YAML files, create self-diffs for each CRD, aggregate non-empty change reports, and write JSON output. The use of secure file permissions (0600) and proper error handling with kingpin.FatalIfError is appropriate for a CLI tool.


101-128: LGTM!

The helper function correctly filters for YAML/YML files in a case-insensitive manner and only processes top-level files. Pre-allocating the slice with make([]string, 0, len(entries)) is a good optimization.

docs/breaking-change-detection.md (2)

7-98: Documentation is comprehensive and well-structured.

The document provides excellent coverage of the breaking change detection system, including clear explanations of build-time analysis, runtime registration phases, and conversion execution flow. The JSON examples effectively illustrate the output format.


1-5: Copyright year appears to be set in the future.

The copyright year is 2026, but the current year is 2025 based on other files in this PR. Consider updating for consistency.

Proposed fix
 <!--
-SPDX-FileCopyrightText: 2026 The Crossplane Authors <https://crossplane.io>
+SPDX-FileCopyrightText: 2025 The Crossplane Authors <https://crossplane.io>
 
 SPDX-License-Identifier: CC-BY-4.0
 -->
⛔ Skipped due to learnings
Learnt from: CR
Repo: crossplane/upjet PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T21:29:32.867Z
Learning: Use Kubernetes-native patterns via crossplane-runtime
docs/README.md (1)

43-43: LGTM!

The new documentation entry follows the established format and provides a clear description of the linked content.

pkg/config/testing_helpers.go (1)

18-49: Test helpers are well-designed.

The helpers provide good flexibility for creating test providers and resources with various Terraform schema configurations. The use of t.Helper() ensures proper stack trace reporting in test failures.

pkg/config/testdata/valid/deeply-nested.json.license (1)

1-3: LGTM!

Standard SPDX license header following the project's licensing conventions.

tests/testdata/bool-to-string.json (1)

1-19: LGTM!

Test data correctly describes a boolean-to-string type change scenario. The pathParts array properly matches the dot-separated path, and all required fields are present.

pkg/config/testdata/valid/type-change-bool-to-string.json (1)

1-19: LGTM!

Test data correctly describes a boolean-to-string type change for the enabled field. Structure is consistent with other type-change test fixtures in the test suite.

pkg/config/testdata/valid/multiple-type-changes.json (1)

1-33: LGTM!

Test data provides good coverage of multiple type changes in a single version transition, including both forProvider and initProvider paths with different type conversion directions (string↔number, string↔boolean).

pkg/config/testdata/valid/deeply-nested.json (1)

1-34: LGTM!

Excellent test coverage for deeply nested path scenarios. The data exercises:

  • Deep nesting (5+ levels)
  • Array wildcard notation ([*])
  • All change types (field_added, field_deleted, type_changed)
  • Multiple path roots (spec.forProvider, spec.initProvider, status.atProvider)

The pathParts arrays correctly correspond to their dot-separated paths with proper handling of array wildcards as discrete elements.

pkg/config/conversion_test.go (9)

1-12: LGTM! Well-structured test file with appropriate imports.

The file follows coding guidelines: uses standard Go testing, places tests in the same package as code under test, and uses google/go-cmp for comparisons which is an established library in the codebase.


14-480: Comprehensive table-driven test with excellent coverage.

The test covers a wide range of scenarios including happy paths (field additions, deletions, type changes), edge cases (nil TerraformResource, empty JSON), and error conditions (malformed JSON, schema mismatches). The test structure follows best practices with clear reason strings for each case. Based on learnings, this follows the strongly preferred table-driven test pattern.


482-554: Good multi-resource test scenario.

Tests the registration logic across multiple resources with different configurations (short groups, change types), ensuring isolation and correct conversion counts.


556-741: Well-designed test for identity exclusion logic.

Covers type change detection, path exclusion interactions, and deduplication across versions. The use of cmpopts.SortSlices is appropriate since path order shouldn't matter.


743-879: Thorough edge case coverage for path exclusion logic.

Tests handle nil/empty lists, exact vs partial matching, case sensitivity, and multiple path scenarios.


881-986: Good test for path prefix handling and deduplication.

Covers the trimming of spec.forProvider, spec.initProvider, and status.atProvider prefixes, and verifies deduplication behavior.


988-1102: Valuable integration test verifying function interoperability.

Tests that ExcludeTypeChangesFromIdentity and RegisterAutoConversions work correctly regardless of call order, which is important for provider integration flexibility.


1104-1179: Good validation test ensuring conversion integrity.

Verifies that all registered conversions are non-nil, which is an important sanity check to prevent runtime panics.


1181-1249: Complete coverage for path prefix trimming.

Tests all expected prefixes and correctly handles non-matching paths returning empty strings.

tests/conversion/integration_test.go (4)

1-33: LGTM! Clean imports and well-documented purpose.

The Level 2 integration test documentation clearly explains the distinction from Level 1 tests (registration vs. actual execution).


146-334: Well-structured field addition/deletion tests.

Tests cover annotation-based storage and restoration during version conversions, including roundtrip verification. The test assertions are thorough.


340-534: Comprehensive type conversion tests with proper numeric handling.

The type switch pattern (lines 388-398, 519-529) correctly handles JSON's tendency to unmarshal numbers as float64, while also supporting int64 and int types. This defensive approach ensures test stability.


800-1079: Excellent integration test combining multiple change types.

Tests verify that field additions, type conversions (string→int, string→bool), and annotation-based storage all work correctly together in both directions and through roundtrips.

tests/conversion/test_resource.go (2)

1-49: Well-structured test resource implementing Terraformed interface.

The struct layout properly embeds Crossplane runtime types and uses map-based parameters for flexible test data. The interface assertion ensures compile-time verification.


51-165: Interface methods correctly implemented for test purposes.

The implementations are minimal but sufficient for conversion testing. The GetTerraformResourceType fallback to "test_resource" and the configurable override via field is a good design for flexible testing.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +35 to +37
if out == nil || *out == "" {
kingpin.Fatalf("output directory file")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix incomplete error message.

The error message is grammatically incomplete - it should describe what is required.

Proposed fix
 	if out == nil || *out == "" {
-		kingpin.Fatalf("output directory file")
+		kingpin.Fatalf("output file required")
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if out == nil || *out == "" {
kingpin.Fatalf("output directory file")
}
if out == nil || *out == "" {
kingpin.Fatalf("output file required")
}
🤖 Prompt for AI Agents
In `@cmd/schemadiff/main.go` around lines 35 - 37, The error message in the
validation for out (the nil/empty check using kingpin.Fatalf) is incomplete;
update the kingpin.Fatalf call in cmd/schemadiff/main.go (the block that checks
if out == nil || *out == "") to a clear, grammatically complete message such as
"output directory is required" or "must specify output directory" so the error
describes what is required.

github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/getkin/kin-openapi v0.108.0 // indirect
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the directory context and read the full go.mod
pwd
head -70 go.mod | tail -20

Repository: crossplane/upjet

Length of output: 1139


🏁 Script executed:

# Find which direct dependency brings in kin-openapi
go mod why github.com/getkin/kin-openapi

# Also check the full go.mod to find all direct dependencies
echo "=== Direct dependencies ===" 
grep -v "// indirect" go.mod | grep "github.com" | head -30

Repository: crossplane/upjet

Length of output: 9764


🌐 Web query:

GO-2025-3533 GHSA-wq9g-9vfc-cfq9 kin-openapi data amplification vulnerability

💡 Result:

Summary — GO-2025-3533 / GHSA-wq9g-9vfc-cfq9 (CVE-2025-30153)

  • Issue: Improper handling of highly-compressed data (data amplification / ZIP-bomb) in github.com/getkin/kin-openapi openapi3filter (ZipFileBodyDecoder). An attacker can upload a crafted ZIP via multipart/form-data and cause the server to exhaust memory (DoS). (osv.dev)
  • Affected versions: kin-openapi before v0.131.0 (openapi3filter symbols such as ValidateRequest / ValidateRequestBody / ValidationHandler.ServeHTTP). (developers.golang.nutanix.com)
  • Severity: CVSS 3.1 7.5 (High). (osv.dev)
  • Fix / mitigation: Upgrade kin-openapi to v0.131.0 or later (patch/PR and commit linked in advisories). As a mitigation, avoid automatically trusting/unrestricted zip file decoding in request validation and apply input size/decompression limits where feasible. (developers.golang.nutanix.com)

If you want, I can fetch the exact advisory and patch commit links or produce a short patch/upgrade checklist for your project.

Citations:


🏁 Script executed:

# Find which direct dependency brings in kin-openapi
rg "getkin/kin-openapi" --type go
rg "openapi3filter" --type go

# Check go.mod details to see if we can identify the chain
cat go.mod | grep -A 100 "require ("

Repository: crossplane/upjet

Length of output: 6273


🌐 Web query:

kin-openapi v0.131.0 release date fix version GO-2025-3533

💡 Result:

kin-openapi v0.131.0 was released on March 19, 2025. It fixes vulnerability GO-2025-3533 (alias CVE-2025-30153 / GHSA-wq9g-9vfc-cfq9); the vuln is fixed in v0.131.0. [1][2]

Sources:

  • Release: getkin/kin-openapi v0.131.0 (GitHub). [1]
  • Vulnerability: GO-2025-3533 / CVE-2025-30153 (pkg.go.dev / OSV). [2]

Correct HIGH severity vulnerability fix version for indirect dependency.

The vulnerability GO-2025-3533 (GHSA-wq9g-9vfc-cfq9) in github.com/getkin/kin-openapi v0.108.0 is confirmed: a data amplification vulnerability in openapi3filter that allows denial-of-service attacks. However, the fix version is v0.131.0 or later (not v0.121.0+). Identify and update the direct dependency that pulls in kin-openapi to a version using the patched kin-openapi.

🧰 Tools
🪛 OSV Scanner (2.3.1)

[HIGH] 64-64: github.com/getkin/kin-openapi 0.108.0: Improper Handling of Highly Compressed Data (Data Amplification) in github.com/getkin/kin-openapi/openapi3filter

(GO-2025-3533)


[HIGH] 64-64: github.com/getkin/kin-openapi 0.108.0: Improper Handling of Highly Compressed Data (Data Amplification) in github.com/getkin/kin-openapi/openapi3filter

(GHSA-wq9g-9vfc-cfq9)

🤖 Prompt for AI Agents
In `@go.mod` at line 64, The go.mod currently pulls in
github.com/getkin/kin-openapi v0.108.0 (indirect) which is vulnerable; identify
which direct module in your dependency graph depends on
github.com/getkin/kin-openapi (use go mod graph or go list -m all to locate the
parent module), then bump that direct dependency to a release that itself
requires kin-openapi v0.131.0 or later (use go get
<direct-module>@<fixed-version>), run go mod tidy to update go.mod/go.sum, and
verify that github.com/getkin/kin-openapi is now recorded at v0.131.0+ as an
indirect dependency.

Comment on lines +95 to +126
var data1, data2, data3, data4, data5, data6 map[string]interface{}
json.Unmarshal(fixture1, &data1)
json.Unmarshal(fixture2, &data2)
json.Unmarshal(fixture3, &data3)
json.Unmarshal(fixture4, &data4)
json.Unmarshal(fixture5, &data5)
json.Unmarshal(fixture6, &data6)

merged := make(map[string]interface{})
for k, v := range data1 {
merged[k] = v
}
for k, v := range data2 {
merged[k] = v
}
for k, v := range data3 {
merged[k] = v
}
for k, v := range data4 {
merged[k] = v
}
for k, v := range data5 {
merged[k] = v
}
for k, v := range data6 {
merged[k] = v
}

mergedJSON, err := json.Marshal(merged)
if err != nil {
t.Fatal(err)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unchecked json.Unmarshal errors could mask test failures.

The return values from json.Unmarshal calls (lines 96-101) are discarded. If any fixture contains invalid JSON, the test would silently proceed with empty/partial data instead of failing explicitly.

🔧 Proposed fix
 	var data1, data2, data3, data4, data5, data6 map[string]interface{}
-	json.Unmarshal(fixture1, &data1)
-	json.Unmarshal(fixture2, &data2)
-	json.Unmarshal(fixture3, &data3)
-	json.Unmarshal(fixture4, &data4)
-	json.Unmarshal(fixture5, &data5)
-	json.Unmarshal(fixture6, &data6)
+	if err := json.Unmarshal(fixture1, &data1); err != nil {
+		t.Fatalf("Failed to unmarshal fixture1: %v", err)
+	}
+	if err := json.Unmarshal(fixture2, &data2); err != nil {
+		t.Fatalf("Failed to unmarshal fixture2: %v", err)
+	}
+	if err := json.Unmarshal(fixture3, &data3); err != nil {
+		t.Fatalf("Failed to unmarshal fixture3: %v", err)
+	}
+	if err := json.Unmarshal(fixture4, &data4); err != nil {
+		t.Fatalf("Failed to unmarshal fixture4: %v", err)
+	}
+	if err := json.Unmarshal(fixture5, &data5); err != nil {
+		t.Fatalf("Failed to unmarshal fixture5: %v", err)
+	}
+	if err := json.Unmarshal(fixture6, &data6); err != nil {
+		t.Fatalf("Failed to unmarshal fixture6: %v", err)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var data1, data2, data3, data4, data5, data6 map[string]interface{}
json.Unmarshal(fixture1, &data1)
json.Unmarshal(fixture2, &data2)
json.Unmarshal(fixture3, &data3)
json.Unmarshal(fixture4, &data4)
json.Unmarshal(fixture5, &data5)
json.Unmarshal(fixture6, &data6)
merged := make(map[string]interface{})
for k, v := range data1 {
merged[k] = v
}
for k, v := range data2 {
merged[k] = v
}
for k, v := range data3 {
merged[k] = v
}
for k, v := range data4 {
merged[k] = v
}
for k, v := range data5 {
merged[k] = v
}
for k, v := range data6 {
merged[k] = v
}
mergedJSON, err := json.Marshal(merged)
if err != nil {
t.Fatal(err)
}
var data1, data2, data3, data4, data5, data6 map[string]interface{}
if err := json.Unmarshal(fixture1, &data1); err != nil {
t.Fatalf("Failed to unmarshal fixture1: %v", err)
}
if err := json.Unmarshal(fixture2, &data2); err != nil {
t.Fatalf("Failed to unmarshal fixture2: %v", err)
}
if err := json.Unmarshal(fixture3, &data3); err != nil {
t.Fatalf("Failed to unmarshal fixture3: %v", err)
}
if err := json.Unmarshal(fixture4, &data4); err != nil {
t.Fatalf("Failed to unmarshal fixture4: %v", err)
}
if err := json.Unmarshal(fixture5, &data5); err != nil {
t.Fatalf("Failed to unmarshal fixture5: %v", err)
}
if err := json.Unmarshal(fixture6, &data6); err != nil {
t.Fatalf("Failed to unmarshal fixture6: %v", err)
}
merged := make(map[string]interface{})
for k, v := range data1 {
merged[k] = v
}
for k, v := range data2 {
merged[k] = v
}
for k, v := range data3 {
merged[k] = v
}
for k, v := range data4 {
merged[k] = v
}
for k, v := range data5 {
merged[k] = v
}
for k, v := range data6 {
merged[k] = v
}
mergedJSON, err := json.Marshal(merged)
if err != nil {
t.Fatal(err)
}
🤖 Prompt for AI Agents
In `@tests/conversion/integration_test.go` around lines 95 - 126, The
json.Unmarshal calls into data1..data6 ignore returned errors, so parse failures
get swallowed; update each json.Unmarshal(fixtureX, &dataX) to capture its error
and fail the test on error (e.g., if err := json.Unmarshal(...); err != nil {
t.Fatalf("unmarshal fixtureX: %v", err) }) so invalid fixtures cause the test to
fail before merging into merged and marshalling mergedJSON.

Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
…onversion logics

Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.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