NestJs/Swagger: How to add `additionalProperties: false` on an existing DTO class - nestjs

Hello I am new to Nestjs and trying to implement additionalProperties: false on a DTO class that already has properties on it. I see that the additionalProperties key can be added inside #ApiProperty({ schema: ... { additionalProperties : false} }) but I want to add it like this:
class SomeResponseDto {
#ApiResponseProperty()
text: string;
#ApiResponseProperty()
id: string;
// maybe a new Decorator like this?
#ApiAdditionalProperties(false)
}
...so that only text and id is allowed in the SomeResponseDto. I want to avoid having to define every class as a schema object inside the controllers.
I should note that I'm using express-openapi-validator with nestjs/swagger, and do not want to use the class-validator/class-transformer plugins, so that I can validate responses as well as requests by using just nestjs/swagger decorators.
I have also tried this:
#ApiResponse({
status: 200,
description: 'success',
schema: {
oneOf: [
{
$ref: getSchemaPath(SomeResponseDto),
// additionalProperties: false, <-- this gets ignored
},
],
// additionalProperties: false, <-- this throws OpenApi invalid response errors
},
Is there any easy way to add additionalProperties: false on an existing DTO class?

Here is a workaround: Post this code inside the bootstrap() method of the application
const schemas = document?.components?.schemas;
Object.keys(schemas).forEach((item) => {
if (schemas[item]['properties']?.allowAdditional) {
schemas[item]['additionalProperties'] = true;
} else {
schemas[item]['additionalProperties'] = false;
}
});
This code above will set additionalProperties to false by default.
If for some reason you have a DTO class that you want to allow additionalProperties: true, then inside your DTO Class, add the following decorator and property:
export class SomeResponseDTO {
#ApiPropertyOptional()
allowAdditional?: boolean;
#ApiResponseProperty()
text: string;
#ApiResponseProperty()
id: string;
}
This is a simple solution for true/false case, but can be modified as needed to handle other use cases.
I hope this helps someone!

Related

NestJs class-validator accepts empty payload

I'm trying to create validator that accepts 2 values as strings (must exist, min/max length etc).
The issue I am facing is that when I POST empty payload the validation passes and TypeORM tries to insert null values and I end up with HTTP status 500.
When I POST with invalid payload the validation works properly.
I want to get proper validation errors as a response when payload is empty (existence of name property, min/max length etc...) ...
I tried adding various class annotations and global settings but no luck...
Global validation enabled:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
forbidUnknownValues: true,
skipMissingProperties: false, //Thought this would check for missing properties
transform: true,
}),
);
Entity:
#Entity('r_cat')
export class ResearchCategory {
[...]
#PrimaryGeneratedColumn()
id: number;
#ApiProperty({
description: 'Name of the category',
example: 'Analytics, Integration',
})
#Column('text')
#IsString()
#ApiProperty()
#Length(2, 30)
#IsNotEmpty()
#IsDefined()
name: string;
[...]
My request object (DTO):
export class CreateResearchCategoryRequest extends PartialType(
OmitType(ResearchCategory, ['created_at', 'updated_at', 'deleted_at'] as const),
) {}
Controller:
#Post()
public async create(
#Req() req,
#Body() researchCategory: CreateResearchCategoryRequest,
): Promise<ResearchCategory> {
return await this.service.createNew(researchCategory, req.user);
}
I'm not sure this is the correct way to create a DTO based on the repository class. The usage of the mapper is different from what you've done since Nest examples show is all based on another DTO class, not a repository class.
However, I think the following snippet should work in your situation:
export class CreateResearchCategoryRequest extends OmitType(
ResearchCategory, ['created_at', 'updated_at', 'deleted_at'] as const
) {}

Nestjs custom class-validator decorator doesn't get the value from param

I have created a Custom ValidatorConstraint in Nestjs from class-validator, just to create my own decorator and apply later to DTO classes for validations.
Imagine this route.
foo/:client
after request it, I just want to check that client contains some pattern
client --> XXX123 ✘
client --> ZZZ123 ✔
I am struggling with it and although I saw some examples, it is still not very clear why it fails.
main.ts
app.useGlobalPipes(new ValidationPipe());
useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.module.ts
providers: [..., IsValidClientConstraint],
app.controller.ts
#Get(':client')
getHello(#Param('client') client: ClientDTO): string {
custom.validator.ts
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
import { Injectable } from '#nestjs/common';
#ValidatorConstraint({ async: false })
#Injectable()
export class IsValidClientConstraint implements ValidatorConstraintInterface {
validate(client: any, args: ValidationArguments) {
console.log(client)
return client.includes('ZZZ');
}
}
export function IsValidClient(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsValidClientConstraint,
});
};
}
client.dto.ts
export class ClientDTO {
#IsValidClient({ message: 'blabla' })
client: string;
}
However doing a request with -> foo/XXX344
ERROR [ExceptionsHandler] Cannot read properties of undefined
So it is not receiving the value of the client itself
What am I missing there?
I with leave the repo here
https://github.com/ackuser/nestjs-sample-custom-validator
Thanks,
I appreciate any help
You don't have to pass parameter name to #Param decorator when you want to use class-validator to validate params, So change it to #Param() params: ClientDTO instead.
Use custom pipes if you want to validate each parameter one by one. because the DTO method you used turns all parameters (not just :client) into a single data class.
Also in IsValidClientConstraint check if client is defined before using it.

nestjs/crud Passing Filter conditions from a controller to a service but CrudRequest type

I am using nestjsx/crud to control my API endpoints, and I would like to call another service from an existing controller as I would like to receive a set of tags back which I will then do more processing on.
The controller that will return me back the tags:
#Crud({
model: {
type: Test,
},
})
#Controller("test")
export class TestController implements CrudController<Test> {
constructor(public service: TestService) {}
get base(): CrudController<Test> {
return this;
}
#Override()
getMany(
#ParsedRequest() req: CrudRequest,
) {
return this.base.getManyBase(req);
}
}
I then have this controller which calls the getMany endpoint and then does some work with the results.
#Get('gettest')
getTest( #ParsedRequest() req: CrudRequest ) {
const results = this.testService.getMany( {
filter: [ { field: 'name', operator: 'eq', value: 'Justin' } ],
});
//
// Do more processing with the results
//
}
The issue I am experiencing is passing the filter parameter into the getMany as it is of type CrudRequest, so I am getting this message:
Object literal may only specify known properties, and 'filter' does not exist in type 'CrudRequest'
I have tried a number of different options and this was a project using nestjsx/crud 2.x so have moved it to latest but cannot find any material on the best way of passing filter information between services.
Does anyone know what the best way of doing this is?
Many thanks

Node.JS: How do you validate QueryParams in routing-controllers?

lets say you have an interface like this:
import { Get, QueryParam } from 'routing-controllers';
// ...
#Get('/students')
async getStudents(
#QueryParam('count') count?: number,
): Promise<void> {
console.log(count);
}
How do you ensure count is an int and not a float, for example? Something like this is not valid:
#IsInt() #QueryParam('count') count?: number,
IsInt can only be used on a class property, eg for a body model, not for a single parameter value. But according to. this https://github.com/typestack/routing-controllers#auto-validating-action-params it is possible:
This technique works not only with #Body but also with #Param,
#QueryParam, #BodyParam and other decorators.
I had missed this in the docs: https://github.com/typestack/routing-controllers#inject-query-parameters By injecting all of the QueryParams instead of individual QueryParam, you can validate them as a class model:
enum Roles {
Admin = "admin",
User = "user",
Guest = "guest",
}
class GetUsersQuery {
#IsPositive()
limit: number;
#IsAlpha()
city: string;
#IsEnum(Roles)
role: Roles;
#IsBoolean()
isActive: boolean;
}
#Get("/users")
getUsers(#QueryParams() query: GetUsersQuery) {
// here you can access query.role, query.limit
// and others valid query parameters
}
Also, make sure you don't use barrel-imports to import Enums, or open-api-generator will produce an error that the enum is undefined and not and object; eg avoid this: import { Roles } from '../..'

NestJS Swagger - How to declare multiselect enum field?

I'm using #nestjs/swagger module in my application. I would like to declare multiselect enum field for one of my query parameters. I've read in the documentation that I can achieve this by combining enumand isArray properties. So I did something like:
class QueryParams {
#ApiModelProperty({
enum: ['test_status_1', 'test_status_2'],
isArray: true
})
status: string[]
}
I'm using this class to validate query. Unfortunately it's not working. So I decided to use #ApiImplicitQuery in my controller like this:
#ApiImplicitQuery({
name: 'status',
enum: ['test_status_1', 'test_status_2'],
isArray: true,
collectionFormat: 'csv'
})
This allowed my to declare multiselect enum, but there is a problem with the way those parameters are being added to the url. If I select multiple values I get:
?status=test_status_1&status=test_status2
I would like them to be send using csv format as I specified above. Right now it's using multi format. Is there a way to achieve this? I am doing something wrong?
I did something like this:
#ApiProperty({
isArray: true,
required: false,
enum: TestCaseFiltersStatuses,
})
#IsOptional()
#IsEnum(TestCaseFiltersStatuses, {each: true})
#IsArray()
#Transform((value) => {
if (typeof value == 'string') {
return value.split(',')
}
return value;
})
statuses?: TestCaseFiltersStatuses[];
.
.
.
async testCaseList(#Query(new ValidationPipe({transform: true})) query: TestCaseFiltersModel) {
...
}
that way this supports both ?params=1&params=2 and ?params=1,2

Resources