Skip to content

fix(converter): convert exclusiveMaximum/exclusiveMinimum to numeric semantics for OAS 3.1 targets#360

Merged
erraggy merged 1 commit intomainfrom
fix/converter-exclusive-min-max-oas31
Mar 31, 2026
Merged

fix(converter): convert exclusiveMaximum/exclusiveMinimum to numeric semantics for OAS 3.1 targets#360
erraggy merged 1 commit intomainfrom
fix/converter-exclusive-min-max-oas31

Conversation

@erraggy
Copy link
Copy Markdown
Owner

@erraggy erraggy commented Mar 31, 2026

Summary

Fixes #358

OAS 2.0/3.0 use exclusiveMaximum/exclusiveMinimum as booleans that modify an adjacent maximum/minimum bound. OAS 3.1 (JSON Schema 2020-12) changed these to standalone numeric values, dropping maximum/minimum entirely. The converter previously copied the boolean form regardless of target version, producing invalid OAS 3.1 output.

  • When targeting OAS 3.1+: sets exclusiveMaximum to *maximum and clears maximum (same for min)
  • When targeting OAS 3.0: boolean form is preserved (no change in behaviour)
  • Edge case: exclusiveMaximum: true with no maximum (malformed OAS 2.0) now emits a SeverityWarning to ConversionResult instead of silently dropping the constraint

Scope

All four conversion sites updated:

Site File
convertOAS2ParameterToOAS3 — inline parameter type/format schema helpers.go
convertOAS2ResponseToOAS3Old — response schemas (component + per-operation) helpers.go
convertOAS2FormDataToRequestBody — formData parameter properties oas2_to_oas3.go
convertOAS2ItemsToSchema — nested items arrays oas2_to_oas3.go
convertOAS2SchemaToOAS3 — component definitions via fixSchemaExclusiveMinMaxForOAS31 schema_convert.go

fixSchemaExclusiveMinMaxForOAS31 is a new recursive schema walker that mirrors the structure of walkSchemaFeatures, visiting all nested schema locations (properties, allOf/anyOf/oneOf, items, patternProperties, $defs, if/then/else, etc.) with cycle detection via a visited map.

convertOAS2ItemsToSchema gained c *Converter, result *ConversionResult, path string parameters — consistent with every other conversion helper in this package.

Test plan

  • OAS 3.1 target: exclusiveMaximum: true + maximum: 100exclusiveMaximum: 100, maximum removed
  • OAS 3.0 target: boolean form preserved, maximum unchanged
  • OAS 3.1 target: exclusiveMaximum: true with nil maximum → warning emitted, no panic
  • Nested items: targetVersion threaded recursively
  • Nested schema properties: recursive walker converts deeply nested fields
  • allOf traversal: composition schemas converted correctly
  • false boolean → clears to nil (valid OAS 3.1 no-op removal)
  • formData parameters: OAS 3.1 numeric and OAS 3.0 boolean paths
  • No mutation of original schema input

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

Updated OAS 2.0 → OAS 3.x conversion to propagate conversion context (target version, result, path) through schema/parameter/response/item/requestBody conversions and to normalize exclusiveMaximum/exclusiveMinimum to numeric semantics for OAS 3.1+ with scoped warning reporting.

Changes

Cohort / File(s) Summary
Schema Conversion
converter/schema_convert.go
convertOAS2SchemaToOAS3 signature changed to accept targetVersion, result, and path. Added fixSchemaExclusiveMinMaxForOAS31 to recursively convert boolean exclusive bounds to numeric semantics for OAS 3.1+, with cycle detection and issue reporting.
Helpers: params & responses
converter/helpers.go
Threaded conversion context into convertOAS2ParameterToOAS3 and convertOAS2ResponseToOAS3Old. Convert parameter schema/items using contextual conversion APIs. For OAS 3.1+ convert boolean exclusives into numeric boundaries (clearing original bounds) or emit warnings if boundary is missing; preserve boolean behavior for OAS 3.0.
Pipeline & items/requestBody
converter/oas2_to_oas3.go
Propagated targetVersion, result, and path through schema/response/requestBody/item conversions. Avoid double-converting body/formData params by filtering earlier. Convert schemas once and deep-copy per media type. Updated convertOAS2ItemsToSchema to accept (c, items, result, path) and recurse with path + ".items".
Tests
converter/helpers_test.go
Added extensive tests (≈404 lines) to validate exclusive min/max conversion across parameters, items, formData/requestBody, nested schemas, allOf, nil-safety, warning emission, and immutability of source schemas.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: converting exclusiveMaximum/exclusiveMinimum to numeric semantics for OAS 3.1 targets, which is the primary focus of all file changes.
Description check ✅ Passed The description provides relevant context about the OAS specification differences, the conversion behavior changes, scope across multiple conversion sites, and comprehensive test coverage, all directly related to the changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #358 requirements: converts exclusive boundaries to numeric semantics for OAS 3.1+ [#358], preserves boolean form for OAS 3.0 [#358], emits warnings for malformed input [#358], and applies fixes across all specified conversion sites [#358].
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing exclusiveMaximum/exclusiveMinimum conversion across the four primary conversion sites (parameters, responses, formData, items) and schema definitions, with supporting test additions—no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/converter-exclusive-min-max-oas31

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.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 69.38776% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.75%. Comparing base (e92b2ec) to head (cc49aac).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
converter/schema_convert.go 53.12% 18 Missing and 12 partials ⚠️
converter/oas2_to_oas3.go 82.75% 8 Missing and 2 partials ⚠️
converter/helpers.go 80.00% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #360      +/-   ##
==========================================
- Coverage   84.81%   84.75%   -0.06%     
==========================================
  Files         194      194              
  Lines       27345    27458     +113     
==========================================
+ Hits        23192    23272      +80     
- Misses       2820     2842      +22     
- Partials     1333     1344      +11     
Files with missing lines Coverage Δ
converter/helpers.go 79.06% <80.00%> (-1.55%) ⬇️
converter/oas2_to_oas3.go 87.45% <82.75%> (+2.16%) ⬆️
converter/schema_convert.go 69.44% <53.12%> (-10.81%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
converter/helpers.go (1)

241-253: ⚠️ Potential issue | 🟡 Minor

Convert the response schema once before filling content.

convertOAS2SchemaToOAS3 can now append warnings. Running it inside the produces loop emits the same issue once per media type even though OAS 2.0 has a single response.schema. A regression with two produces values would catch this.

🔧 Suggested change
+		convertedSchema := c.convertOAS2SchemaToOAS3(response.Schema, targetVersion, result, path+".schema")
 		for _, mediaType := range mediaTypes {
+			var schemaCopy *parser.Schema
+			if convertedSchema != nil {
+				schemaCopy = convertedSchema.DeepCopy()
+			}
 			converted.Content[mediaType] = &parser.MediaType{
-				Schema: c.convertOAS2SchemaToOAS3(response.Schema, targetVersion, result, path+".schema"),
+				Schema: schemaCopy,
 			}
 		}
As per coding guidelines: Use generated `DeepCopy()` methods for deep copying; never use JSON marshal/unmarshal for deep copying.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@converter/helpers.go` around lines 241 - 253, The conversion of an OAS2
response schema is being run inside the produces loop (see response.Schema,
produces and convertOAS2SchemaToOAS3), which causes duplicate warnings for each
media type; instead call convertOAS2SchemaToOAS3 once before iterating
mediaTypes, store the returned parser.Schema result, then for each media type
assign a deep copy (use the generated DeepCopy() method) to
converted.Content[mediaType] so each media type gets its own copy without
re-running conversion or using JSON marshal/unmarshal; ensure you fall back to
getDefaultMediaType() when produces is empty.
converter/oas2_to_oas3.go (1)

180-206: ⚠️ Potential issue | 🟠 Major

Don't emit the new exclusive-bound warnings in both passes.

By the time this branch runs, the same body/formData parameter has already been converted once via convertParameters. The new OAS 3.1 checks now fire in that discarded parameter pass and then again when the source is remapped into a request body, so one malformed input produces duplicate issues. Please keep the request-body remap as the only place that emits those schema warnings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@converter/oas2_to_oas3.go` around lines 180 - 206, The duplicate
"exclusive-bound" schema warnings come from validating body/formData parameters
twice—once in convertParameters and again when remapping into a RequestBody—so
update the earlier parameter-pass to suppress those specific schema checks for
parameters with In == "body" or In == "formData" (or add a boolean flag to
convertParameters to skip emitting exclusive-bound warnings for parameters that
will be remapped), leaving convertOAS2RequestBody and
convertOAS2FormDataToRequestBody as the sole emitters of those warnings (use
addIssueWithContext only in the request-body remap code paths).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@converter/helpers.go`:
- Around line 51-52: The conversion call for parameter schemas forwards the
parent path as-is causing nested diagnostic paths like
parameters[0].properties.foo; update the call that sets converted.Schema by
appending ".schema" to the forwarded path so warnings point to
parameters[0].schema.properties.foo; specifically modify the invocation of
c.convertOAS2SchemaToOAS3 (which currently passes path) to pass path+".schema"
(keeping result.TargetOASVersion and result unchanged) so param.Schema ->
converted.Schema mapping uses the correct path.

In `@converter/oas2_to_oas3.go`:
- Around line 247-250: The loop is calling convertOAS2SchemaToOAS3 for each
media type causing duplicated warnings; instead call convertOAS2SchemaToOAS3
once (using bodyParam.Schema, result.TargetOASVersion, result, "requestBody") to
produce a single converted schema, then assign a deep copy of that converted
schema to requestBody.Content[mediaType] for each entry in consumes using the
generated DeepCopy() method on the converted schema (do not use JSON
marshal/unmarshal for copying); update the block that fills requestBody.Content
to compute convertedSchema := c.convertOAS2SchemaToOAS3(...) once and then set
requestBody.Content[mediaType].Schema = convertedSchema.DeepCopy() inside the
loop.

---

Outside diff comments:
In `@converter/helpers.go`:
- Around line 241-253: The conversion of an OAS2 response schema is being run
inside the produces loop (see response.Schema, produces and
convertOAS2SchemaToOAS3), which causes duplicate warnings for each media type;
instead call convertOAS2SchemaToOAS3 once before iterating mediaTypes, store the
returned parser.Schema result, then for each media type assign a deep copy (use
the generated DeepCopy() method) to converted.Content[mediaType] so each media
type gets its own copy without re-running conversion or using JSON
marshal/unmarshal; ensure you fall back to getDefaultMediaType() when produces
is empty.

In `@converter/oas2_to_oas3.go`:
- Around line 180-206: The duplicate "exclusive-bound" schema warnings come from
validating body/formData parameters twice—once in convertParameters and again
when remapping into a RequestBody—so update the earlier parameter-pass to
suppress those specific schema checks for parameters with In == "body" or In ==
"formData" (or add a boolean flag to convertParameters to skip emitting
exclusive-bound warnings for parameters that will be remapped), leaving
convertOAS2RequestBody and convertOAS2FormDataToRequestBody as the sole emitters
of those warnings (use addIssueWithContext only in the request-body remap code
paths).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9610f6e2-53a2-4085-b048-247c2312ab98

📥 Commits

Reviewing files that changed from the base of the PR and between e92b2ec and 774c912.

📒 Files selected for processing (4)
  • converter/helpers.go
  • converter/helpers_test.go
  • converter/oas2_to_oas3.go
  • converter/schema_convert.go

…semantics for OAS 3.1 targets

OAS 2.0 and 3.0 use boolean exclusiveMaximum/exclusiveMinimum to modify
the adjacent maximum/minimum bound. OAS 3.1 (JSON Schema 2020-12) changed
these to standalone numeric values, dropping maximum/minimum entirely.

The converter previously copied the boolean form regardless of target
version, producing invalid OAS 3.1 output.

Fix all four conversion sites:
- convertOAS2ParameterToOAS3: inline parameter type/format schema
- convertOAS2FormDataToRequestBody: formData parameter properties
- convertOAS2ItemsToSchema: nested items arrays (new c/result/path params)
- convertOAS2SchemaToOAS3: component definitions via new recursive
  fixSchemaExclusiveMinMaxForOAS31 walker (mirrors walkSchemaFeatures)

For OAS 3.1 targets, each site now sets exclusiveMaximum to *maximum
and clears maximum. For OAS 3.0 targets, boolean form is preserved.

Edge case: exclusiveMaximum:true with no maximum (malformed OAS 2.0)
emits a SeverityWarning to ConversionResult at all four sites rather
than silently dropping the constraint.

Closes #358

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@erraggy erraggy force-pushed the fix/converter-exclusive-min-max-oas31 branch from 774c912 to cc49aac Compare March 31, 2026 02:37
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@converter/helpers.go`:
- Around line 70-97: The code reads result.TargetOASVersion and calls
c.addIssueWithContext(result, ...) without ensuring result is non-nil, risking a
nil-pointer panic; update the blocks handling param.ExclusiveMaximum and
param.ExclusiveMinimum to first check if result != nil before accessing
result.TargetOASVersion or invoking c.addIssueWithContext (e.g., compute
isOAS31OrLater only when result is non-nil or use a safe default, and only call
c.addIssueWithContext when result is non-nil), referencing the symbols
param.ExclusiveMaximum, param.ExclusiveMinimum, c.isOAS31OrLater,
result.TargetOASVersion, and c.addIssueWithContext to locate where to add the
guard.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 16039dff-5002-43d8-a3d6-50f77b65a4ae

📥 Commits

Reviewing files that changed from the base of the PR and between 774c912 and cc49aac.

📒 Files selected for processing (4)
  • converter/helpers.go
  • converter/helpers_test.go
  • converter/oas2_to_oas3.go
  • converter/schema_convert.go

@erraggy erraggy merged commit 81c55d9 into main Mar 31, 2026
14 checks passed
@erraggy erraggy deleted the fix/converter-exclusive-min-max-oas31 branch March 31, 2026 02:46
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.

fix(converter): OAS 3.1 exclusiveMaximum/exclusiveMinimum should use numeric semantics

1 participant