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) 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()