Skip to content

Add optional type generation for inline schemas#2366

Merged
mromaszewicz merged 2 commits into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-1139
Jun 20, 2026
Merged

Add optional type generation for inline schemas#2366
mromaszewicz merged 2 commits into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-1139

Conversation

@mromaszewicz

Copy link
Copy Markdown
Member

Adds output-options.generate-types-for-anonymous-schemas (default false). When set, every inline schema that would otherwise generate as an anonymous Go struct is instead emitted as a named type with a path- derived name (e.g. GetRolesId200JSONResponseBody_Data). Equivalent to adding x-go-type-name to every inline schema; when both are present at the same site, x-go-type-name wins.

I will need to document this, since it's still better, from a usability perspective, to put concrete types of interest in component/schemas or as a second alternative, use x-go-type-name. However, this is requested often enough, that I'd thing it's worth exposing as a feature.

Test coverage reorganized: internal/test/anonymous_inner_hoisting/ now has two subdirectories. The existing always-on hoisting test (oneOf, anyOf, additionalProperties) moves to implicit/. The new flag-gated behavior gets its own test in global/, exercising the canonical issue shape (allOf-merged response with a sibling inline data object that itself contains a nested role reference).

Closes: #1139

@mromaszewicz
mromaszewicz requested a review from a team as a code owner May 4, 2026 02:39
@mromaszewicz mromaszewicz added enhancement New feature or request notable changes Used for release notes to highlight these more highly labels May 4, 2026
@greptile-apps

greptile-apps Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds optional named type generation for inline schemas. The main changes are:

  • Adds output-options.generate-types-for-anonymous-schemas to hoist anonymous inline structs into named Go types.
  • Updates schema and operation generation so hoisted inline response and nested schemas are referenced consistently.
  • Refactors type collection and boilerplate rendering for component and operation-derived types.
  • Adds CLI warnings for config combinations where hoisted names may be referenced without model declarations.
  • Reorganizes anonymous inner hoisting tests into implicit and flag-gated global cases.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
pkg/codegen/schema.go Marks auto-hoisted inline schemas as referenced named types while preserving their additional type declarations.
pkg/codegen/codegen.go Splits component and operation type collection while keeping declaration and boilerplate emission paths aligned.
pkg/codegen/configuration.go Adds cross-field warnings for anonymous schema hoisting without model generation.

Reviews (4): Last reviewed commit: "codegen: warn when generate-types-for-an..." | Re-trigger Greptile

Comment thread internal/test/anonymous_inner_hoisting/global/spec.yaml
@jamietanna

Copy link
Copy Markdown
Member

Given #648 was going to close #626, #636. they may be relevant here

Comment thread internal/test/anonymous_inner_hoisting/global/spec.yaml Outdated

@jamietanna jamietanna left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Functionally it looks good - I wonder if we can improve the generated Description that gets added to it?

It's generally an existing issue, but especially where we're generating this new anonymous schema, it'd be great to improve

I..e we get:

 // AddMicrosoftTeamsChatTabTaskParams defines model for add_microsoft_teams_chat_tab_task_params.
 type AddMicrosoftTeamsChatTabTaskParams struct {
-       Chat struct {
-               ID   *string `json:"id,omitempty"`
-               Name *string `json:"name,omitempty"`
-       } `json:"chat"`
+       Chat AddMicrosoftTeamsChatTabTaskParams_Chat `json:"chat"`
 
        // Link The tab link
        Link     string                                      `json:"link"`
@@ -39399,15 +39402,18 @@ type AddMicrosoftTeamsChatTabTaskParams struct {
        Title string `json:"title"`
 }
 
+// AddMicrosoftTeamsChatTabTaskParams_Chat defines model for AddMicrosoftTeamsChatTabTaskParams.Chat.
+type AddMicrosoftTeamsChatTabTaskParams_Chat struct {
+       ID   *string `json:"id,omitempty"`
+       Name *string `json:"name,omitempty"`
+}

I wonder if we could instead generate it as i.e.

// AddMicrosoftTeamsChatTabTaskParams_Chat defines model for the anonymous schema `chat` under path ..., or something similar?

Comment thread internal/test/anonymous_inner_hoisting/global/cfg.yaml
@mromaszewicz

mromaszewicz commented May 4, 2026

Copy link
Copy Markdown
Member Author

Functionally it looks good - I wonder if we can improve the generated Description that gets added to it?

It's generally an existing issue, but especially where we're generating this new anonymous schema, it'd be great to improve

I..e we get:

 // AddMicrosoftTeamsChatTabTaskParams defines model for add_microsoft_teams_chat_tab_task_params.
 type AddMicrosoftTeamsChatTabTaskParams struct {
-       Chat struct {
-               ID   *string `json:"id,omitempty"`
-               Name *string `json:"name,omitempty"`
-       } `json:"chat"`
+       Chat AddMicrosoftTeamsChatTabTaskParams_Chat `json:"chat"`
 
        // Link The tab link
        Link     string                                      `json:"link"`
@@ -39399,15 +39402,18 @@ type AddMicrosoftTeamsChatTabTaskParams struct {
        Title string `json:"title"`
 }
 
+// AddMicrosoftTeamsChatTabTaskParams_Chat defines model for AddMicrosoftTeamsChatTabTaskParams.Chat.
+type AddMicrosoftTeamsChatTabTaskParams_Chat struct {
+       ID   *string `json:"id,omitempty"`
+       Name *string `json:"name,omitempty"`
+}

I wonder if we could instead generate it as i.e.

// AddMicrosoftTeamsChatTabTaskParams_Chat defines model for the anonymous schema `chat` under path ..., or something similar?

Ok, so the code which generates those comments is kinda broken. It calls everything a "parameter". I'm going to make a minimal fix to this PR to inject the correct string with the field name, eg, AddMicrosoftTeamsChatTabTaskParams.Chat., however, the rest of the string will remain wrong. I'll file an Issue for the misleading comments and deal with the high level problems separately.

Update:
After exploring a bit, the fix for this comment is quite painful. I don't want it in this PR.

Adds output-options.generate-types-for-anonymous-schemas (default false).
When set, every inline schema that would otherwise generate as an
anonymous Go struct is instead emitted as a named type with a path-
derived name (e.g. GetRolesId200JSONResponseBody_Data). Equivalent to
adding x-go-type-name to every inline schema; when both are present at
the same site, x-go-type-name wins.

Test coverage reorganized: internal/test/anonymous_inner_hoisting/ now
has two subdirectories. The existing always-on hoisting test (oneOf,
anyOf, additionalProperties) moves to implicit/. The new flag-gated
behavior gets its own test in global/, exercising the canonical issue
shape (allOf-merged response with a sibling inline `data` object that
itself contains a nested `role` reference).

Closes: oapi-codegen#1139
@mromaszewicz

Copy link
Copy Markdown
Member Author

Greptile had a good finding. This refactor breaks the splitting of code generation into (types, server, strict-server), etc, because of references among the types. Operation Definitions can introduce new types (the anonymous inner types), but those are only emitted when types are being emitted, so if I generate types without a server, then say, server: echo into another file, then we will have missing types.

the fix is non-trivial. It's a big refactor of the codegen structure.

Comment thread pkg/codegen/schema.go
…s this config does not emit

The new `output-options.generate-types-for-anonymous-schemas` flag (issue
flow through the same emission path that `generate.models` gates. In a
single-config setup where the flag is set with `models: false` but a
client or server generator is enabled, the generated code emits
references to the hoisted names without declaring them, and `go build`
fails.

Multi-config setups where one config emits `models: true` and a sibling
emits a client/server into the same Go package work correctly today;
the hoisting decisions are deterministic across the two codegen
invocations and the sibling's declarations resolve same-package
references. The failure mode is specifically the single-config case.

The two cases are indistinguishable from a single codegen invocation
(both look like `flag: true, models: false, <ops>: true`), so this
warns rather than errors. Split-config users see an informational
notice they can ignore; misconfigured single-config users get a
heads-up before `go build` complains.

Changes:

- pkg/codegen/configuration.go:
  - Add `GenerateOptions.AnyOperationGenerator()` reporting whether any
    client/server/strict generator is enabled.
  - Add `Configuration.Warnings()`, a cross-field diagnostic that fires
    when `GenerateTypesForAnonymousSchemas && !Models &&
    AnyOperationGenerator()`. Follows the existing
    `GenerateOptions.Warnings()` pattern used for the std-http-server
    Go-version check.
  - Strengthen the doc comment on `GenerateTypesForAnonymousSchemas` to
    call out the single-config failure mode and the multi-config
    consistency requirement explicitly.

- configuration-schema.json: matching description text for the
  IDE-surfaced schema doc.

- cmd/oapi-codegen/oapi-codegen.go: wire `opts.Warnings()` to stderr
  immediately after the existing `Generate.Warnings()` printout, under a
  "cross-field configuration warning(s)" header.

Helper-function cleanup in pkg/codegen/codegen.go:

- Decompose the monolithic `GenerateTypeDefinitions` into
  `collectComponentTypes` (gathers #/components-derived TypeDefinitions),
  `collectOperationTypes` (gathers op-derived TypeDefinitions for the
  boilerplate scan), and `renderBoilerplate` (runs enum / additional-
  properties / union / union+additional passes over the union). The
  emission gating is unchanged — still a single `if opts.Generate.Models`
  block — so this is a pure refactor with no behavioral effect on any
  existing config. Generated output is byte-identical.

No new fixtures or behavioral changes; this commit is intended to be
squashed with the parent `Add optional type generation for inline
schemas` commit before the PR lands.
@mromaszewicz
mromaszewicz merged commit c1d48f7 into oapi-codegen:main Jun 20, 2026
14 checks passed
mromaszewicz added a commit that referenced this pull request Jun 20, 2026
Resolved one conflict in pkg/codegen/codegen.go: upstream now uses the
collectComponentTypes / collectOperationTypes / renderBoilerplate helper
decomposition (from PR #2366). Kept that structure and changed the op-
related helper calls to use allOps (paths + webhooks + callbacks)
rather than ops, matching what the previous GenerateTypeDefinitions(t,
spec, allOps, ...) call did, so webhook/callback-derived types continue
to be emitted.
lwc pushed a commit to lwc/oapi-codegen that referenced this pull request Jun 23, 2026
* Add optional type generation for inline schemas

Adds output-options.generate-types-for-anonymous-schemas (default false).
When set, every inline schema that would otherwise generate as an
anonymous Go struct is instead emitted as a named type with a path-
derived name (e.g. GetRolesId200JSONResponseBody_Data). Equivalent to
adding x-go-type-name to every inline schema; when both are present at
the same site, x-go-type-name wins.

Test coverage reorganized: internal/test/anonymous_inner_hoisting/ now
has two subdirectories. The existing always-on hoisting test (oneOf,
anyOf, additionalProperties) moves to implicit/. The new flag-gated
behavior gets its own test in global/, exercising the canonical issue
shape (allOf-merged response with a sibling inline `data` object that
itself contains a nested `role` reference).

Closes: oapi-codegen#1139

* codegen: warn when generate-types-for-anonymous-schemas requires types this config does not emit

The new `output-options.generate-types-for-anonymous-schemas` flag (issue
flow through the same emission path that `generate.models` gates. In a
single-config setup where the flag is set with `models: false` but a
client or server generator is enabled, the generated code emits
references to the hoisted names without declaring them, and `go build`
fails.

Multi-config setups where one config emits `models: true` and a sibling
emits a client/server into the same Go package work correctly today;
the hoisting decisions are deterministic across the two codegen
invocations and the sibling's declarations resolve same-package
references. The failure mode is specifically the single-config case.

The two cases are indistinguishable from a single codegen invocation
(both look like `flag: true, models: false, <ops>: true`), so this
warns rather than errors. Split-config users see an informational
notice they can ignore; misconfigured single-config users get a
heads-up before `go build` complains.

Changes:

- pkg/codegen/configuration.go:
  - Add `GenerateOptions.AnyOperationGenerator()` reporting whether any
    client/server/strict generator is enabled.
  - Add `Configuration.Warnings()`, a cross-field diagnostic that fires
    when `GenerateTypesForAnonymousSchemas && !Models &&
    AnyOperationGenerator()`. Follows the existing
    `GenerateOptions.Warnings()` pattern used for the std-http-server
    Go-version check.
  - Strengthen the doc comment on `GenerateTypesForAnonymousSchemas` to
    call out the single-config failure mode and the multi-config
    consistency requirement explicitly.

- configuration-schema.json: matching description text for the
  IDE-surfaced schema doc.

- cmd/oapi-codegen/oapi-codegen.go: wire `opts.Warnings()` to stderr
  immediately after the existing `Generate.Warnings()` printout, under a
  "cross-field configuration warning(s)" header.

Helper-function cleanup in pkg/codegen/codegen.go:

- Decompose the monolithic `GenerateTypeDefinitions` into
  `collectComponentTypes` (gathers #/components-derived TypeDefinitions),
  `collectOperationTypes` (gathers op-derived TypeDefinitions for the
  boilerplate scan), and `renderBoilerplate` (runs enum / additional-
  properties / union / union+additional passes over the union). The
  emission gating is unchanged — still a single `if opts.Generate.Models`
  block — so this is a pure refactor with no behavioral effect on any
  existing config. Generated output is byte-identical.

No new fixtures or behavioral changes; this commit is intended to be
squashed with the parent `Add optional type generation for inline
schemas` commit before the PR lands.
@jamietanna

Copy link
Copy Markdown
Member

In rootlyhq/rootly-go#131 I've noticed that some nullable entries are missing - might be intentional, but I'm taking a look

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

Labels

enhancement New feature or request notable changes Used for release notes to highlight these more highly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate a type for nested struct in respond instead of anonymous struct

2 participants