feat(spec-merge): add alternative DEEP merge mode with by-method path merging and configurable conflict resolution strategy#24100
Conversation
…erwriting on path collision
…or reactive generators
… types for reactive generators" This reverts commit 8972528.
…lisions in open api merging test
…omponent and path+method conflicts
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
5 issues found across 17 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/config/MergedSpecBuilder.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/config/MergedSpecBuilder.java:356">
P1: Deep merge path+method conflict detection is unconditional: it treats any duplicate method as a conflict without comparing whether the incoming operation is identical to the existing one. In FAIL mode this throws RuntimeException even for identical operations, which is inconsistent with component merging (where identical duplicates are silently deduplicated via Objects.equals) and contradicts documented intent of only warning on "different definitions".</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return; | ||
| } | ||
| incoming.readOperationsMap().forEach((method, operation) -> { | ||
| if (existing.readOperationsMap() != null && existing.readOperationsMap().containsKey(method)) { |
There was a problem hiding this comment.
P1: Deep merge path+method conflict detection is unconditional: it treats any duplicate method as a conflict without comparing whether the incoming operation is identical to the existing one. In FAIL mode this throws RuntimeException even for identical operations, which is inconsistent with component merging (where identical duplicates are silently deduplicated via Objects.equals) and contradicts documented intent of only warning on "different definitions".
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/config/MergedSpecBuilder.java, line 356:
<comment>Deep merge path+method conflict detection is unconditional: it treats any duplicate method as a conflict without comparing whether the incoming operation is identical to the existing one. In FAIL mode this throws RuntimeException even for identical operations, which is inconsistent with component merging (where identical duplicates are silently deduplicated via Objects.equals) and contradicts documented intent of only warning on "different definitions".</comment>
<file context>
@@ -99,51 +186,238 @@ public String buildMergedSpec() {
+ return;
+ }
+ incoming.readOperationsMap().forEach((method, operation) -> {
+ if (existing.readOperationsMap() != null && existing.readOperationsMap().containsKey(method)) {
+ String message = String.format(Locale.ROOT,
+ "Path+method collision during spec merge: %s %s is defined in multiple specs with different definitions. Keeping the first definition.",
</file context>
There was a problem hiding this comment.
This is on purpose - schema reuse across multiple specs (e.g. sharing from some common-schemas.yaml file is expected and the merging should be able to handle that. Having POST operation defined for the same path in multiple specs is unexpected. But having POST operation for a path and PUT operation for the same path in different specs is expected and handled correctly. The documentation now tries to explain it clearly
…rameter loss caused by resolve:true in parseSpecFiles
… with full parity for mergedFileInfoName/Description/Version across all three plugins
There was a problem hiding this comment.
All reported issues were addressed across 22 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- MergedSpecBuilder: resolve external \ into components for self-contained DEEP output; normalize output dir before relativize; propagate root-level security onto operations; apply conflictStrategy to path+method conflicts; detect/rename duplicate operationIds; use \ as parameter identity when deduping path params. - CLI Generate: extract shared applyMergeOptions and validate --merge-conflict-strategy regardless of merge mode. - Gradle GenerateTask: skip root-directory merge when inputSpecFiles is set (explicit list precedence). - TestBase: correct toPropertyReference KDoc. - Add merger test specs and tests for the above. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 34 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
… & pathItems - MergedSpecBuilder: merge root tags/externalDocs/webhooks (first-wins) and components.pathItems (3.1) - CLI Generate: pass --auth into merge builders so DEEP external-\ resolution honors credentials - Gradle GenerateTask: track inputSpecFiles order as @input; run cleanupOutput before merge writes - Tests: yaml/json FAIL operationId variants + direct mergeSpecs metadata/pathItems test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
- Fail when a source omits the required openapi field while others declare one (previously null versions were filtered out and silently accepted) - Reject malformed versions (e.g. 3.x.3) instead of normalizing non-numeric segments to 0 - Add tests for both cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Tighten version validation so malformed values like '3.0', '3.0.3.1', or those with leading zeroes are rejected instead of being emitted as the merged document's openapi value. Extend the malformed-version test to cover these forms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Note: This change should be fully backwards compatible as it is entirely opt-in for now.
Problem
When merging multiple OpenAPI specs, the previous
MergedSpecBuilderoverwrote a pathwhenever two specs defined the same URL path. If one spec defined
GET /petsand anotherdefined
POST /pets, one of the operations was silently lost.What changed
(e.g.
GETfrom one file +POSTfrom another) instead of overwriting.parameters,servers, andx-vendorextensions are merged (first definition wins on conflict); server URLs are de-duplicated.
.yaml,.yml,.jsonare processed, in adeterministic (sorted) order; other files are ignored.
(with an output dir) across CLI, Gradle, and Maven, instead of only scanning a directory.
Why the new
mergeModeandmergeConflictStrategyTwo merge modes exist so behavior stays backward-compatible while still allowing a real merge:
mergeModeREF(default): the original behavior - produces a lightweight index spec where eachpath is a
$refback to its source file. Kept as default so existing setups don't change.DEEP: fully inlines all paths and components into one self-contained spec, with componentdeduplication and the by-method path merging described above.
mergeConflictStrategy(only applies inDEEPmode)When two specs define the same component name (schema, response, etc.) with different
definitions, we need a policy:
WARN(default): keep the first definition and log a warning.FAIL: abort the merge. Useful in CI to catch accidental conflicts early.Note: identical duplicate components are silently de-duplicated. Duplicate path+method
definitions always fail, since there is no valid reason for the same method on the same path
to be defined twice.
Where it's exposed
--merge-mode,--merge-conflict-strategy,--input-spec-files,--merged-file-output-dirmergeMode,mergeConflictStrategy,inputSpecFiles,mergedFileOutputDirmergeMode,mergeConflictStrategy,inputSpecFiles,mergedFileOutputDirInvalid values fail fast with a clear error. Docs and tests updated accordingly.
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
Summary by cubic
Adds a DEEP merge mode that merges paths by HTTP method, preserves path-level data, resolves external
$refs for a self-contained spec, and uses a configurable conflict strategy. Also supports ordered input spec lists and root metadata merging across CLI, Gradle, and Maven, while keeping REF as the original$ref-based mode.Bug Fixes
mergeConflictStrategyin DEEP; REF keeps last.$ref; dedupe servers and addhttp://localhost:8080if none.$refs intocomponentsin DEEP; CLI forwards--authfor authenticated refs.securityto operations unless overridden; warn-and-rename duplicateoperationIds.tags,externalDocs,webhooks(first-wins) andcomponents.pathItems(OAS 3.1)..yaml,.yml,.jsondeterministically; respectinputSpecFilesorder; keep JSON if the first valid spec is JSON.--merge-conflict-strategyregardless of mode; Gradle cleans output before writing; wheninputSpecFilesis set, skip directory scan; fail fast on empty.3.0,3.0.3.1, leading zeroes), and prevent mixing different major versions with clear errors.New Features
mergeMode(REF,DEEP) andmergeConflictStrategy(WARN,FAIL) in CLI (--merge-mode,--merge-conflict-strategy), Gradle, and Maven; invalid values fail fast.--input-spec-fileswith--merged-file-output-dir), Gradle (inputSpecFiles,mergedFileOutputDir), and Maven (inputSpecFiles,mergedFileOutputDir); order overrides directory scanning.mergedFileInfoName,mergedFileInfoDescription,mergedFileInfoVersion); Gradle also addsmergedFileName(defaults tomerged).Written for commit e0737b2. Summary will update on new commits.