Skip to content

fix(plugin): support boolean literal types and boolean enum values#3761

Merged
kamilmysliwiec merged 1 commit intonestjs:masterfrom
lucreiss:fix/boolean-literal-enum-type-handling
Apr 22, 2026
Merged

fix(plugin): support boolean literal types and boolean enum values#3761
kamilmysliwiec merged 1 commit intonestjs:masterfrom
lucreiss:fix/boolean-literal-enum-type-handling

Conversation

@lucreiss
Copy link
Copy Markdown
Contributor

@lucreiss lucreiss commented Mar 5, 2026

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?

  1. Boolean literal types are not recognized by the CLI plugin. When a DTO property is typed as literal true or false (e.g. hasPaymentMethod: true), the plugin's isBoolean() only checks TypeFlags.Boolean and misses TypeFlags.BooleanLiteral. This causes getTypeReferenceAsString to return { typeName: undefined }, so no type is emitted in the plugin metadata for the property.

  2. getEnumType() returns 'number' for boolean enum values. The function only distinguishes between 'string' and 'number', defaulting to 'number' for anything that isn't a string. So @ApiProperty({ enum: [true, false] }) produces type: "number" in the OpenAPI schema, which violates the OpenAPI 3.0 specification (Section 5.1 — Schema Object lists boolean as a valid primitive type).

  3. Explicit type is overwritten by enum type inference. Both createFromObjectLiteral() in SchemaObjectFactory and the @ApiProperty decorator unconditionally overwrite the type property with the result of getEnumType() when enum is present. This means @ApiProperty({ type: 'boolean', enum: [true] }) has its type: 'boolean' replaced with type: 'number', with no escape hatch for the user.

Issue Number: N/A

What is the new behavior?

  1. isBoolean() now also matches TypeFlags.BooleanLiteral, so the plugin correctly emits type: () => Boolean for literal true/false properties. A new isBooleanLiteral() helper is added, consistent with the existing isStringLiteral() and isEnumLiteral() functions.

  2. getEnumType() detects boolean values and returns 'boolean', producing spec-compliant OpenAPI schemas. SwaggerEnumType and getEnumValues() return types are updated to include boolean[].

  3. The enum type inference in both createFromObjectLiteral() and the @ApiProperty decorator now only applies when no explicit type is already set on the metadata. This is consistent with every other code path in the library where user-provided decorator metadata takes precedence over inference.

OpenAPI / JSON Schema specification compliance

These changes bring the generated schemas in line with the OpenAPI 3.0.3 specification and the JSON Schema Validation draft it builds on:

  • type: "boolean" is a first-class primitive. OAS 3.0.3 §4.7.24.1 (Schema Object) defines data types via JSON Schema §4.2, which lists boolean alongside string, number, integer, array, and object as valid primitive types. Before this fix, enum: [true, false] produced type: "number" — an invalid schema that would cause spec validators (e.g. swagger-cli validate, Redocly, Spectral) to flag a type mismatch since the enum values are not numbers.

  • enum constrains values, not types. JSON Schema §5.20 defines enum as a value-space constraint that is valid for any type. The OpenAPI toolchain (Swagger UI, Swagger Codegen, openapi-generator) resolves type independently from enum — they are orthogonal. By returning 'boolean' from getEnumType() when the values are booleans, the generated schema now correctly pairs type: "boolean" with enum: [true] or enum: [true, false], which is valid per §5.20 and renders correctly in Swagger UI as a constrained boolean field.

  • Explicit type must not be silently discarded. OAS 3.0.3 §4.7.24.1 states that the type field determines the data type of the schema. When a user explicitly declares @ApiProperty({ type: 'boolean', enum: [true] }), the intent is unambiguous — the property is a boolean constrained to a single value. Overwriting this to type: "number" produced a schema where the declared type contradicts the enum values, which is a validation error under JSON Schema §5.20 (enum values must be valid against the schema). The guard now preserves user intent and produces schemas where type and enum are always consistent.

  • Boolean literal discriminators are now representable. A common OpenAPI pattern for discriminated unions uses a fixed boolean property (e.g. hasSomething: true vs hasSomething: false) as a discriminator with oneOf. This requires { type: "boolean", enum: [true] } and { type: "boolean", enum: [false] } in the respective sub-schemas. Before this fix, both would incorrectly produce type: "number", breaking code generators (openapi-generator, swagger-codegen) that rely on type to determine the target language type for the property.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Files changed (source):

  • lib/plugin/utils/ast-utils.ts — update isBoolean(), add isBooleanLiteral()
  • lib/utils/enum.utils.ts — add boolean support to getEnumType() and getEnumValues()
  • lib/types/swagger-enum.type.ts — add boolean[] to SwaggerEnumType
  • lib/services/schema-object-factory.ts — guard type assignment in createFromObjectLiteral()
  • lib/decorators/api-property.decorator.ts — guard type assignment in createApiPropertyDecorator()

Files changed (tests):

  • test/plugin/fixtures/boolean-literal.dto.ts — new fixture with boolean literal properties
  • test/plugin/model-class-visitor.spec.ts — test that plugin transpiles boolean literals to type: () => Boolean
  • test/utils/enum-utils.spec.ts — unit tests for getEnumType() and getEnumValues() with boolean values
  • test/services/schema-object-factory.spec.ts — tests for boolean enum schemas and explicit type preservation

@kamilmysliwiec
Copy link
Copy Markdown
Member

Could you resolve conflicts?

The CLI plugin does not recognize TypeScript `BooleanLiteral` types (true/false),
causing properties typed as literal `true` or `false` to emit no `type` in metadata.
Additionally, `getEnumType()` only returns 'string' or 'number', so `enum: [true]`
or `enum: [true, false]` incorrectly produces `type: "number"` in the OpenAPI schema,
violating the spec. Finally, both `createFromObjectLiteral()` and the `@ApiProperty`
decorator unconditionally overwrite an explicit `type` with the inferred enum type,
preventing users from setting `type: 'boolean'` alongside `enum`.

- Update `isBoolean()` in ast-utils to also match `TypeFlags.BooleanLiteral`
- Add `isBooleanLiteral()` helper consistent with existing `isStringLiteral()`
- Add boolean detection to `getEnumType()` so it returns 'boolean' for boolean enums
- Include `boolean[]` in `SwaggerEnumType` and `getEnumValues()` return type
- Guard `type` assignment in `createFromObjectLiteral()` and `@ApiProperty` decorator
  to only infer from `getEnumType()` when no explicit `type` is already set
@lucreiss lucreiss force-pushed the fix/boolean-literal-enum-type-handling branch from 44363c3 to 0199c74 Compare April 21, 2026 21:48
@lucreiss
Copy link
Copy Markdown
Contributor Author

Hey @kamilmysliwiec, sorry for the delay. Rebased onto current master and resolved the conflicts.

Noticed that #3774 landed in the meantime and overlaps with the getEnumType() change in lib/utils/enum.utils.ts. Took master's version verbatim in the resolution, semantically identical.

The rest of this PR is independent of #3774 and still applies:

  1. Plugin BooleanLiteral support (ast-utils.ts) - literal true/false DTO properties still emit no type in metadata; fix enum issue #3774 doesn't touch the plugin.
  2. Explicit type preservation (schema-object-factory.ts, api-property.decorator.ts) - both spots still unconditionally overwrite user-provided type with the inferred one. Affects non-boolean cases too (e.g. { type: 'string', enum: [1,2,3] } becomes type: 'number').
  3. SwaggerEnumType consistency - fix enum issue #3774 widened getEnumValues's return type to include boolean[] but left the input type inconsistent.
  4. Test coverage - fix enum issue #3774 shipped without tests; this PR adds unit tests for getEnumType/getEnumValues and end-to-end tests that 'boolean' actually reaches the generated schema.

Thanks for your patience!

@kamilmysliwiec kamilmysliwiec merged commit a5ba77e into nestjs:master Apr 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants