Skip to content

Commit fddb84b

Browse files
Merge pull request #2684 from drewish/extensions
feat(@nestjs/swagger): Support extensions in Document and Schema objects
2 parents c4cbca6 + ae67789 commit fddb84b

File tree

5 files changed

+37
-4
lines changed

5 files changed

+37
-4
lines changed

e2e/api-spec.json

+4
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,10 @@
10851085
]
10861086
}
10871087
},
1088+
"x-tags": [
1089+
"foo",
1090+
"bar"
1091+
],
10881092
"required": [
10891093
"name",
10901094
"age",

e2e/src/cats/dto/create-cat.dto.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { ApiExtraModels, ApiProperty } from '../../../../lib';
1+
import { ApiExtension, ApiExtraModels, ApiProperty } from '../../../../lib';
22
import { ExtraModel } from './extra-model.dto';
33
import { LettersEnum } from './pagination-query.dto';
44
import { TagDto } from './tag.dto';
55

66
@ApiExtraModels(ExtraModel)
7+
@ApiExtension('x-tags', ['foo', 'bar'])
78
export class CreateCatDto {
89
@ApiProperty()
910
readonly name: string;

lib/document-builder.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Logger } from '@nestjs/common';
2-
import { isString, isUndefined, negate, pickBy } from 'lodash';
2+
import { clone, isString, isUndefined, negate, pickBy } from 'lodash';
33
import { buildDocumentBase } from './fixtures/document.base';
44
import { OpenAPIObject } from './interfaces';
55
import {
@@ -85,6 +85,18 @@ export class DocumentBuilder {
8585
return this;
8686
}
8787

88+
public addExtension(extensionKey: string, extensionProperties: any): this {
89+
if (!extensionKey.startsWith('x-')) {
90+
throw new Error(
91+
'Extension key is not prefixed. Please ensure you prefix it with `x-`.'
92+
);
93+
}
94+
95+
this.document[extensionKey] = clone(extensionProperties);
96+
97+
return this;
98+
}
99+
88100
public addSecurity(name: string, options: SecuritySchemeObject): this {
89101
this.document.components.securitySchemes = {
90102
...(this.document.components.securitySchemes || {}),

lib/services/schema-object-factory.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,13 @@ export class SchemaObjectFactory {
204204
if (!propertiesWithType) {
205205
return '';
206206
}
207+
const extensionProperties = Reflect.getMetadata(DECORATORS.API_EXTENSION, type) || {};
207208
const typeDefinition: SchemaObject = {
208209
type: 'object',
209210
properties: mapValues(keyBy(propertiesWithType, 'name'), (property) =>
210211
omit(property, ['name', 'isArray', 'required', 'enumName'])
211-
) as Record<string, SchemaObject | ReferenceObject>
212+
) as Record<string, SchemaObject | ReferenceObject>,
213+
...extensionProperties
212214
};
213215
const typeDefinitionRequiredFields = propertiesWithType
214216
.filter((property) => property.required != false)

test/services/schema-object-factory.spec.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ApiProperty } from '../../lib/decorators';
1+
import { ApiExtension, ApiProperty } from '../../lib/decorators';
22
import { SchemasObject } from '../../lib/interfaces/open-api-spec.interface';
33
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
44
import { SchemaObjectFactory } from '../../lib/services/schema-object-factory';
@@ -297,6 +297,20 @@ describe('SchemaObjectFactory', () => {
297297
properties: { name: { type: 'string', minLength: 1 } }
298298
});
299299
});
300+
301+
it('should include extension properties', () => {
302+
@ApiExtension('x-test', 'value')
303+
class CreatUserDto {
304+
@ApiProperty({ minLength: 0, required: true })
305+
name: string;
306+
}
307+
308+
const schemas: Record<string, SchemasObject> = {};
309+
310+
schemaObjectFactory.exploreModelSchema(CreatUserDto, schemas);
311+
312+
expect(schemas[CreatUserDto.name]['x-test']).toEqual('value');
313+
});
300314
});
301315

302316
describe('createEnumSchemaType', () => {

0 commit comments

Comments
 (0)