Skip to content

Proposal: Export resolved context for extension tools (no internal API exposure) #863

@novr

Description

@novr

Motivation

We propose a CLI-based contract so that third-party tools (e.g. Build Tool Plugins) can generate additional Swift files alongside the built-in Types, Client, and Server, using the same resolved OpenAPI document and config—without the generator exposing or stabilizing any internal APIs (e.g. _OpenAPIGeneratorCore).

The real problem is behavioral consistency. The official generator applies Filter (e.g. only certain endpoints), visibility (public/internal), naming strategy, and other config. Manually re-implementing config resolution and re-parsing the spec in a third-party tool is error-prone and will drift when the generator's filter or naming rules change. Staying in sync with the generator's resolved output is what extension tools need—not access to internal data structures.

Use case: Kawarimi is a SwiftPM Build Tool Plugin that today: (1) Invokes the official generator (via _OpenAPIGeneratorCore) to produce Types, Client, and Server; (2) Loads the same OpenAPI spec and config again and re-resolves filter, visibility, etc.; (3) Generates extra files: a ClientTransport mock and an APIProtocol default implementation. Any mismatch in filter or visibility between (1) and (2) leads to inconsistent generated code. Current workaround: re-parsing and re-resolving in a separate process, which duplicates logic and can drift from the generator's behavior.

Proposed solution

Possible directions (overview):

  • Option A – Callback / delegate: The generator accepts an optional callback that receives the resolved document and config and can write extra files. Extension logic runs in the same process as the generator.
  • Option B – CLI-based contract: The generator optionally exports its "Resolved Context" (post-filtered document + resolved config) to a file. A separate process (e.g. another build tool) reads that file and generates additional Swift. No internal API exposure; only a documented file format.
  • Option C – Pluggable generator list: The generator supports a list of "modes" or pluggable implementations (including third-party) so one build command produces both built-in and custom outputs in the same process.

We recommend Option B for process isolation, no dependency clash risk, and no stability burden on internal types. Below is the concrete shape we have in mind.

Option B refined ("Resolved Context" export):

  • No internal API exposure. The generator does not expose or stabilize _OpenAPIGeneratorCore or other internal types. Extension tools do not link against the generator's internals.
  • CLI-based contract. The generator gains an optional way to export its "Resolved Context" (e.g. post-filtered OpenAPI document + resolved configuration) to a file (e.g. JSON or a documented format). A separate process (e.g. Kawarimi) reads that file and generates additional Swift. Example: a --save-resolved-config <path> (or similar) flag that writes the resolved context after the main generation run.
  • Process isolation and version safety. Extension tools run in a separate process; no risk of dependency clashes (e.g. different SwiftSyntax or OpenAPIKit versions) inside the generator process.
  • Opt-in and non-blocking. The export step runs only when explicitly requested. Default runs are unchanged. Extension tools are separate build commands and do not block the generator's step.

Benefits: Single source of truth; custom outputs stay in sync with filter, visibility, naming; zero maintenance burden for core (no linking against third-party code, no stability guarantee on internal types); enables mock transports, default handlers, and other extensions.

Anticipated concerns:

Concern Response
"Exposing core logic will freeze internal data structures." We are not asking for internal API exposure. Only an exported artifact (resolved document + config) in a documented format. Internal types can evolve; only the export format needs a stability story.
"Hooks could slow down builds." The mechanism is opt-in. Extension tools run as separate build commands.
"Third-party tools might pull in conflicting dependencies." Process separation (Option B): extension tools use their own dependencies in their own process. No dependency hell inside the core.

Alternatives considered

  • Option A (callback in-process): Would require the core to load or depend on third-party code, risking dependency hell and forcing stability guarantees on internal types. We do not recommend it.
  • Option C (pluggable generator list in one process): Similar dependency and pipeline complexity; we prefer a clear separation: generator writes resolved context; extension reads it in another process.
  • No export: Third-party tools keep re-parsing spec and config. Current approach; works but duplicates logic and can drift from generator behavior.

Additional information

  • Use case / reference: Kawarimi (ClientTransport mock, APIProtocol default implementation).
  • We can propose a concrete schema for the exported file (which metadata to include) and contribute to a prototype if that would help.
  • Open questions: (1) Is adding an optional "export resolved context" (e.g. --save-resolved-config <path>) within the desired scope? (2) What format and schema would fit the project's goals? (3) Are there existing CLI flags or plugin outputs we could align with?

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureNew feature.status/triageCollecting information required to triage the issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions