Skip to content

feat(swagger): support raw component schemas - #3914

Open
yudin-s wants to merge 2 commits into
nestjs:v12.0.0from
yudin-s:feat/raw-component-schemas
Open

feat(swagger): support raw component schemas#3914
yudin-s wants to merge 2 commits into
nestjs:v12.0.0from
yudin-s:feat/raw-component-schemas

Conversation

@yudin-s

@yudin-s yudin-s commented May 15, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

@ApiSchema() metadata is always merged into a generated object schema. Even when a class-level schema declares oneOf, anyOf, or allOf, the resulting components.schemas entry still gets generated object scaffolding such as type: 'object' and properties: {}.

This prevents using a class-level placeholder as a raw top-level component schema for cases like oneOf plus discriminator.mapping.

Issue Number: Fixes #3886

What is the new behavior?

@ApiSchema() now accepts the full SchemaObject shape. When a class-level schema declares oneOf, anyOf, or allOf, SchemaObjectFactory registers it as a raw top-level component schema instead of wrapping it in generated object metadata.

This lets users define component schemas such as:

@ApiSchema({
  name: 'Pet',
  oneOf: [
    { $ref: '#/components/schemas/Cat' },
    { $ref: '#/components/schemas/Dog' },
  ],
  discriminator: {
    propertyName: 'type',
    mapping: {
      cat: '#/components/schemas/Cat',
      dog: '#/components/schemas/Dog',
    },
  },
})
class PetDto {}

The existing object-schema behavior is preserved for regular DTOs and for @ApiSchema() options without schema combinators.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Local checks:

  • npm test -- test/services/schema-object-factory.spec.ts
  • npm test -- test/explorer/swagger-explorer.spec.ts
  • npm run build
  • npm run lint (passes with existing warnings outside this change)
  • npm exec prettier -- --check lib/decorators/api-schema.decorator.ts lib/services/schema-object-factory.ts test/services/schema-object-factory.spec.ts test/explorer/swagger-explorer.spec.ts
  • git diff --check

Note: local Node is v20.18.3, while some dependencies now warn for ^20.19.0 || >=22.12.0. I installed missing optional native packages for rolldown and oxlint without changing the lockfile to run the checks.

@yudin-s
yudin-s marked this pull request as ready for review May 15, 2026 07:44
@kamilmysliwiec

Copy link
Copy Markdown
Member

could you please target v12.0.0?

@yudin-s
yudin-s force-pushed the feat/raw-component-schemas branch from 7234c1e to 02c3b62 Compare May 15, 2026 10:34
@yudin-s
yudin-s changed the base branch from master to chore/esm-migration May 15, 2026 10:34
@yudin-s

yudin-s commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

@kamilmysliwiec we don't have v12.0.0 on this repo

Rebased and retargeted this PR to chore/esm-migration, which appears to be the active v12 branch at the moment (I couldn't find a separate v12.0.0 branch in the repository).

@yudin-s
yudin-s force-pushed the feat/raw-component-schemas branch from 02c3b62 to 1217e3f Compare May 27, 2026 08:08
@kamilmysliwiec
kamilmysliwiec changed the base branch from chore/esm-migration to v12.0.0 May 27, 2026 09:39
@kamilmysliwiec

Copy link
Copy Markdown
Member

could you update e2e tests as well?

@yudin-s

yudin-s commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

Done!

@dogemad

dogemad commented Jul 21, 2026

Copy link
Copy Markdown

I think this is solving a named schema registration problem rather than a type modeling problem.

Using an empty class as a placeholder is misleading because it makes PetDto look like a real DTO or a common base type, while it is actually only being used as a runtime token for registering an OpenAPI component schema.

Because PetDto is an empty class, it does not establish a meaningful type relationship with CatDto or DogDto, and it does not model CatDto | DogDto in TypeScript.

For example:

@ApiSchema({
  name: 'Pet',
  oneOf: [
    { $ref: getSchemaPath(CatDto) },
    { $ref: getSchemaPath(DogDto) },
  ],
  discriminator: {
    propertyName: 'type',
    mapping: {
      cat: getSchemaPath(CatDto),
      dog: getSchemaPath(DogDto),
    },
  },
})
class PetDto {}

The actual application type still needs to be declared separately:

type PetDto = CatDto | DogDto;

Would it make more sense to expose a helper for registering a named SchemaObject directly?

const PetApiSchema = defineApiSchema('Pet', {
  oneOf: [
    { $ref: getSchemaPath(CatDto) },
    { $ref: getSchemaPath(DogDto) },
  ],
  discriminator: {
    propertyName: 'type',
    mapping: {
      cat: getSchemaPath(CatDto),
      dog: getSchemaPath(DogDto),
    },
  },
});

The helper could accept the standard OpenAPI SchemaObject shape rather than introducing a union-specific abstraction. That would preserve the existing OpenAPI model while also supporting primitives, arrays, enums, allOf, anyOf, oneOf, not, and other valid schema constructs.

There is already a similar precedent in @nestjs/graphql, where createUnionType() is used to register union schema metadata instead of requiring an empty placeholder class that appears to represent an application type.

A similar helper here would keep the responsibilities clear:

  • TypeScript types describe the application model.
  • SchemaObject describes the OpenAPI schema.
  • The helper registers the schema under components.schemas.
  • No placeholder class is required purely for schema registration.

This seems more consistent with the existing NestJS approach to schema-level constructs and avoids overloading DTO classes with schema-registration semantics.

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.

Allow registering raw top-level component schemas

3 participants