Skip to content

xrd: Opportunistically infer integer types when generating from an XR#108

Merged
adamwg merged 2 commits into
crossplane:mainfrom
adamwg:awg/infer-integers
Jun 15, 2026
Merged

xrd: Opportunistically infer integer types when generating from an XR#108
adamwg merged 2 commits into
crossplane:mainfrom
adamwg:awg/infer-integers

Conversation

@adamwg

@adamwg adamwg commented Jun 15, 2026

Copy link
Copy Markdown
Member

Description of your changes

When generating an XRD from an example XR, we have to infer the type of each field from the example input. We do this by unmarshalling into a map[string]any and then observing the actual type of each value.

Since the JSON specification has only a single numerical type, which is floating point, json.Unmarshal treats all numbers as floats. This means a field like replicas: 3 in an example XR produces a number field in the OpenAPI spec when an integer field would be more appropriate.

Detect integers by comparing the truncation of the value to the original value. This is an imperfect heuristic since not all integers are representable as floats, but it will work for common cases (small integers) and produce less surprising behavior for users.

I have:

When generating an XRD from an example XR, we have to infer the type of each
field from the example input. We do this by unmarshalling into a
`map[string]any` and then observing the actual type of each value.

Since the JSON specification has only a single numerical type, which is floating
point, `json.Unmarshal` treats all numbers as floats. This means a field like
`replicas: 3` in an example XR produces a `number` field in the OpenAPI spec
when an `integer` field would be more appropriate.

Detect integers by comparing the truncation of the value to the original
value. This is an imperfect heuristic since not all integers are representable
as floats, but it will work for common cases (small integers) and produce less
surprising behavior for users.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
@adamwg adamwg requested review from a team, jcogilvie and tampakrap as code owners June 15, 2026 16:55
@adamwg adamwg requested review from bobh66 and removed request for a team June 15, 2026 16:55
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The math package is imported and a set of schemaType* constants are introduced to replace hard-coded JSON schema type strings throughout the inference logic. Float type inference is enhanced to use math.Trunc to detect whole-valued floats and infer them as "integer" rather than "number". Array schema inference is updated to treat "integer" and "number" element types as compatible, reconciling mixed numeric types to "number" instead of raising a mixed-types error. All corresponding test expectations are updated to use the new constants, and a new "IntegerAsFloatType" test case is added.

Changes

JSON schema type inference improvements

Layer / File(s) Summary
Schema type constants and imports
internal/xrd/infer.go
Introduces math import and defines schemaType* constants for "integer", "number", "object", "array", "string", and "boolean" literals used throughout schema inference.
Float-to-integer inference heuristic and test
internal/xrd/infer.go, internal/xrd/infer_test.go
Applies math.Trunc to float32 and float64 values in inferProperty, emitting schemaTypeInteger when the float has no fractional component and schemaTypeNumber otherwise. Adds a "IntegerAsFloatType" test case verifying float64(1) infers as "integer".
Array element type compatibility and merging
internal/xrd/infer.go, internal/xrd/infer_test.go
Modifies array schema inference to allow mixing "integer" and "number" element types by promoting to "number", rather than immediately failing with a mixed-types error. Preserves strict type checking for other mismatches. Updates array-related test cases.
Schema type constant adoption in array and test code
internal/xrd/infer.go, internal/xrd/infer_test.go
Replaces all remaining hard-coded schema type strings with schemaType* constants across array inference logic and test expectations in TestInferProperty and TestInferProperties.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Breaking Changes ❌ Error PR removes exported Output field from cmd/crossplane/validate/Cmd struct without breaking-change label. Add 'breaking-change' label or restore Output field to maintain API compatibility.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: opportunistically inferring integer types during XRD generation from example XRs, which directly aligns with the changeset's core functionality.
Description check ✅ Passed The description thoroughly explains the problem (JSON unmarshalling treats numbers as floats), the solution (comparing truncation to detect integers), and acknowledges limitations, directly relating to all changes in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Feature Gate Requirement ✅ Passed This PR improves existing type inference functionality (not a new experimental feature) in internal/xrd, which is not subject to feature gate requirements; the XRD command is already beta-maturity.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/xrd/infer_test.go (1)

56-61: ⚡ Quick win

Please add one regression case for mixed numeric floats in arrays.

Great addition for scalar inference. Could you also add a case that exercises []any{float64(1), float64(1.5)} (or equivalent via InferProperties) so we lock in intended numeric-array behavior and prevent silent regressions here?

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

In `@internal/xrd/infer_test.go` around lines 56 - 61, Add a new regression test
case as a sibling to the IntegerAsFloatType test case in the same test
structure. The new test case should exercise mixed numeric floats in an array
input, such as []any{float64(1), float64(1.5)}, either by passing it directly as
input or using InferProperties, and should specify the expected JSONSchemaProps
output to lock in the intended numeric-array behavior. This ensures silent
regressions in array numeric type inference are prevented.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/xrd/infer.go`:
- Around line 111-133: The heuristic change now causes array type inference to
fail when arrays contain both whole-number floats (inferred as "integer") and
decimal floats (inferred as "number"). Fix the type compatibility logic in the
inferArrayProperty function (referenced at Line 74) to treat "integer" and
"number" types as compatible rather than conflicting. When merging array element
types, if both "integer" and "number" types are present, promote the merged
result to "number" type to allow numeric arrays with mixed whole and decimal
numbers.

---

Nitpick comments:
In `@internal/xrd/infer_test.go`:
- Around line 56-61: Add a new regression test case as a sibling to the
IntegerAsFloatType test case in the same test structure. The new test case
should exercise mixed numeric floats in an array input, such as
[]any{float64(1), float64(1.5)}, either by passing it directly as input or using
InferProperties, and should specify the expected JSONSchemaProps output to lock
in the intended numeric-array behavior. This ensures silent regressions in array
numeric type inference are prevented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a22cff0d-cfa0-4202-859c-7852f75c86b0

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfeb97 and 24976cd.

📒 Files selected for processing (2)
  • internal/xrd/infer.go
  • internal/xrd/infer_test.go

Comment thread internal/xrd/infer.go
If an array in an example XR contains a mix of integers and floats, use "number"
as the array's element type rather than returning an error.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/xrd/infer_test.go (1)

180-183: ⚡ Quick win

Switch error assertions to cmp.Diff + cmpopts.EquateErrors() for guideline compliance.

Nice coverage additions here. Could we align the error checks with the repo’s test pattern? Comparing err.Error() strings is brittle and bypasses the prescribed cmpopts.EquateErrors() flow.

Suggested patch
 import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@
-			if err != nil || tc.want.err != nil {
-				if err == nil || tc.want.err == nil || err.Error() != tc.want.err.Error() {
-					t.Errorf("inferProperty() error = %v, wantErr %v", err, tc.want.err)
-				}
-				return
-			}
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("inferProperty() -wantErr, +gotErr:\n%s", diff)
+				return
+			}
@@
-			if err != nil || tc.want.err != nil {
-				if err == nil || tc.want.err == nil || err.Error() != tc.want.err.Error() {
-					t.Errorf("InferProperties() error = %v, wantErr %v", err, tc.want.err)
-				}
-				return
-			}
+			if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("InferProperties() -wantErr, +gotErr:\n%s", diff)
+				return
+			}

As per coding guidelines for **/*_test.go, error testing should use cmp.Diff with cmpopts.EquateErrors().

Also applies to: 263-266

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

In `@internal/xrd/infer_test.go` around lines 180 - 183, Replace the error string
comparison logic in the inferProperty test function with the repository's
prescribed testing pattern. At lines 180-183 and also at lines 263-266, replace
the manual error comparison using err.Error() string comparison with cmp.Diff
combined with cmpopts.EquateErrors(). Instead of checking if err == nil,
tc.want.err == nil, and comparing error strings, use cmp.Diff to compare the
errors directly with EquateErrors option to properly validate error values
according to the repo's testing guidelines for test files.

Source: Coding guidelines

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

Inline comments:
In `@internal/xrd/infer.go`:
- Around line 93-95: The error message in the mixed-type detection block for
arrays in the inferSchema function is too generic and doesn't provide users with
actionable information. Enhance the error message returned when elemSchema.Type
differs from firstElemSchema.Type by including the actual conflicting types
being detected (extract the Type field values from both elemSchema and
firstElemSchema) and append a clear next step such as "ensure all array elements
have consistent types" to help users understand how to resolve the issue
quickly.

---

Nitpick comments:
In `@internal/xrd/infer_test.go`:
- Around line 180-183: Replace the error string comparison logic in the
inferProperty test function with the repository's prescribed testing pattern. At
lines 180-183 and also at lines 263-266, replace the manual error comparison
using err.Error() string comparison with cmp.Diff combined with
cmpopts.EquateErrors(). Instead of checking if err == nil, tc.want.err == nil,
and comparing error strings, use cmp.Diff to compare the errors directly with
EquateErrors option to properly validate error values according to the repo's
testing guidelines for test files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00bee349-8dc3-4de3-8407-21c7c1cd9339

📥 Commits

Reviewing files that changed from the base of the PR and between 24976cd and 25a5e5a.

📒 Files selected for processing (2)
  • internal/xrd/infer.go
  • internal/xrd/infer_test.go

Comment thread internal/xrd/infer.go
Comment on lines 93 to 95
if elemSchema.Type != firstElemSchema.Type {
return extv1.JSONSchemaProps{}, errors.New("mixed types detected in array")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Improve mixed-array error to include conflicting types and a next step (Line 94).

Thanks for implementing numeric-type reconciliation here — one thing to tighten is the remaining mixed-type error path. Right now mixed types detected in array is pretty opaque for CLI users. Could we include both observed types and guidance (e.g., “make array element types consistent”) so users can fix their XR quickly?

Suggested patch
-		if elemSchema.Type != firstElemSchema.Type {
-			return extv1.JSONSchemaProps{}, errors.New("mixed types detected in array")
-		}
+		if elemSchema.Type != firstElemSchema.Type {
+			return extv1.JSONSchemaProps{}, errors.Errorf(
+				"cannot infer schema for array: found mixed element types (%q and %q); make all elements use a consistent type",
+				firstElemSchema.Type, elemSchema.Type,
+			)
+		}

As per coding guidelines, error messages should be meaningful to end users, include context, and suggest next steps.

📝 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 elemSchema.Type != firstElemSchema.Type {
return extv1.JSONSchemaProps{}, errors.New("mixed types detected in array")
}
if elemSchema.Type != firstElemSchema.Type {
return extv1.JSONSchemaProps{}, errors.Errorf(
"cannot infer schema for array: found mixed element types (%q and %q); make all elements use a consistent type",
firstElemSchema.Type, elemSchema.Type,
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/xrd/infer.go` around lines 93 - 95, The error message in the
mixed-type detection block for arrays in the inferSchema function is too generic
and doesn't provide users with actionable information. Enhance the error message
returned when elemSchema.Type differs from firstElemSchema.Type by including the
actual conflicting types being detected (extract the Type field values from both
elemSchema and firstElemSchema) and append a clear next step such as "ensure all
array elements have consistent types" to help users understand how to resolve
the issue quickly.

Source: Coding guidelines

@adamwg adamwg merged commit 3cbfbf6 into crossplane:main Jun 15, 2026
10 checks passed
@adamwg adamwg deleted the awg/infer-integers branch June 15, 2026 19:17
@github-actions

Copy link
Copy Markdown

Successfully created backport PR for release-2.3:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants