Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ If you have any questions, tag [Honza Dvorsky](https://github.com/czechboy0) or
- <doc:SOAR-0012>
- <doc:SOAR-0013>
- <doc:SOAR-0014>
- <doc:SOAR-0015>
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# SOAR-0015: Namespace-Based Types File Splitting

Split generated types into deterministic, depth-two namespace files by default.

## Overview

- Proposal: SOAR-0015
- Author(s): [Nick Candello](https://github.com/nac5504)
- Status: **Ready for Implementation**
- Issue: [apple/swift-openapi-generator#929](https://github.com/apple/swift-openapi-generator/issues/929)
- Implementation:
- [apple/swift-openapi-generator#925](https://github.com/apple/swift-openapi-generator/pull/925)
- Affected components:
- generator
- Related links:
- Prior discussion: [apple/swift-openapi-generator#866](https://github.com/apple/swift-openapi-generator/pull/866)

### Introduction

This proposal splits generated `Types.swift` into multiple output files at the existing generated namespace boundaries to improve post-generator Swift build times.

### Motivation

Large generated `Types.swift` files can dominate downstream Swift compilation time for sizeable OpenAPI documents. Currently, adopters cannot ask the generator to produce smaller types files; working around the issue requires post-processing generated source, which is brittle and unsupported. In [apple/swift-openapi-generator#866](https://github.com/apple/swift-openapi-generator/pull/866), several ways of splitting generated declarations demonstrated compile-time improvements to differing degrees.

The benchmark results from [PR #925](https://github.com/apple/swift-openapi-generator/pull/925) show meaningful post-generator Swift build-time improvements for several large API documents. Stripe's API improved from 4m49s to 2m13s, GitHub REST improved from 9m12s to 5m54s, and Jira improved from 59.5s to 37.7s. The benchmark suite did not show a statistically significant generator-time slowdown.

Generated namespaces provide stable, understandable splitting boundaries without introducing arbitrary declaration counts or dependency-grouping rules. Initially, this proposal introduced a more trivial version of namespace splitting at the first namespace depth (i.e. `Types.swift`, `Types+Operations.swift`, `Types+Components.swift`), however enabling second-layer namespace splitting enables docs with heavy diversity within `Components` to see substantial benefits.

### Proposed solution

Namespace-based splitting becomes the default types output shape. It requires no YAML configuration or command-line flag.

A types generation run emits the following set of files:

- `Types.swift`
- `Types+Components.swift`
- `Types+Operations.swift`
- `Types+Components+Schemas.swift`
- `Types+Components+Parameters.swift`
- `Types+Components+RequestBodies.swift`
- `Types+Components+Responses.swift`
- `Types+Components+Headers.swift`

Declarations are assigned to files according to the generated namespace that owns them:

- Root declarations remain in `Types.swift`.
- The `Components` namespace container is emitted in `Types+Components.swift`.
- The `Operations` namespace is emitted in `Types+Operations.swift`.
- The supported second-level `Components` namespaces are each emitted in their corresponding `Types+Components+<Namespace>.swift` file.

#### Benchmark results

The benchmark suite in PR #925 compares current single-file output against namespace splitting across representative OpenAPI documents. Each row uses five attempts with outliers excluded for VM compute inconsistencies; build and generator time charts use whiskers for +/- 1 standard deviation.

The main benchmark result is that post-generator Swift build time improves across the board for documents of varying sizes, while generator time remains close to the single-file baseline. The generator-stage breakdown suggests that assigning declarations to output files is not a meaningful generator-time cost.

![Post-generator Swift build times](https://gist.githubusercontent.com/nac5504/9452377b295faeca1d1198dd6d8ac07d/raw/e4580d68735097dec960548db3db3ca3da7ea833/cli-initiated-compile-build-times.svg)

![Generator time](https://gist.githubusercontent.com/nac5504/9452377b295faeca1d1198dd6d8ac07d/raw/e40ef57a323e6e792b4964af647f3bfe63178e92/generator-time.svg)

![Generator stage breakdown](https://gist.githubusercontent.com/nac5504/9452377b295faeca1d1198dd6d8ac07d/raw/c2da7a2272d8c71a1f8305da3c0dc940f474a7f6/generator-stage-breakdown.svg)

### Detailed design

The generator's types-output path changes from producing one source file to producing a named collection of source files. The file layout mirrors the generated Swift namespace hierarchy. A declaration belongs to the file for its nearest supported namespace boundary, while declarations outside those boundaries remain in the primary types file.

Each generated file is a source-level fragment of the same generated API. Moving a declaration between these files does not rename it, change its access level, or introduce an additional Swift namespace.

Contributors adding a new types-output entry point must preserve the complete collection rather than selecting only the primary file. Direct CLI generation writes all returned files to the output directory. SwiftPM and Xcode build-tool plugins declare the complete deterministic file set and compile it as generated source.

#### Build-tool plugin support

SwiftPM and Xcode build-tool plugins must declare generated output files before invoking the generator executable. Because namespace splitting is unconditional and its depth-two file set is fixed, the plugins can declare those files without inspecting the generator configuration or using a prebuild command.

### API stability

This proposal does not change the runtime public API, runtime "Generated" SPI, or the APIs implemented by transports and middleware.

Existing and newly generated adopter code remains source compatible: symbol names, namespace nesting, and intended access are unchanged. Only the source-file layout changes, so integrations that assume a single `Types.swift` file must instead write, track, or compile the complete output set.

Existing configuration files and CLI arguments remain valid, but types generation now emits multiple files by default. The command and build-tool plugins automatically handle all generated files without additional configuration.

### Future directions

More advanced strategies could split especially large namespaces into count-based slices or dependency-aware layers if future benchmarks justify their additional complexity. Unlike the namespace layout in this proposal, those strategies may produce an output set that depends on the input document. Supporting them may therefore require SwiftPM and Xcode prebuild commands, or another mechanism for declaring dynamic outputs.

### Alternatives considered

#### Make namespace splitting opt-in

An opt-in configuration was considered, but it would require build-tool plugins to determine which output set to declare before generation and would leave most users on the slower single-file layout. A fixed default provides the benefit without adding a configuration knob.

#### Split only the top-level namespaces

Initially, this proposal introduced a more trivial verson of namespace splitting at the first namespace depth (i.e. `Types.swift`, `Types+Operations.swift`, `Types+Components.swift`), however enabling second-layer namespace splitting enables docs with heavy diversity within `Components` to see substantial benefits.

#### Add declaration-count or dependency-based sharding now

These strategies could create more parallelized compilation for very large namespaces, but their output sets depend on generated content and would add naming, configuration, stability, and build-tool-plugin questions. They remain possible follow-up work if benchmarks demonstrate sufficient benefit.
Loading