From 6beee0cb34afb1c4e922ebbc0e18953ba08803d6 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Fri, 27 Mar 2026 19:33:38 +0530 Subject: [PATCH 1/2] fix(schema): include type field when nullable is used with allOf When a property is nullable with a $ref (e.g. UserDto | null), the schema had nullable: true and allOf but no type field. This caused Redocly lint errors since OpenAPI 3.0 requires type when nullable is present. Now preserves type: 'object' for nullable properties with schema combinators. Closes #3274 --- lib/services/schema-object-factory.ts | 3 ++- test/services/schema-object-factory.spec.ts | 30 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/services/schema-object-factory.ts b/lib/services/schema-object-factory.ts index 3b94b61d8..fa1c59b7b 100644 --- a/lib/services/schema-object-factory.ts +++ b/lib/services/schema-object-factory.ts @@ -243,7 +243,7 @@ export class SchemaObjectFactory { schemaObjectMetadata.items[declaredSchemaCombinator] = property[declaredSchemaCombinator]; delete property[declaredSchemaCombinator]; - } else { + } else if (!schemaObjectMetadata['nullable']) { delete schemaObjectMetadata.type; } } @@ -525,6 +525,7 @@ export class SchemaObjectFactory { name: metadata.name || key, required: metadata.required, ...validMetadataObject, + ...(validMetadataObject['nullable'] ? { type: 'object' } : {}), allOf: [{ $ref }] } as SchemaObjectMetadata; } diff --git a/test/services/schema-object-factory.spec.ts b/test/services/schema-object-factory.spec.ts index 371a8713d..640eb7178 100644 --- a/test/services/schema-object-factory.spec.ts +++ b/test/services/schema-object-factory.spec.ts @@ -292,6 +292,7 @@ describe('SchemaObjectFactory', () => { profile: { description: 'Profile', nullable: true, + type: 'object', allOf: [ { $ref: '#/components/schemas/CreateProfileDto' @@ -395,6 +396,35 @@ describe('SchemaObjectFactory', () => { }); }); + it('should include type "object" for nullable $ref properties (issue #3274)', () => { + class ProfileDto { + @ApiProperty() + bio: string; + } + + class UserWithNullableProfile { + @ApiProperty({ + nullable: true, + type: () => ProfileDto + }) + profile: ProfileDto; + } + + const schemas: Record = {}; + schemaObjectFactory.exploreModelSchema( + UserWithNullableProfile, + schemas + ); + expect( + (schemas['UserWithNullableProfile'] as Record).properties + .profile + ).toEqual({ + nullable: true, + type: 'object', + allOf: [{ $ref: '#/components/schemas/ProfileDto' }] + }); + }); + it('should purge linked types from properties', () => { class Human { @ApiProperty() From efc14a99b6367996e55b41027e16b26cd813975c Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 9 Apr 2026 19:29:33 +0530 Subject: [PATCH 2/2] test(e2e): add e2e test for nullable allOf type inclusion --- e2e/src/cats/dto/create-cat.dto.ts | 3 +++ e2e/validate-schema.e2e-spec.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/e2e/src/cats/dto/create-cat.dto.ts b/e2e/src/cats/dto/create-cat.dto.ts index 995060269..a4251238a 100644 --- a/e2e/src/cats/dto/create-cat.dto.ts +++ b/e2e/src/cats/dto/create-cat.dto.ts @@ -96,6 +96,9 @@ export class CreateCatDto { @ApiProperty({ description: 'tag', required: false }) readonly tag: TagDto; + @ApiProperty({ description: 'nullable tag', nullable: true, type: () => TagDto }) + readonly nullableTag: TagDto; + nested: { first: string; second: number; diff --git a/e2e/validate-schema.e2e-spec.ts b/e2e/validate-schema.e2e-spec.ts index c58026706..4dd3f8608 100644 --- a/e2e/validate-schema.e2e-spec.ts +++ b/e2e/validate-schema.e2e-spec.ts @@ -252,6 +252,18 @@ describe('Validate OpenAPI schema', () => { }); }); + it('should include type field when nullable is used with allOf (issue #3274)', () => { + const document = SwaggerModule.createDocument(app, options); + const createCatDtoSchema = document.components?.schemas + ?.CreateCatDto as SchemaObject; + expect(createCatDtoSchema.properties.nullableTag).toEqual({ + description: 'nullable tag', + nullable: true, + type: 'object', + allOf: [{ $ref: '#/components/schemas/TagDto' }] + }); + }); + it('should not add optional properties to required list', () => { const document = SwaggerModule.createDocument(app, options); const required = (document.components?.schemas?.Cat as SchemaObject)