NestJS Swagger - How to declare multiselect enum field? - node.js

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

Related

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

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!

Why I'm getting Validation failed (numeric string is expected)

I have this code
#Get()
#ApiQuery({
name: "code",
type: String,
required: false,
})
#ApiQuery({
name: "id",
type: Number,
required: false,
})
async read(
#Query("entity") entity: string,
#Query("code") code: string,
#Query("id", ParseIntPipe) id: number
): Promise<Q> {
return this.service.readAsync({ where: { codigo: code, id: id } });
}
Why I'm getting Validation failed (numeric string is expected) when I request to http://localhost:3000/api/v1/endpoint?entity=a&code=b
I know is related with id param, but I don't know how to solve this.
I want to be able to use code or id params according my needs.
If I request to http://localhost:3000/api/v1/endpoint?entity=a&code=b&id=1 or http://localhost:3000/api/v1/endpoint?entity=a&id=1 all is fine.
here #Query("id", ParseIntPipe) id: number you're saying that the query parameter id is required and must be an integer.
Thus, if you do GET /endpoint?entity=a&code=b, it will reply with bad request as there's no id parameter.
You can use the DefaultValuePipe pipe if id should be optional and will have a fallback value.
If you don't want any default value, then you'll need to write your own pipe (that could extends ParseIntPipe). Or you could use the builtin one ValidationPipe with class-validator decorators.
ParserIntPipe doesn't work on optional parameters, from its source code, you can see
async transform(value: string, metadata: ArgumentMetadata): Promise<number> {
if (!this.isNumeric(value)) {
throw this.exceptionFactory(
'Validation failed (numeric string is expected)',
);
}
return parseInt(value, 10);
}
/**
* #returns `true` if `value` is a valid integer number
*/
protected isNumeric(value: string): boolean {
return (
['string', 'number'].includes(typeof value) &&
/^-?\d+$/.test(value) &&
isFinite(value as any)
);
}
As per Micael Levi answer, you either provide a default value using DefaultValuePipe in case it was missing, or you build your own custom pipe that pass parameter undefined value

How to use Get request with DTO in NestJs?

I am trying to write a function to handle the Get request, here is my code:
#Get('/find')
async find(#Param() testname: NameDto) {
console.log(testname.name);
}
Here is my dto:
export class NameDto {
#IsString()
#ApiProperty({ required: true })
name: string;
}
I am using the Swagger to test this API :
When I input a signle a , I got the following response:
{
"statusCode": 400,
"message": [
"name must be a string"
],
"error": "Bad Request"
}
here are more input example :
They all return the same response.
Then, I change the find function like this with #Query:
#Get('/find')
async find(#Query() testname: NameDto) {
console.log(testname.name);
}
here is my input :
I can have the 200-ok response.
Here is another example:
I enter 1 as the input, and I still can get the 200 response. The dto is not working as expected.
Am I missing something?
Any help would be appreciate.
you are sending nothing in param
you can read in Nestjs document
try this one:
#Get('/find/:testname')
async find(#Param('testname') testname: NameDto) {
console.log(testname.name);
}
You're using query parameters for the url, so you do need to use #Query(). Query parameters will always come in as strings, because that's how express and fastify work. You can enable transform: true and {transformOptions: { enableImplicitConverion: true } } in the ValidationPipe's options and get numbers to show up as actual numbers.
you can do something like this it's works for me:
//find-one.dto.ts
import { IsNotEmpty, IsUUID } from 'class-validator';
export class FindOneParams {
#IsUUID()
#IsNotEmpty()
uuid: string;
}
//controller.ts
#Get(':uuid')
find(#Param() { uuid }: FindOneParams) {
return this.yourService.find(uuid);
}
//service.ts
find = (uuid: string): Promise<yourType> => ...

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 '../..'

Sequelize is returning integer as string

I using nodejs v4 with sequelize, and I have a model like this:
var Device = sequelize.define('Device', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
},
tenantId: {
type: DataTypes.BIGINT,
allowNull: false
},
token: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'devices'
});
When I select a device by id the type of id is a string, exemple:
Device.findById(9).then( function(result) {
console.log(result.toJSON().id + 10);
});
The output will be 910, rather than 19, so I look at json and a saw this:
{
id: "9"
tenantId: "123"
token: "adsadsdsa"
}
The id in found device is a string, but I defined it as a number...
Doesn't it should be { "id": 9 } ?
How can I select a device with the types that I defined previously?
BIGINT maximum value is 2^63-1, javascript can safely represent up to 2^53. To be on the safe side libraries return those numbers as strings.
If you want to have numbers instead of strings, you can use this library https://github.com/mirek/node-pg-safe-numbers which deals with this issue.
I found a fix to this problem on sequelize repo.
https://github.com/sequelize/sequelize/issues/4523
The pg module used for sequelize returns bigint as string because bigints are not guaranteed to fit into js numbers. So I change my model to use integer (DataTypes.INTEGER)
IMO, a smoother solution is to force parsing of int to int, instead of strings. Checkout this issue and comments.
Trying to put this line before your logic code worked for me, this forces parsing of int8 to int, instead of strings:
require("pg").defaults.parseInt8 = true;
...
try {
const list = await Posts.findAll();
console.log(JSON.stringify(list));
} catch (e) {
console.log(e.message);
}
By default, sequelize try format your results. You can set raw :true for get raw data

Resources