Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/decorators/api-property.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EnumAllowedTypes,
SchemaObjectMetadata
} from '../interfaces/schema-object-metadata.interface';
import { ParameterStyle } from '../interfaces/open-api-spec.interface';
import { getEnumType, getEnumValues } from '../utils/enum.utils';
import { createPropertyDecorator, getTypeIsArrayTuple } from './helpers';

Expand All @@ -20,6 +21,19 @@ export type ApiPropertyCommonOptions = SchemaObjectMetadata & {
* @see [Swagger link objects](https://swagger.io/docs/specification/links/)
*/
link?: () => Type<unknown> | Function;
/**
* Describes how the parameter value will be serialized.
*
* @see [Swagger serialization](https://swagger.io/docs/specification/serialization/)
*/
style?: ParameterStyle;
/**
* When true, array or object parameter values generate
* separate parameters for each value.
*
* @see [Swagger serialization](https://swagger.io/docs/specification/serialization/)
*/
explode?: boolean;
};

export type ApiPropertyOptions =
Expand Down
73 changes: 73 additions & 0 deletions test/explorer/swagger-explorer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2615,4 +2615,77 @@ describe('SwaggerExplorer', () => {
GlobalParametersStorage.clear();
});
});

describe('deepObject style for nested query params', () => {
class GeolocationDto {
@ApiProperty()
latitude: number;

@ApiProperty()
longitude: number;

@ApiProperty({ description: 'Distance in kilometers' })
distance: number;
}

class SearchQueryDto {
@ApiProperty({
required: false,
type: () => GeolocationDto,
style: 'deepObject',
explode: true
})
geolocation?: GeolocationDto;
}

@Controller('search')
class SearchController {
@Get()
@ApiOperation({ summary: 'Search' })
search(@Query() query: SearchQueryDto): void {}
}

it('should emit a single query parameter with style=deepObject and $ref schema', () => {
const explorer = new SwaggerExplorer(schemaObjectFactory);
const routes = explorer.exploreController(
{
instance: new SearchController(),
metatype: SearchController
} as InstanceWrapper<SearchController>,
new ApplicationConfig(),
{ modulePath: '' }
);

const params = routes[0].root.parameters;
expect(params).toHaveLength(1);
expect(params[0]).toMatchObject({
name: 'geolocation',
in: 'query',
required: false,
style: 'deepObject',
explode: true,
schema: {
$ref: '#/components/schemas/GeolocationDto'
}
});
});

it('should not flatten GeolocationDto properties into individual query params', () => {
const explorer = new SwaggerExplorer(schemaObjectFactory);
const routes = explorer.exploreController(
{
instance: new SearchController(),
metatype: SearchController
} as InstanceWrapper<SearchController>,
new ApplicationConfig(),
{ modulePath: '' }
);

const params = routes[0].root.parameters;
const paramNames = params.map((p: any) => p.name);
expect(paramNames).not.toContain('latitude');
expect(paramNames).not.toContain('longitude');
expect(paramNames).not.toContain('distance');
});
});
});
71 changes: 71 additions & 0 deletions test/services/schema-object-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,77 @@ describe('SchemaObjectFactory', () => {
});
});

describe('createFromModel (deepObject)', () => {
class GeolocationDto {
@ApiProperty()
latitude: number;

@ApiProperty()
longitude: number;

@ApiProperty({ description: 'Distance in kilometers' })
distance: number;
}

it('should preserve style and explode at ParameterObject level for named nested query params', () => {
class QueryDto {
@ApiProperty({
required: false,
type: () => GeolocationDto,
style: 'deepObject',
explode: true
})
geolocation?: GeolocationDto;
}

const schemas = {};
const param = {
name: 'geolocation',
in: 'query',
required: false,
type: GeolocationDto,
style: 'deepObject',
explode: true
};

const [result] = schemaObjectFactory.createFromModel(
[param as any],
schemas
);

const mapped = swaggerTypesMapper.mapParamTypes([result as any]);
expect(mapped[0]).toMatchObject({
name: 'geolocation',
in: 'query',
style: 'deepObject',
explode: true,
schema: {
$ref: '#/components/schemas/GeolocationDto'
}
});
});

it('should not flatten nested object properties when style is deepObject', () => {
const schemas = {};
const param = {
name: 'geolocation',
in: 'query',
required: false,
type: GeolocationDto,
style: 'deepObject',
explode: true
};

const result = schemaObjectFactory.createFromModel(
[param as any],
schemas
);

// Should produce a single parameter (not 3 flattened ones)
expect(result).toHaveLength(1);
});
});

describe('createEnumSchemaType', () => {
it('should assign schema type correctly if enumName is provided', () => {
const metadata = {
Expand Down