feat(differ): implement Phase 1 recursive schema diffing#34
Conversation
Implemented comprehensive recursive schema diffing with cycle detection for Properties, Items, and AdditionalProperties fields following the schema-diffing RFC in planning/schema-diffing.md. Features: - Pointer-based cycle detection for circular $ref references - Type-aware comparison for any fields (Items, AdditionalProperties) - Breaking change severity following oasdiff conventions - Support for all OAS versions (2.0, 3.0.x, 3.1.x, 3.2.0) Changes: - Added differ/schema.go with cycle detection helpers - Modified differ/breaking.go with recursive breaking mode functions - Modified differ/simple.go with recursive simple mode functions - Updated CONTRIBUTORS.md and CLAUDE.md with benchmark requirements - Created planning/schema-diffing.md RFC document Quality: - All 35 existing tests pass - Linter clean (0 issues) - Benchmarks updated (benchmark-20251124-214518.txt) - No performance regressions detected 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #34 +/- ##
==========================================
+ Coverage 48.85% 49.75% +0.90%
==========================================
Files 30 31 +1
Lines 7365 8009 +644
==========================================
+ Hits 3598 3985 +387
- Misses 3083 3310 +227
- Partials 684 714 +30 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code Review: Phase 1 Recursive Schema DiffingThis PR implements Phase 1 of recursive schema diffing with cycle detection. Overall, this is a well-structured and thoughtful implementation that follows best practices. However, I've identified one critical bug that must be fixed before merging, along with several recommendations. 🚨 Critical Issues1. Incorrect Required Field Logic (MUST FIX)Location: Problem: The code is checking the wrong schema's // INCORRECT (lines 1732-1733):
if isPropertyRequired(name, sourceSchema.Required) {
severity = SeverityError
}
// INCORRECT (lines 1756-1757):
if isPropertyRequired(name, targetSchema.Required) {
severity = SeverityWarning
}Why this is wrong: In JSON Schema (and OpenAPI), the type: object
required: ["name", "email"] # Parent level
properties:
name: # Properties don't have their own 'required'
type: string
email:
type: stringImpact: This bug causes incorrect severity classification:
Fix: The function needs access to the parent schema's
The same issue exists in
|
Addressed all critical and high-priority issues from code review: Critical Fixes: - Fixed Required field bug: Now correctly uses parent schema's Required list when determining severity for property additions/removals - Fixed cycle detection: Now tracks (source, target) pairs instead of just source to prevent infinite loops on target-only cycles - Added unknown type handling: Gracefully skips identical unknown types and reports when types differ between source and target Test Coverage: - Added comprehensive test suite (differ/schema_test.go) with 13 tests - Tests cover circular references, required vs optional properties, Items type changes, AdditionalProperties breaking changes, nested properties, and unknown type handling - Coverage improved: differ package now at 66.7% (up from 47.10%) - All 962 tests pass Key Test Cases: - Circular reference detection (self-references and mutual references) - Required property severity (Error for removed required, Warning for removed optional) - Items field type transitions (Schema ↔ bool ↔ nil) - AdditionalProperties breaking changes (true→false is Error, false→true is Info) - Nested property diffing through multiple levels - Unknown type graceful handling (skip if both unknown, report if different) - Simple mode verification (no severity assigned) Quality: - All existing tests pass (962/962) - Linter clean (0 issues) - Benchmarks updated (no regressions) - schema.go: 83-100% coverage across all functions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Review ResponseThank you for the detailed code review! I've addressed all critical and high-priority issues. Here's a summary of the changes: ✅ Critical Issues Fixed1. Required Field Bug (FIXED)Problem: Checking child property's Required field instead of parent schema's Required list. Solution: Updated func (d *Differ) diffSchemaPropertiesBreaking(
source, target map[string]*parser.Schema,
sourceRequired, targetRequired []string, // NEW
path string,
visited *schemaVisited,
result *DiffResult,
)Now correctly determines severity based on whether property appears in parent's Required list. 2. Cycle Detection Bug (FIXED)Problem: Only tracking source schema could cause infinite loops if target has cycles. Solution: Implemented schema pair tracking: type schemaPair struct {
source *parser.Schema
target *parser.Schema
}
func (v *schemaVisited) enter(source, target *parser.Schema, path string) bool {
pair := schemaPair{source: source, target: target}
if _, exists := v.visited[pair]; exists {
return true
}
v.visited[pair] = path
return false
}This prevents infinite loops when either source OR target has circular references. 3. Unknown Type Handling (ADDED)Problem: Unknown types (like unresolved Solution: Skip comparison when both have same unknown type: // If both have unknown type, skip comparison (can't diff unknown structures)
if sourceType == schemaItemsTypeUnknown && targetType == schemaItemsTypeUnknown {
return
}Only report as warning when unknown types differ between source and target. ✅ Test Coverage AddedAdded comprehensive test suite (
📊 Coverage ImprovementBefore: 22.88% patch coverage (438 lines missing) Breakdown by file:
✅ Quality Checks
📝 Medium Priority IssuesIssue 5 (Missing Godoc/Examples): Removed from scope - these are internal functions not needing public examples. Issue 6 (Map Allocation Performance): Acknowledged as potential future optimization. Not addressed in this PR as impact is minimal for typical schemas. The PR is now ready for re-review! All critical and high-priority issues have been resolved with comprehensive test coverage demonstrating the fixes work correctly. |
Code Review: Phase 1 Recursive Schema DiffingThank you for this well-implemented PR! This is a high-quality implementation of recursive schema diffing with excellent attention to detail. Below is my comprehensive review. ✅ SummaryOverall Assessment: APPROVED with minor suggestions This PR successfully implements Phase 1 of recursive schema diffing with:
🎯 Strengths1. Excellent Cycle Detection DesignThe pointer-based type schemaPair struct {
source *parser.Schema
target *parser.Schema
}Why this works: Tracks both source AND target pointers, preventing infinite loops even when only one side has circular references. The 2. Comprehensive Test CoverageThe test suite covers all critical scenarios:
Test Quality: Tests use table-driven patterns where appropriate, have clear names, and validate both positive and negative cases. 3. Type-Safe Handling of
|
[skip-review] - automated code formatting, no logic changes
Added extensive test coverage for simple mode schema diffing functions, significantly improving coverage for simple.go. New Tests (3 test functions with 15 subtests): - TestDiffSchemaItemsSimpleMode: 7 test cases covering all Items transitions - TestDiffSchemaAdditionalPropertiesSimpleMode: 7 test cases for AdditionalProperties - TestDiffSchemaPropertiesSimpleMode: Complex multi-property test Coverage Improvements: - diffSchemaItems (simple.go): 30.0% → 86.7% (+56.7%) - diffSchemaAdditionalProperties (simple.go): 26.7% → 83.3% (+56.6%) - Overall package: 66.7% → 69.9% (+3.2%) Test Cases Cover: - All Items field transitions (nil, Schema, bool) - All AdditionalProperties transitions (nil, Schema, bool) - Property additions, removals, and type modifications - Verification that simple mode has no severity assigned Quality: - All 979 tests pass (up from 962) - Linter clean (0 issues) - make check passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Test Coverage UpdateAdded comprehensive simple mode tests to further improve coverage: New Tests Added:
Coverage Improvements:
Test Results:
The simple mode functions now have comprehensive coverage matching the breaking mode tests. |
Removed 6 completed planning documents: - differ-consolidation.md (completed in PR #38) - improve-testing.md (completed in PR #32) - security-audit-len-arithmetic.md (completed in PR #33) - schema-diffing.md (Phases 1-2 completed in PRs #34, #36) - releases-with-immutability.md (completed in PR #40) - release-issues.md (historical, lessons incorporated elsewhere) Updated review-feedback-implementation.md to reflect all 3 phases completed in PR #46, including Phase 3 JSON marshaling refactor (552 lines removed). Remaining planning docs are either active work, reference documentation, or intentionally on-hold pending feedback. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
#47) Removed 6 completed planning documents: - differ-consolidation.md (completed in PR #38) - improve-testing.md (completed in PR #32) - security-audit-len-arithmetic.md (completed in PR #33) - schema-diffing.md (Phases 1-2 completed in PRs #34, #36) - releases-with-immutability.md (completed in PR #40) - release-issues.md (historical, lessons incorporated elsewhere) Updated review-feedback-implementation.md to reflect all 3 phases completed in PR #46, including Phase 3 JSON marshaling refactor (552 lines removed). Remaining planning docs are either active work, reference documentation, or intentionally on-hold pending feedback. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
Summary
This PR implements Phase 1 of the schema diffing RFC, adding comprehensive recursive schema diffing with cycle detection for the
differpackage. This enables full comparison of nested schema structures including Properties, Items, and AdditionalProperties fields.What's New
Core Features
$refreferences using a visited set patternanytype fields (Items, AdditionalProperties) that can be*SchemaorboolNew Files
differ/schema.go- Cycle detection helpers and type assertion utilitiesplanning/schema-diffing.md- RFC-style planning document for complete schema diffing implementationModified Files
differ/breaking.go- Added recursive breaking mode schema diffing functions (+335 lines)differ/simple.go- Added recursive simple mode schema diffing functions (+477 lines)CONTRIBUTORS.md- Added benchmark update requirements to change processCLAUDE.md- Added benchmark requirements to submission checklistImplementation Details
Cycle Detection Strategy
Uses a pointer-based visited set to track schemas during recursive traversal:
The
enter/leavepattern with defer ensures automatic cleanup:Type-Aware Comparison
Handles fields that can be multiple types (e.g., Items can be
*Schemaorbool):Breaking Change Severity
Follows oasdiff conventions for constraint semantics:
true → false= Error (restricting),false → true= Info (relaxing)Changes by Component
differ/schema.go(NEW - 104 lines)schemaVisitedtype for cycle detectiongetSchemaItemsType()- Type detection for Items fieldgetSchemaAdditionalPropsType()- Type detection for AdditionalProperties fieldformatSchemaType()- Format schema Type field for displayisPropertyRequired()- Check if property is in required listdiffer/breaking.go(+335 lines)diffSchemaRecursiveBreaking()- Main recursive diffing with cycle detectiondiffSchemaPropertiesBreaking()- Compare Properties maps with severitydiffSchemaItemsBreaking()- Type-aware Items comparisondiffSchemaAdditionalPropertiesBreaking()- Type-aware AdditionalProperties comparisondiffSchemaBreaking()to use new recursive functionsdiffer/simple.go(+477 lines)diffSchemaRecursive()- Simple mode recursive diffingdiffSchemaProperties()- Simple Properties comparisondiffSchemaItems()- Simple Items comparisondiffSchemaAdditionalProperties()- Simple AdditionalProperties comparisonDocumentation Updates
CONTRIBUTORS.md- Added benchmark update to "Before You Commit" and "PR Review Checklist"CLAUDE.md- Added benchmark requirement to "Before Submitting" checklistplanning/schema-diffing.md- Phase 1 marked complete with implementation detailsTesting
Test Results
Linter Results
$ make lint golangci-lint run # Clean - 0 issuesBenchmarks
Updated baseline benchmarks with
make bench-save:benchmark-20251124-214518.txt- No performance regressions detectedfor b.Loop()patternRFC Planning Document
The
planning/schema-diffing.mdfile provides:parser.Schema(50+ fields)Quality Checklist
Related Issues
This is the first phase of implementing comprehensive schema diffing support. Future phases will cover:
Breaking Changes
None - this is purely additive functionality that extends existing diff capabilities.
🤖 Generated with Claude Code