Skip to content

Refactor OpenApiDocumentFactory into separate modules#1144

Merged
christianhelle merged 3 commits into
mainfrom
deepened-factory
Jun 14, 2026
Merged

Refactor OpenApiDocumentFactory into separate modules#1144
christianhelle merged 3 commits into
mainfrom
deepened-factory

Conversation

@christianhelle

@christianhelle christianhelle commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary

Split the 488-line OpenApiDocumentFactory into 4 independently testable modules, as specified in docs/prd/prd-deepen-openapi-document-factory.md.

Changes

New modules

  • IDocumentLoader / DocumentLoader — document loading from file or HTTP URL with YAML/JSON detection, OpenApiMultiFileReader for external references, and NSwag fallback
  • IDocumentMerger / DocumentMerger — multi-document merge logic (paths, schemas, definitions, security schemes, tags) with conflict detection via DocumentEquivalenceComparer
  • DocumentEquivalenceComparer — canonical JSON equivalence comparison extracted as a standalone, testable module

Modified

  • OpenApiDocumentFactory — deepened to thin composition of IDocumentLoader + IDocumentMerger. Public API unchanged.
  • OpenApiDocumentFactoryMergeTests — updated to use new modules directly instead of reflection

Summary by CodeRabbit

Summary

  • Refactor
    • Reworked OpenAPI handling by splitting document loading and multi-document merging into focused, reusable components to improve structure and maintainability.
  • New Features
    • Improved OpenAPI loading from local paths or HTTP(S) URLs, with automatic YAML/JSON detection and fallback behavior when multi-file references aren’t fully contained.
  • Bug Fixes
    • More reliable merge behavior when combining documents, including deterministic schema/payload equivalence checks and faster failure on conflicting duplicates.
  • Tests
    • Updated merge tests to use the new loading/merging flow directly.

…mentLoader modules from OpenApiDocumentFactory

DocumentEquivalenceComparer: canonical JSON equivalence comparison
for merge conflict detection. Extracted from OpenApiDocumentFactory
with public methods for testability.

IDocumentMerger + DocumentMerger: multi-document merge logic
(paths, schemas, definitions, security schemes, tags).
Depends on DocumentEquivalenceComparer for conflict detection.

IDocumentLoader + DocumentLoader: document loading from file
or HTTP URL with YAML/JSON detection, OpenApiMultiFileReader
for external references, and NSwag fallback.

OpenApiDocumentFactory: deepened to thin composition of
IDocumentLoader + IDocumentMerger. Public API unchanged.
Replace reflection-based InvokeMerge, InvokeAreEquivalent,
InvokeCreateCanonicalSchemaToken, InvokeCreateCanonicalJsonToken,
InvokeAddReferencedSchemas, InvokeGetDefinitionName,
InvokeCreateOpenApiJson, and InvokeRemoveNullProperties with
direct calls to DocumentMerger and DocumentEquivalenceComparer.

InvokeMergeIfMissingOrThrowOnConflict continues to use reflection
on DocumentMerger for targeted merge-conflict tests. All 2159 tests pass.
@christianhelle christianhelle added enhancement New feature, bug fix, or request .NET Pull requests that contain changes to .NET code labels Jun 14, 2026
@christianhelle christianhelle self-assigned this Jun 14, 2026
@christianhelle christianhelle added enhancement New feature, bug fix, or request .NET Pull requests that contain changes to .NET code labels Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd32945b-d255-4f9a-9f29-27e113666a7a

📥 Commits

Reviewing files that changed from the base of the PR and between 46ccfa9 and c93ec4b.

📒 Files selected for processing (3)
  • src/Refitter.Core/DocumentEquivalenceComparer.cs
  • src/Refitter.Core/DocumentLoader.cs
  • src/Refitter.Core/DocumentMerger.cs

📝 Walkthrough

Walkthrough

Extracts all OpenAPI document loading and merging logic out of OpenApiDocumentFactory into three new dedicated types: DocumentLoader (handles multi-file reading with NSwag fallback), DocumentEquivalenceComparer (canonical JSON/schema comparison), and DocumentMerger (multi-document merge with conflict detection). Two interfaces (IDocumentLoader, IDocumentMerger) back the implementations. OpenApiDocumentFactory becomes a thin façade delegating to these components, and tests are updated to call the new classes directly.

Changes

OpenAPI Document Loading and Merging Decomposition

Layer / File(s) Summary
IDocumentLoader and IDocumentMerger interfaces
src/Refitter.Core/IDocumentLoader.cs, src/Refitter.Core/IDocumentMerger.cs
Declares IDocumentLoader.LoadAsync(string) returning Task<OpenApiDocument> and IDocumentMerger.Merge(OpenApiDocument[]) returning OpenApiDocument.
DocumentEquivalenceComparer
src/Refitter.Core/DocumentEquivalenceComparer.cs
Implements canonical JSON tokenization and deep structural equality via JToken.DeepEquals; handles JsonSchema inputs with recursive canonical token generation (ref/visited cycle prevention, deterministic property ordering, null-property removal); includes OpenAPI document construction wrappers and schema traversal for referenced-schema discovery.
DocumentLoader
src/Refitter.Core/DocumentLoader.cs
Implements IDocumentLoader with a shared static HttpClient; LoadAsync uses OpenApiMultiFileReader.Read as primary path and falls back to NSwag-based YAML/JSON parsing on missing external refs or any exception; helper methods handle HTTP fetching, YAML detection, and missing Info field population.
DocumentMerger
src/Refitter.Core/DocumentMerger.cs
Implements IDocumentMerger by deep-cloning the first document and merging Paths, component schemas, Definitions, SecurityDefinitions, and Tags from subsequent documents; non-equivalent duplicate keys throw InvalidOperationException.
OpenApiDocumentFactory refactored as façade
src/Refitter.Core/OpenApiDocumentFactory.cs
Replaces ~448 lines of in-file implementation with private static DocumentLoader and DocumentMerger collaborators; both CreateAsync overloads delegate entirely to the new classes; XML exception docs distinguish ArgumentNullException from ArgumentException.
Test helpers updated
src/Refitter.Tests/OpenApi/OpenApiDocumentFactoryMergeTests.cs
Adds static Comparer and Merger instances; replaces reflection-based invocation of OpenApiDocumentFactory private statics with direct calls to DocumentEquivalenceComparer and DocumentMerger public methods.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • christianhelle/refitter#1076: Targets the same structural-equivalence problem for duplicate schemas during multi-spec merging that DocumentEquivalenceComparer now resolves via canonical JToken.DeepEquals.
  • christianhelle/refitter#1064: Directly overlaps — that PR hardened OpenApiDocumentFactory with HTTP/YAML/ConfigureAwait and merge-equivalence behavior that this PR extracts into DocumentLoader/DocumentMerger/DocumentEquivalenceComparer.
  • christianhelle/refitter#1079: Both PRs modify OpenAPI document merge/clone behavior in OpenApiDocumentFactory.cs; this PR removes the cloning logic that PR #1079 adjusted.

Poem

🐇 Hop, hop, refactor time!
The factory grew fat and wide,
So I split it, piece by piece,
A Loader, Merger, Comparer inside.
Each class now knows its task alone —
Clean carrots in their burrow, grown! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title accurately summarizes the main change: refactoring a large monolithic class into four separate, independently testable modules with clear responsibilities.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 deepened-factory

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 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 `@src/Refitter.Core/DocumentEquivalenceComparer.cs`:
- Around line 11-154: Add XML documentation comments to all public methods in
the DocumentEquivalenceComparer class. For each public method (AreEquivalent,
CreateCanonicalJsonToken, NormalizeJsonToken, CreateCanonicalSchemaToken,
RemoveNullProperties, CreateOpenApiJson, AddReferencedSchemas, and
GetDefinitionName), add a summary comment block that describes what the method
does, documents all parameters with param tags, and documents the return value
with a returns tag. This ensures compliance with the C# API documentation
requirement.
- Around line 76-79: The canonicalization process is order-sensitive for
unordered schema constructs like allOf, oneOf, anyOf, and enum, which means
semantically equivalent schemas with different declaration order can be treated
as conflicts. Sort these arrays before adding them to the JSON structure in the
AddSchemaArray method calls and wherever enum values are being processed to
ensure consistent, order-insensitive canonicalization regardless of the input
declaration order.

In `@src/Refitter.Core/DocumentLoader.cs`:
- Around line 28-30: The LoadAsync method accepts the openApiPath parameter
without validating it for null or blank values, allowing invalid input to
propagate to downstream helper methods and cause unclear runtime errors. Add
explicit input validation at the beginning of the LoadAsync method to check if
openApiPath is null, empty, or contains only whitespace, and throw an
ArgumentException with a descriptive message if validation fails. This will
catch the issue at the boundary and provide clear, actionable error messages
instead of failures in the helpers that consume this parameter.
- Around line 99-103: The IsYaml method performs suffix checks on the raw input
path without accounting for query strings or URL fragments. Paths like
`.../openapi.yaml?raw=1` fail the EndsWith check and are misclassified as JSON.
Modify the IsYaml method to first extract the base path by removing query string
parameters (everything from the '?' character onward) and URL fragments
(everything from the '#' character onward), then perform the EndsWith check for
"yaml" and "yml" extensions on this cleaned path portion.
- Around line 48-51: The catch block for Exception is too broad and captures
cancellation-related exceptions like OperationCanceledException and
TaskCanceledException, forcing a fallback attempt that contradicts cancellation
semantics. Modify the catch block to be more specific about which exceptions
should trigger the fallback to CreateUsingNSwagAsync. Either catch only the
specific exception types that warrant a fallback behavior, or add logic to
re-throw cancellation exceptions before attempting the CreateUsingNSwagAsync
fallback.

In `@src/Refitter.Core/DocumentMerger.cs`:
- Around line 12-17: Add XML documentation comments to the public members of the
DocumentMerger class that are currently undocumented. Specifically, add a
summary comment above the DocumentMerger constructor that explains its purpose
and the comparer parameter, and add a summary comment above the Merge method
that describes what it does, its parameter, and return value. Follow standard C#
XML documentation format with <summary>, <param>, and <returns> tags as
appropriate.
- Around line 17-20: The Merge method accesses documents[0] without validating
that the documents array is not null or empty, which will result in an unhelpful
IndexOutOfRangeException at runtime. Add a guard clause at the beginning of the
Merge method to check if the documents parameter is null or empty, and if so
throw an ArgumentException or similar with a clear error message explaining that
at least one document is required. This validation should occur before any
attempt to access documents[0] or call CloneDocument.
- Around line 30-35: Add null checks for the Components property before
accessing its Schemas collection in the DocumentMerger.cs file. Currently, both
document.Components and baseDocument.Components are accessed without null
validation, which can cause NullReferenceException when parsing Swagger 2.0
documents where the "components" section is missing. Wrap the existing
conditional block that checks if document.Components.Schemas.Count is greater
than zero with an additional null check for document.Components itself, and
similarly ensure baseDocument.Components is not null before accessing
baseDocument.Components.Schemas in the MergeIfMissingOrThrowOnConflict call.
This should mirror the same defensive null-checking pattern already applied to
Definitions and SecurityDefinitions handling in the same method.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3c633d0-5731-49bd-9c31-5e779bfc937c

📥 Commits

Reviewing files that changed from the base of the PR and between e819ceb and 46ccfa9.

📒 Files selected for processing (7)
  • src/Refitter.Core/DocumentEquivalenceComparer.cs
  • src/Refitter.Core/DocumentLoader.cs
  • src/Refitter.Core/DocumentMerger.cs
  • src/Refitter.Core/IDocumentLoader.cs
  • src/Refitter.Core/IDocumentMerger.cs
  • src/Refitter.Core/OpenApiDocumentFactory.cs
  • src/Refitter.Tests/OpenApi/OpenApiDocumentFactoryMergeTests.cs

Comment thread src/Refitter.Core/DocumentEquivalenceComparer.cs
Comment thread src/Refitter.Core/DocumentEquivalenceComparer.cs Outdated
Comment thread src/Refitter.Core/DocumentLoader.cs
Comment thread src/Refitter.Core/DocumentLoader.cs Outdated
Comment thread src/Refitter.Core/DocumentLoader.cs
Comment thread src/Refitter.Core/DocumentMerger.cs
Comment thread src/Refitter.Core/DocumentMerger.cs
Comment thread src/Refitter.Core/DocumentMerger.cs Outdated
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.52174% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.14%. Comparing base (25407ad) to head (c93ec4b).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
src/Refitter.Core/DocumentLoader.cs 89.79% 3 Missing and 2 partials ⚠️
src/Refitter.Core/DocumentMerger.cs 91.17% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1144      +/-   ##
==========================================
- Coverage   94.31%   94.14%   -0.18%     
==========================================
  Files          48       55       +7     
  Lines        2796     2868      +72     
==========================================
+ Hits         2637     2700      +63     
- Misses         53       58       +5     
- Partials      106      110       +4     
Flag Coverage Δ
unittests 94.14% <96.52%> (-0.18%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@christianhelle christianhelle changed the title refactor: deepen OpenApiDocumentFactory into separate modules Refactor OpenApiDocumentFactory into separate modules Jun 14, 2026
@christianhelle christianhelle enabled auto-merge June 14, 2026 23:06
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 3 file(s) based on 8 unresolved review comments.

Files modified:

  • src/Refitter.Core/DocumentEquivalenceComparer.cs
  • src/Refitter.Core/DocumentLoader.cs
  • src/Refitter.Core/DocumentMerger.cs

Commit: c93ec4bd6fc557fea88d18a38023f45bb939ee87

The changes have been pushed to the deepened-factory branch.

Time taken: 3m 4s

Fixed 3 file(s) based on 8 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@christianhelle christianhelle merged commit a626197 into main Jun 14, 2026
9 of 10 checks passed
@christianhelle christianhelle deleted the deepened-factory branch June 14, 2026 23:13
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
6.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

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

Labels

enhancement New feature, bug fix, or request .NET Pull requests that contain changes to .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant