Skip to content

chore: remove duplication, dead code, and over-abstraction#300

Merged
erraggy merged 2 commits intomainfrom
chore/codebase-polish
Feb 11, 2026
Merged

chore: remove duplication, dead code, and over-abstraction#300
erraggy merged 2 commits intomainfrom
chore/codebase-polish

Conversation

@erraggy
Copy link
Owner

@erraggy erraggy commented Feb 11, 2026

Summary

  • Split monolithic helpers.go files in converter, generator, and validator into domain-specific files (ref_rewrite.go, schema_convert.go, server_url.go, naming.go, type_mapping.go, format_validation.go, path_validation.go)
  • Removed over-abstracted constraintTarget interface (110 lines of adapter boilerplate) in builder — inlined constraint application directly to schema/parameter fields
  • Consolidated duplicate utilities into shared internal packages: maputil.SortedKeys[V any], testutil.Ptr[T], pathutil.PathParamRegex, stringutil.IsValidEmail
  • Modernized all interface{}any across the codebase (code and comments)
  • Fixed review issues: silent default: returnpanic in fixer/refs.go, corrected 4 inaccurate comments, added file-level docs to all new files
  • Updated dependencies (go.mod/go.sum)

Net result: -190 lines, 76 files changed, all 7,585 tests passing.

Test plan

  • make check passes (lint, format, vet, all tests)
  • go_diagnostics reports no errors across all modified files
  • Codegen template and generated output are in sync (go run ./internal/codegen/deepcopy)
  • No remaining interface{} usages (grep returns zero matches)
  • PR review (code, errors, tests, comments) completed with all findings addressed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Removed dead code and consolidated test helpers; many tests updated to use a shared pointer utility.
  • Refactor
    • Simplified and flattened internal logic; centralized key-sorting and iteration for consistent outputs.
    • Cleaner, smaller modules for conversion and generation internals.
  • New Features
    • Stricter path-template validation and improved format validators (URLs, emails, media types) for clearer validation feedback.

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Large-scale refactor: extracted/added internal utilities (testutil, maputil, pathutil, stringutil), centralized test pointer helpers, removed/flattened adapter layers, split converter/generator helpers, added ref-rewrite and schema-conversion modules, simplified deep-copy paths, and updated many tests and callers to use new utilities. No public API changes reported.

Changes

Cohort / File(s) Summary
Codecov Config
\.codecov.yml
Added internal/testutil/* to coverage.ignore.
Builder
builder/builder.go, builder/constraints.go, builder/parameter.go
Switched interface{}any in one signature; removed constraintTarget/adapters and inlined constraint field assignments; use schemautil.GetPrimaryType.
Builder tests & reflect
builder/reflect.go, builder/reflect_test.go, builder/tags_test.go
Replaced local contains with slices.Contains; comments updated interface{}any; tests adjusted.
Converter split
converter/helpers.go (removed), converter/ref_rewrite.go (new), converter/schema_convert.go (new), converter/server_url.go (new), converter/converter.go
Extracted $ref rewriting, schema conversion, and server-URL parsing into new files; removed monolithic ref-rewrite/deep-copy helpers; simplified DeepCopy usage.
Fixer
fixer/helpers.go (removed), fixer/oas2.go, fixer/oas3.go, fixer/refs.go, fixer/path_parameters.go
Removed deep-copy wrappers in favor of DeepCopy(); unified param/header ref collection via collectParamOrHeaderRefs; moved path-param regex to internal/pathutil.
Generator split
generator/helpers.go (removed), generator/naming.go (new), generator/type_mapping.go (new), generator/template_builders.go
Moved naming and OpenAPI→Go type-mapping helpers into new files; removed many monolithic helper functions; minor any usage update.
Sorted-keys utility
internal/maputil/keys.go (new), callers: walker/*, generator/oas*.go, joiner/*, walker/walk_shared.go
Added generic SortedKeys[V any] and replaced local sortedMapKeys/sortedPathKeys across codebase for deterministic iteration.
Path utilities & validation
internal/pathutil/path.go (new), internal/pathutil/path_test.go, validator/path_validation.go (new), fixer/path_parameters.go
Added PathParamRegex, moved path-template parsing/validation into validator, updated callers to use pathutil.
String/format utilities
internal/stringutil/validate.go (new), internal/stringutil/validate_test.go, validator/format_validation.go (new), httpvalidator/schema.go
Added IsValidEmail and format helpers; replaced local email regex usage with stringutil.
Test util centralization
internal/testutil/ptr.go (new) and many tests (builder/*_test.go, differ/*, parser/*_test.go, httpvalidator/*_test.go, internal/*_test.go)
Added generic testutil.Ptr[T] and replaced ~20+ local pointer helper functions in tests.
Standard library adoption
integration/harness/*, parser/oas*_json_test.go, others
Replaced custom contains helpers with slices.Contains / strings.Contains where appropriate and removed local helpers.
JSONPath change
internal/jsonpath/jsonpath.go
Renamed segment marker method segmentType() stringsegment() across implementations.
Tests pruned / helpers removed
converter/helpers_test.go, joiner/helpers_test.go, parser/schema_test_helpers_test.go
Removed tests and local test helper file(s) related to removed utilities.
Design doc
docs/plans/2026-02-10-codebase-polish-design.md
Added design document describing multi-wave refactor plan and validation approach.
Dependencies
go.mod
Bumped indirect x/text, x/tools, x/mod versions.
Misc edits
Various tests/comments/generated files
Many comment/text updates swapping interface{}any, small wording tweaks, and removal of now-unused helpers.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: removing duplication, dead code, and over-abstraction across the codebase through file splits, constraint flattening, utility consolidation, and modernization.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/codebase-polish

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

@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 77.95031% with 142 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.58%. Comparing base (80b56e0) to head (605c103).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
converter/ref_rewrite.go 56.70% 53 Missing and 31 partials ⚠️
validator/format_validation.go 52.94% 9 Missing and 7 partials ⚠️
generator/type_mapping.go 88.88% 9 Missing and 3 partials ⚠️
converter/schema_convert.go 53.33% 4 Missing and 3 partials ⚠️
internal/jsonpath/jsonpath.go 0.00% 6 Missing ⚠️
fixer/refs.go 77.27% 2 Missing and 3 partials ⚠️
generator/naming.go 93.90% 3 Missing and 2 partials ⚠️
validator/path_validation.go 91.83% 2 Missing and 2 partials ⚠️
converter/server_url.go 83.33% 1 Missing and 1 partial ⚠️
builder/reflect.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #300      +/-   ##
==========================================
- Coverage   84.62%   84.58%   -0.04%     
==========================================
  Files         158      166       +8     
  Lines       25125    25053      -72     
==========================================
- Hits        21261    21192      -69     
+ Misses       2618     2617       -1     
+ Partials     1246     1244       -2     
Files with missing lines Coverage Δ
builder/builder.go 89.56% <100.00%> (ø)
builder/constraints.go 100.00% <ø> (ø)
builder/parameter.go 100.00% <100.00%> (ø)
converter/converter.go 85.90% <100.00%> (+0.55%) ⬆️
converter/helpers.go 80.99% <100.00%> (+14.70%) ⬆️
fixer/oas2.go 70.00% <100.00%> (+1.18%) ⬆️
fixer/oas3.go 42.27% <100.00%> (+0.21%) ⬆️
fixer/path_parameters.go 93.75% <100.00%> (ø)
fixer/prune.go 75.44% <ø> (ø)
generator/helpers.go 94.73% <ø> (+2.68%) ⬆️
... and 25 more

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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: 10

Caution

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

⚠️ Outside diff range comments (3)
builder/parameter.go (1)

607-624: ⚠️ Potential issue | 🟠 Major

Replace type assertions with schemautil.GetPrimaryType() to handle OAS 3.1 array types in applyTypeFormatOverridesToOAS2Param.

When schema.Type is []string (e.g., ["string", "null"]) in OAS 3.1, the current type assertion silently fails, leaving param.Type empty and producing invalid OAS 2.0 output. Extract the primary (non-null) type using schemautil.GetPrimaryType() instead, which already handles both string and array representations.

Suggested fix
 import (
 	"errors"
 	"fmt"
 	"regexp"
 
 	"github.com/erraggy/oastools/internal/pathutil"
+	"github.com/erraggy/oastools/internal/schemautil"
 	"github.com/erraggy/oastools/oaserrors"
 	"github.com/erraggy/oastools/parser"
 )
@@
 	// Start with inferred type/format from schema
 	if schema != nil {
-		if typeStr, ok := schema.Type.(string); ok {
-			param.Type = typeStr
-		}
+		if typeStr := schemautil.GetPrimaryType(schema); typeStr != "" {
+			param.Type = typeStr
+		}
 		param.Format = schema.Format
 	}
@@
 	if cfg.schemaOverride != nil {
-		// Schema.Type is any to support OAS 3.1 array types
-		// For OAS 2.0, we only support string types
-		if typeStr, ok := cfg.schemaOverride.Type.(string); ok {
-			param.Type = typeStr
-		}
+		// Schema.Type is any to support OAS 3.1 array types
+		// For OAS 2.0, we only support string types
+		if typeStr := schemautil.GetPrimaryType(cfg.schemaOverride); typeStr != "" {
+			param.Type = typeStr
+		}
 		param.Format = cfg.schemaOverride.Format
 	} else {
walker/walk_oas2.go (1)

81-86: 🧹 Nitpick | 🔵 Trivial

Consider using maputil.SortedKeys for other map iterations for consistency.

The file still uses inline sort.Strings patterns for iterating doc.Definitions, doc.Parameters, doc.Responses, and doc.SecurityDefinitions (Lines 81-163), while walkOAS2Paths now uses maputil.SortedKeys. For consistency with the PR's consolidation goal, these could also be migrated. For example:

-defKeys := make([]string, 0, len(doc.Definitions))
-for k := range doc.Definitions {
-    defKeys = append(defKeys, k)
-}
-sort.Strings(defKeys)
+defKeys := maputil.SortedKeys(doc.Definitions)

This is optional since the current code is correct, but would reduce boilerplate and align with the PR's objectives.

converter/helpers.go (1)

195-223: 🛠️ Refactor suggestion | 🟠 Major

Use httputil.Method constants for standardHTTPMethods.*

This keeps method names consistent with the rest of the codebase and aligns with the project’s HTTP method constants.

♻️ Suggested fix
 import (
 	"fmt"
 
+	"github.com/erraggy/oastools/internal/httputil"
 	"github.com/erraggy/oastools/internal/schemautil"
 	"github.com/erraggy/oastools/parser"
 )
@@
 var standardHTTPMethods = []httpMethod{
-	{"get", func(p *parser.PathItem) *parser.Operation { return p.Get }, func(p *parser.PathItem, op *parser.Operation) { p.Get = op }},
-	{"put", func(p *parser.PathItem) *parser.Operation { return p.Put }, func(p *parser.PathItem, op *parser.Operation) { p.Put = op }},
-	{"post", func(p *parser.PathItem) *parser.Operation { return p.Post }, func(p *parser.PathItem, op *parser.Operation) { p.Post = op }},
-	{"delete", func(p *parser.PathItem) *parser.Operation { return p.Delete }, func(p *parser.PathItem, op *parser.Operation) { p.Delete = op }},
-	{"options", func(p *parser.PathItem) *parser.Operation { return p.Options }, func(p *parser.PathItem, op *parser.Operation) { p.Options = op }},
-	{"head", func(p *parser.PathItem) *parser.Operation { return p.Head }, func(p *parser.PathItem, op *parser.Operation) { p.Head = op }},
-	{"patch", func(p *parser.PathItem) *parser.Operation { return p.Patch }, func(p *parser.PathItem, op *parser.Operation) { p.Patch = op }},
+	{httputil.MethodGet, func(p *parser.PathItem) *parser.Operation { return p.Get }, func(p *parser.PathItem, op *parser.Operation) { p.Get = op }},
+	{httputil.MethodPut, func(p *parser.PathItem) *parser.Operation { return p.Put }, func(p *parser.PathItem, op *parser.Operation) { p.Put = op }},
+	{httputil.MethodPost, func(p *parser.PathItem) *parser.Operation { return p.Post }, func(p *parser.PathItem, op *parser.Operation) { p.Post = op }},
+	{httputil.MethodDelete, func(p *parser.PathItem) *parser.Operation { return p.Delete }, func(p *parser.PathItem, op *parser.Operation) { p.Delete = op }},
+	{httputil.MethodOptions, func(p *parser.PathItem) *parser.Operation { return p.Options }, func(p *parser.PathItem, op *parser.Operation) { p.Options = op }},
+	{httputil.MethodHead, func(p *parser.PathItem) *parser.Operation { return p.Head }, func(p *parser.PathItem, op *parser.Operation) { p.Head = op }},
+	{httputil.MethodPatch, func(p *parser.PathItem) *parser.Operation { return p.Patch }, func(p *parser.PathItem, op *parser.Operation) { p.Patch = op }},
 }

As per coding guidelines use package constants instead of string literals for HTTP Methods (httputil.MethodGet, httputil.MethodPost, etc.).

🤖 Fix all issues with AI agents
In `@converter/server_url.go`:
- Around line 6-24: The local regex variable pathParamRegx duplicates the shared
PathParamRegex; remove the local var and import and use
internal/pathutil.PathParamRegex inside parseServerURL (replace
pathParamRegx.ReplaceAllString(...) with
pathutil.PathParamRegex.ReplaceAllString(...)); add the internal/pathutil import
to the import block and update any references from pathParamRegx to
pathutil.PathParamRegex so the shared regex is reused.

In `@generator/type_mapping.go`:
- Around line 141-169: The schemaTypeFromMap function currently ignores string
formats; update it to check m["format"] when type == "string" and, if present
and a string, call stringFormatToGoType(format) to map formats like
"date-time"/"byte"/"binary" to Go types (falling back to "string" if no format
or unmapped), mirroring how integerFormatToGoType/numberFormatToGoType are used
for "integer" and "number".
- Around line 130-139: The zeroValue function currently returns invalid literals
like "string{}" for primitive scalars; update zeroValue(t string) to use a
switch on t: return `""` for "string"; `"false"` for "bool"; `"0"` for numeric
types
("int","int8","int16","int32","int64","uint","uint8","uint16","uint32","uint64","float32","float64");
`"nil"` for "any" and "interface{}"; keep existing nil behavior for
pointers/slices/maps (strings.HasPrefix checks) and retain t + "{}" only for
struct types; then update the unit test in helpers_test.go to expect the
corrected zero value for "string" (i.e., `""`) instead of `"string{}"`.

In `@go.mod`:
- Around line 10-11: The go.mod changes include unrelated dependency bumps
(golang.org/x/text v0.34.0 and golang.org/x/tools v0.42.0) that should be
separated from the refactor; revert or remove those version changes from this
branch/commit so the PR only contains the cleanup/refactor, then create a
separate commit or PR that updates golang.org/x/text and golang.org/x/tools (and
runs `go mod tidy`/tests) so dependency updates are tracked and reviewed
independently.

In `@internal/maputil/keys_test.go`:
- Around line 1-67: Replace manual checks in TestSortedKeys,
TestSortedKeys_StringValues, and TestSortedKeys_PointerValues with
stretchr/testify assertions: import "github.com/stretchr/testify/require" (or
"assert") and use require.Equal/require.ElementsMatch where appropriate to
compare expected vs actual; call require.NoError where needed and use
require.Equal(t, tt.expected, got) (or require.ElementsMatch for unordered
comparisons) for the SortedKeys results to remove t.Errorf and slices.Equal
usage while keeping the same test cases that call SortedKeys.

In `@internal/pathutil/path_test.go`:
- Around line 3-51: The test TestPathParamRegex uses raw testing.T checks;
replace them with stretchr/testify assertions to match repo style: import
"github.com/stretchr/testify/require" (and assert if needed), use
require.Equal/require.Len for comparing lengths and
require.Greater/require.NotNil for capture existence, and require.Equal for
comparing captured group values when iterating matches; update references to
PathParamRegex and TestPathParamRegex accordingly so failures use require
(fail-fast) semantics consistent with other tests.

In `@internal/stringutil/validate_test.go`:
- Around line 5-34: Add the suggested edge-case table-driven tests to
TestIsValidEmail in validate_test.go to strengthen IsValidEmail coverage:
include cases for multiple @ signs (e.g., "user@@example.com"), consecutive dots
in the local part (e.g., "user..name@example.com"), and leading/trailing dots in
the local part (e.g., ".user@example.com" and "user.@example.com") with expected
false outcomes; keep the same test structure (tests slice and t.Run loop) so
IsValidEmail is exercised consistently.

In `@internal/stringutil/validate.go`:
- Around line 1-10: The current emailRegex used by emailRegex and IsValidEmail
permits leading/trailing dots and consecutive dots in the local part and may
allow domain labels with leading/trailing hyphens; update the validation to be
stricter by replacing the pattern in the emailRegex variable with a regex that
disallows leading/trailing dots and consecutive dots in the local part and
disallows domain labels that start or end with a hyphen, or alternately add
post-regex checks inside IsValidEmail that split the address into local and
domain parts and reject if local starts/ends with '.' or contains '..' or if any
domain label starts/ends with '-'. Ensure emailRegex remains a compiled regexp
and keep IsValidEmail using it (or performing those additional checks) so
callers get the stricter validation.

In `@internal/testutil/ptr.go`:
- Around line 1-4: Add a new test file ptr_test.go that exercises the exported
generic helper Ptr by verifying it returns non-nil pointers whose dereferenced
value equals the original for a variety of types: primitives (int, float64,
bool), string, struct value, slice and map, and a pointer type (including
passing a nil pointer typed value). Include tests for zero values (e.g., 0, "",
empty struct) and ensure calling Ptr twice with the same literal yields distinct
addresses but equal values; also include one test that uses an explicit type
parameter and one that relies on type inference; reference the Ptr function in
each test and assert non-nil pointer and equality of *Ptr(v) and v.

In `@validator/format_validation.go`:
- Around line 48-69: The isValidURL function accepts http/https without
verifying host or ensuring url.Parse did not produce an opaque value; update
isValidURL to require that for u.Scheme == "http" or "https" the parsed URL has
a non-empty u.Host and u.Opaque is empty, and still allow relative paths that
start with "/" when u.Scheme == "" (keep the existing strings.HasPrefix(s, "/")
check); adjust logic in isValidURL accordingly so malformed inputs like
"http:///path", "http:", or "http:example.com" are rejected.

…nes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@erraggy erraggy force-pushed the chore/codebase-polish branch from 22dd7d2 to 3af6969 Compare February 11, 2026 05:35
@erraggy
Copy link
Owner Author

erraggy commented Feb 11, 2026

Addressed the outside-diff-range comments:

  1. builder/parameter.go (Major) — Fixed. Replaced schema.Type.(string) type assertions with schemautil.GetPrimaryType() which handles both string and []string (OAS 3.1 type arrays). This prevents silent failure when extracting types for OAS 2.0 parameter fields.

  2. walker/walk_oas2.go (Trivial) — Fixed. Replaced all 4 inline sort.Strings patterns with maputil.SortedKeys() for consistency with the PR's consolidation goal.

  3. converter/helpers.go (Major) — Fixed. Replaced string literals with httputil.Method* constants per project conventions.

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

Caution

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

⚠️ Outside diff range comments (1)
walker/walk_oas2.go (1)

370-375: 🧹 Nitpick | 🔵 Trivial

Inconsistent: responses.Codes still uses manual key collection.

This block uses inline key collection and sort.Strings while other map iterations in this file have been migrated to maputil.SortedKeys. Consider migrating for consistency with the PR's consolidation goal.

♻️ Suggested refactor
 	// Status code responses - using Codes
 	if responses.Codes != nil {
-		codeKeys := make([]string, 0, len(responses.Codes))
-		for k := range responses.Codes {
-			codeKeys = append(codeKeys, k)
-		}
-		sort.Strings(codeKeys)
-
-		for _, code := range codeKeys {
+		for _, code := range maputil.SortedKeys(responses.Codes) {
 			if w.stopped {
 				return nil
 			}

If migrated, the sort import could be removed from this file.

🤖 Fix all issues with AI agents
In `@converter/ref_rewrite.go`:
- Around line 73-158: walkSchemaRefs currently skips refs in
parser.Schema.Discriminator.Mapping — add logic in walkSchemaRefs to, after
handling schema.Ref, check schema.Discriminator != nil and iterate over
schema.Discriminator.Mapping values, rewriting each mapping ref via the provided
refRewriter (e.g., schema.Discriminator.Mapping[key] =
rewrite(schema.Discriminator.Mapping[key])) and also traverse any target schemas
if the mapping stores *parser.Schema; ensure nil checks for Discriminator and
Mapping and use the existing refRewriter signature so discriminator mapping $ref
strings are converted consistently.

In `@converter/schema_convert.go`:
- Around line 24-43: In convertOAS3SchemaToOAS2, extend the OAS3 feature
detection beyond Nullable: inspect schema.WriteOnly, schema.Deprecated, presence
of conditional keywords (schema.If, schema.Then, schema.Else), and JSON‑Schema
2020+ constructs (schema.PrefixItems, schema.Contains, schema.PropertyNames);
for each detected unsupported/ potentially lossy feature call
c.addIssueWithContext(result, path, "<feature> is an OAS 3.x / JSON‑Schema 2020+
feature", "Consider alternative or x‑extension for OAS 2.0 compatibility") so
every lossy conversion is recorded—reuse the existing convertOAS3SchemaToOAS2
function and ConversionResult/addIssueWithContext helper and keep
rewriteSchemaRefsOAS3ToOAS2 unchanged.

In `@generator/naming.go`:
- Around line 126-135: cleanDescription currently truncates by byte index which
can split multi-byte UTF-8 characters; update it to be rune-aware by converting
the string to a []rune, check the rune length against maxDescriptionLength, and
slice the rune slice (e.g. keep maxDescriptionLength-3 runes before appending
"...") so truncation never breaks UTF-8; keep the replacements and trimming
as-is and return the reconstructed string.

In `@internal/stringutil/validate_test.go`:
- Around line 3-33: Replace raw testing checks in TestIsValidEmail with testify
assertions: import github.com/stretchr/testify/require (or assert), and inside
TestIsValidEmail use require.Equal/require.True/require.False (or assert
equivalents) to compare got := IsValidEmail(tt.input) to tt.want; update the
subtest body that currently calls t.Errorf to use the testify assertion
functions and keep TestIsValidEmail, IsValidEmail, and the test table unchanged
so behavior and names remain identifiable.

In `@walker/walk_oas3.go`:
- Around line 7-9: Several functions in walk_oas3.go still use manual sorting
via sort.Strings; update them to use maputil.SortedKeys for consistency and
deterministic ordering. Specifically, in functions walkOAS3Webhooks,
walkOAS3PathItemOperations, walkOAS3Operation, walkOAS3Responses,
walkOAS3Response, and walkOAS3Callback, replace the pattern of extracting keys
into a slice then calling sort.Strings with a direct call to
maputil.SortedKeys(map) (or equivalent) and iterate the returned []string;
ensure you import/use maputil.SortedKeys the same way as the component walker
does and remove any now-unnecessary sort.Strings usages and temporary slices.

- Rewrite discriminator mapping $refs during OAS version conversion
- Use rune-aware truncation in cleanDescription to avoid splitting UTF-8
- Migrate validate_test.go to testify assertions
- Complete maputil.SortedKeys migration in walker (7 remaining sites)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 2

🤖 Fix all issues with AI agents
In `@converter/ref_rewrite.go`:
- Around line 177-210: Both rewriteParameterRefsOAS2ToOAS3 and
rewriteParameterRefsOAS3ToOAS2 currently skip rewriting refs on param.Items (and
nested Items), so add logic after the schema rewrite to traverse param.Items
recursively and rewrite each Items.Ref using rewriteRefOAS2ToOAS3 (in
rewriteParameterRefsOAS2ToOAS3) and rewriteRefOAS3ToOAS2 (in
rewriteParameterRefsOAS3ToOAS2); to avoid duplication, extract this traversal
into helper functions like rewriteItemsRefsOAS2ToOAS3 and
rewriteItemsRefsOAS3ToOAS2 and call them from the two parameter functions,
ensuring you handle nil checks and nested Items recursion.

Comment on lines +177 to +210
// rewriteParameterRefsOAS2ToOAS3 rewrites $ref values in a parameter from OAS 2.0 to OAS 3.x format
func rewriteParameterRefsOAS2ToOAS3(param *parser.Parameter) {
if param == nil {
return
}

if param.Ref != "" {
param.Ref = rewriteRefOAS2ToOAS3(param.Ref)
}

// Rewrite refs in the schema
rewriteSchemaRefsOAS2ToOAS3(param.Schema)
}

// rewriteParameterRefsOAS3ToOAS2 rewrites $ref values in a parameter from OAS 3.x to OAS 2.0 format
func rewriteParameterRefsOAS3ToOAS2(param *parser.Parameter) {
if param == nil {
return
}

if param.Ref != "" {
param.Ref = rewriteRefOAS3ToOAS2(param.Ref)
}

// Rewrite refs in the schema
rewriteSchemaRefsOAS3ToOAS2(param.Schema)

// Rewrite refs in content media types (OAS 3.x)
for _, mediaType := range param.Content {
if mediaType != nil {
rewriteSchemaRefsOAS3ToOAS2(mediaType.Schema)
}
}
}
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:

#!/bin/bash
# Find Items type definition
echo "=== Finding Items type definition ==="
rg "type Items struct" -A 25 --type go

echo ""
echo "=== Checking if Items has Ref field ==="
rg "type Items struct" -A 30 --type go | grep -i "Ref"

echo ""
echo "=== Looking for Items field in Parameter struct ==="
rg "type Parameter struct" -A 40 --type go

echo ""
echo "=== Checking parameter Items usage in ref_rewrite.go ==="
rg "param\.Items" converter/ref_rewrite.go -C 3

Repository: erraggy/oastools

Length of output: 6896


Handle param.Items refs in parameter rewriting functions.

The Items type in OAS 2.0 contains a Ref field and is recursive (can have nested Items). Both rewriteParameterRefsOAS2ToOAS3 and rewriteParameterRefsOAS3ToOAS2 should rewrite refs in param.Items and its nested items. Currently, these refs are not being handled.

Add the following after handling param.Schema in both functions:

// Rewrite refs in items (OAS 2.0)
if param.Items != nil {
	if param.Items.Ref != "" {
		param.Items.Ref = rewriteRefOAS2ToOAS3(param.Items.Ref) // or OAS3ToOAS2 in the other function
	}
	// Handle nested items recursively
	items := param.Items
	for items.Items != nil {
		items = items.Items
		if items.Ref != "" {
			items.Ref = rewriteRefOAS2ToOAS3(items.Ref) // or OAS3ToOAS2 in the other function
		}
	}
}

Consider extracting item ref rewriting into a helper function (e.g., rewriteItemsRefsOAS2ToOAS3) to avoid duplication.

🤖 Prompt for AI Agents
In `@converter/ref_rewrite.go` around lines 177 - 210, Both
rewriteParameterRefsOAS2ToOAS3 and rewriteParameterRefsOAS3ToOAS2 currently skip
rewriting refs on param.Items (and nested Items), so add logic after the schema
rewrite to traverse param.Items recursively and rewrite each Items.Ref using
rewriteRefOAS2ToOAS3 (in rewriteParameterRefsOAS2ToOAS3) and
rewriteRefOAS3ToOAS2 (in rewriteParameterRefsOAS3ToOAS2); to avoid duplication,
extract this traversal into helper functions like rewriteItemsRefsOAS2ToOAS3 and
rewriteItemsRefsOAS3ToOAS2 and call them from the two parameter functions,
ensuring you handle nil checks and nested Items recursion.

@erraggy erraggy merged commit a6e6bc1 into main Feb 11, 2026
31 checks passed
@erraggy erraggy deleted the chore/codebase-polish branch February 11, 2026 06:07
github-actions bot added a commit that referenced this pull request Feb 11, 2026
Incorporate the file extraction refactoring from PR #300 into this
feature branch to resolve merge conflicts. Moves extracted code to
match main's new file layout while preserving our OAS 3.x feature
detection additions.

- Extract ref rewriting functions to converter/ref_rewrite.go
- Extract schema conversion to converter/schema_convert.go
- Extract server URL parsing to converter/server_url.go
- Add internal/pathutil/path.go with PathParamRegex
- Replace deepCopyOAS3Document() with direct doc.DeepCopy()
- Use httputil constants for HTTP method names
- Remove tests for code that was extracted by #300

Co-authored-by: Robbie Coleman <erraggy@users.noreply.github.com>
erraggy added a commit that referenced this pull request Feb 11, 2026
…to OAS 2.0 (#302)

* feat(converter): detect additional OAS 3.x features during downgrade to OAS 2.0

Add detection for 6 additional OAS 3.x schema keywords that lack OAS 2.0
equivalents: writeOnly, deprecated (on schemas), if/then/else, prefixItems,
contains, and propertyNames. Each emits a warning via addIssueWithContext
so users are aware of potential data loss during conversion.

Closes #301

Co-authored-by: Robbie Coleman <erraggy@users.noreply.github.com>

* refactor(converter): align with main branch structure from #300

Incorporate the file extraction refactoring from PR #300 into this
feature branch to resolve merge conflicts. Moves extracted code to
match main's new file layout while preserving our OAS 3.x feature
detection additions.

- Extract ref rewriting functions to converter/ref_rewrite.go
- Extract schema conversion to converter/schema_convert.go
- Extract server URL parsing to converter/server_url.go
- Add internal/pathutil/path.go with PathParamRegex
- Replace deepCopyOAS3Document() with direct doc.DeepCopy()
- Use httputil constants for HTTP method names
- Remove tests for code that was extracted by #300

Co-authored-by: Robbie Coleman <erraggy@users.noreply.github.com>

* feat(converter): add recursive OAS 3.x feature detection in nested schemas

Previously, convertOAS3SchemaToOAS2 only checked top-level schema fields
for OAS 3.x incompatible features (writeOnly, deprecated, if/then/else,
prefixItems, contains, propertyNames, nullable). Nested schemas inside
Properties, Items, AllOf, AnyOf, OneOf, Not, AdditionalProperties, and
other structural locations were silently skipped.

This adds walkSchemaFeatures — a recursive traversal mirroring the
existing walkSchemaRefs pattern — that detects OAS 3.x features across
all nested schema locations with descriptive paths and cycle detection.

Addresses review feedback from coderabbitai on #302.

Co-authored-by: Robbie Coleman <erraggy@users.noreply.github.com>

* ci: re-trigger workflow checks for PR #302

The previous commit (2d8b153) was pushed via the GitHub Git Data API,
which did not trigger pull_request workflow events. This empty commit
forces a standard push event to run all required CI checks.

Co-authored-by: Robbie Coleman <erraggy@users.noreply.github.com>

* chore: update dependencies (golang.org/x/{mod,text,tools}, go toolchain)

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Robbie Coleman <erraggy@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.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