Skip to content

openapi3gen: clear nullable on exported component bodies#1164

Merged
fenollp merged 1 commit into
getkin:masterfrom
0-don:fix/no-nullable-on-component-body
May 3, 2026
Merged

openapi3gen: clear nullable on exported component bodies#1164
fenollp merged 1 commit into
getkin:masterfrom
0-don:fix/no-nullable-on-component-body

Conversation

@0-don

@0-don 0-don commented May 3, 2026

Copy link
Copy Markdown
Contributor

Problem

When a struct is reached via *T, generateSchemaRefFor sets schema.Nullable = true to encode nullability of that one reference site:

isNullable := false
for t.Kind() == reflect.Ptr {
    t = t.Elem()
    isNullable = !isRoot
}
// ...
schema.Nullable = isNullable

With ExportComponentSchemas: true, that same *openapi3.Schema body later becomes the canonical component definition shared by every \$ref site:

g.componentSchemaRefs[typeName] = struct{}{}
return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema), nil

So the nullable flag leaks into the component itself. Real-world example from a Fuego-based API:

components:
  schemas:
    Channel:
      type: object
      nullable: true   # <-- wrong, the component is always defined
      properties: { ... }
    AddChannelRequest:
      properties:
        channel:
          \$ref: '#/components/schemas/Channel'  # this single use was *Channel

Codegen tools (Orval, openapi-typescript) interpret nullable on a component body as "the type itself can be null" and emit broken TypeScript:

export interface Channel {
  // ...
} | null   // <-- syntax error

Fix

Clear schema.Nullable right before registering the schema as a component. Nullability of the body is meaningless once the body is shared by every reference site; the field-level decision belongs at the \$ref site (and OpenAPI 3.0 has no clean way to mark a \$ref as nullable, but that's a separate concern). Inline schemas (the fallback at the bottom of the function) keep the existing nullable behavior.

Test

TestExportComponentSchemasNoNullableOnBody registers Wrapper{ Data *Channel }, exports components, and asserts schemas["Channel"].Value.Nullable == false.

The existing ExampleThrowErrorOnCycle snapshot is unaffected (it doesn't enable ExportComponentSchemas).

When a struct is reached via *T, generateSchemaRefFor sets
schema.Nullable=true to encode nullability of that one reference site.
With ExportComponentSchemas on, that same body becomes the canonical
component definition shared by every $ref site, so the nullable flag
leaks into the component itself. Codegen tools (Orval, openapi-typescript)
then emit broken types like `interface Foo {...} | null`.

Clear the flag right before registering the schema as a component.
@fenollp fenollp merged commit 3342b7c into getkin:master May 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants