Using Nestjs OpenAPI module, I can specify the response type using a decorator like so:
#Controller('app')
class MyController {
#Get()
#ApiResponse({ status: 200, description: 'Success', type: MyDto })
getThing() {
// code here
}
}
However, the documentation does not specify how to have a polymorphic response type (or in OpenAPI terms a oneOf response type). E.g., in the example below I would want to have my response type be oneOf: [MyDto, MyOtherDto]:
#Controller('app')
class MyController {
#Get()
#ApiResponse({ status: 200, description: 'Success', type: MyDto }) // What do do for type here?
getThing(): MyDto | MyOtherDto {
// code here
}
}
How can I do this?
Okay, so not sure if this is the best/easiest way, but a "raw schema definition" can be supplied for the #ApiResponse() decorator, like so:
#Controller('app')
class MyController {
#Get()
#ApiResponse({
status: 200,
description: 'Success',
schema: {
oneOf: [
{ $ref: getSchemaPath(MyDto) },
{ $ref: getSchemaPath(MyOtherDto) },
]
}
})
getThing(): MyDto | MyOtherDto {
// code here
}
}
In case your polymorphic class has a discriminator - you can take it one step further with the following code:
enum DtoType {
MY_DTO = 'MY_DTO',
MY_OTHER_DTO = 'MY_OTHER_DTO',
}
#Controller('app')
class MyController {
#Get()
#ApiResponse({
status: 200,
description: 'Success',
schema: {
oneOf: [
{ $ref: getSchemaPath(MyDto) },
{ $ref: getSchemaPath(MyOtherDto) }
],
discriminator: {
propertyName: 'type',
mapping: {
[DtoType.MY_DTO]: 'MyDto',
[DtoType.MY_OTHER_DTO]: 'MyOtherDto',
},
},
}
})
getThing(): MyDto | MyOtherDto {
// code here
}
}
Related
I'm trying to generate a controller method through NestJS' swagger decorators.
A method should return an array of mixed ClassA and ClassB types, and the only solution I could find that returns that kind of response is
#ApiResponse({
isArray: true,
schema: {
items: {
oneOf: [
{ $ref: getSchemaPath(ClassA) },
{ $ref: getSchemaPath(ClassB) },
]
}
}
})
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<ClassA | ClassB>>>;
but at the same time generates the following import:
import { ClassAClassB } from '../model/classAClassB';
without generating the class file.
What am I missing in the schema definition?
Thanks in advance
Any other configuration would return either a generated "mixed class type", but not in an array format
export type MixedClass = ClassA | ClassB
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<MixedClass>>;
or a whole wrong output
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<>>>
public generatedMethod(body: ..., observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>
In order to use other schemas, you need to add them using #ApiExtraModels at the top of the class.
#ApiExtraModels(ClassA, ClassB) // Add this decorator
#Controller()
export class MyController {
#ApiResponse({
status: 200, // => **is added**
isArray: true,
schema: {
items: {
oneOf: [
{ $ref: getSchemaPath(ClassA) },
{ $ref: getSchemaPath(ClassB) },
]
}
}
})
myMethod(#Body body) {
...
}
}
It sounds like a quite simple question but I've been searching for a solution for a very long time now. I want to validate an array of UUIDs in an endpoint.
Like this:
["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]
I have already successfully implemented it as a JSON object { "id": ["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]} with the following code:
public getIds(
#Body(ValidationPipe)
uuids: uuidDto
) {
console.log(uuids);
}
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: [String],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string;
}
But unfortunately I can't customize the function that calls that endpoint. So I need a solution to only validate a array of uuids.
instead of type string , write string[]. like below:
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: string[],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string[];
}
You can build a custom validation pipe for it:
#Injectable()
export class CustomClassValidatorArrayPipe implements PipeTransform {
constructor(private classValidatorFunction: (any)) {}
transform(value: any[], metadata: ArgumentMetadata) {
const errors = value.reduce((result, value, index) => {
if (!this.classValidatorFunction(value))
result.push(`${value} at index ${index} failed validation`)
return result
}, [])
if (errors.length > 0) {
throw new BadRequestException({
status: HttpStatus.BAD_REQUEST,
message: 'Validation failed',
errors
});
}
return value;
}
}
In your controller:
#Post()
createExample(#Body(new CustomClassValidatorArrayPipe(isUUID)) body: string[]) {
...
}
Ensure to use the lowercase functions from class-validator. It has to be isUUID instead of IsUUID. (This is used for the manual validation with class-validator.)
CustomClassValidatorArrayPipe is build modular. You can validate any other type with it. For example a MongoId: #Body(new CustomClassValidatorArrayPipe(isMongoId)) body: ObjectId[]
Result
If you send this:
POST http://localhost:3000/example
Content-Type: application/json
[
"986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e",
"123",
"test"
]
Server will reply:
{
"status": 400,
"message": "Validation failed",
"errors": [
"123 at index 1 failed validation",
"test at index 2 failed validation"
]
}
So I created a fastify route using fastify.route. But there is no way I could return a 201 statusCode in the response. And even if I return a 201 statusCode, it will be converted to a 200 one.
fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' }
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
¶
EDIT
The code that I was referring to was that of swagger
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
Here I want to replace 200 with a 201, and gives the required fields, but seems like that is not throwing an error as imagined
This is messing up my post request.
In NestJs I can not figure out how can get the validaiton error message from ValidationPipe. Always send generic response that includes no "message" parameter like this. :
{
"statusCode": 400,
"timestamp": "2021-05-22T09:59:27.708Z",
"path": "/batchs"
}
In main.ts I put the pipeline defination
app.useGlobalPipes(new ValidationPipe());
And this my dto
export class BatchCreateDto {
#ApiProperty()
#IsNotEmpty({ message: 'username is required' })
code: string;
#ApiProperty({ type: Lang })
title: Lang;
#ApiProperty({ type: Lang })
description: Lang;
#ApiProperty()
startingDate: Date;
#ApiProperty()
duraitonInHours: number;
}
If I send data with emty "code" field to api, the api returns generic Bad Request response that not includes message parameter. If the "code" field contains any data, there is no problem, api insert object to database. So I understand that validation pipe works, but why does not return any detailed message if validation fails.
I want to get the message parameter that includes something like "username is required" or any generic message. Thanks for help.
UPDATE
Body of the request that causes error
{
"code": "",
"title": {
"en": "this is test",
"tr": "bu testtir"
},
"description": {
"en": "some explination...",
"tr": "bazı açıklamalar..."
},
"startingDate": "2021-05-24T07:51:03.197Z",
"duraitonInHours": 14
}
BatchController uses BatchCreateDto
import { Body, Controller, Delete, Get, Param, Post, Put, Query, UsePipes, ValidationPipe } from '#nestjs/common';
import { BatchService } from '../services/batch.service';
import { ApiBody, ApiTags } from '#nestjs/swagger';
import { BatchCreateDto } from 'src/entities/dto/batch.create.dto';
#ApiTags('Batchs')
#Controller('batchs')
export class BatchController {
constructor(private batchService: BatchService) { }
#Post()
public async addBatch(#Body() batch: BatchCreateDto) {
const result = await this.batchService.add(batch);
return result;
}
}
I need to change the error response 404 error for exemple.
This is de case:
#Entity('role')
export class RoleEntity extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'varchar', length: 100, nullable: false })
name: string;
}
#Injectable()
export class RoleService extends TypeOrmCrudService<RoleEntity> {
constructor(#InjectRepository(RoleEntity) repo) {
super(repo);
}
}
#Crud({
model: {
type: RoleEntity,
},
})
#Controller('role')
export class RoleController implements CrudController<RoleEntity> {
constructor(public service: RoleService) {}
}
When i try to get an id not created in the base i got the error
{
"statusCode": 404,
"message": "RoleEntitynot found",
"error": "Not Found"
}
I want to change this error to something like this:
{
"success": false,
"message": "The given id was not found in the database",
}
What is the best way to do this?
Override nestjs/crud getOneBase method (Doc nestjs/crud)
#Override('getOneBase')
getOneAndDoStuff(
#ParsedRequest() req: CrudRequest,
) {
// Catch below error and return your message
this.base.getOneBase(req)
return ...;
}
You can wrap the base call in try catch statement like below, by this you can override errors object.
#Override()
async getOne(#ParsedRequest() req: CrudRequest) {
try {
return await this.base.getOneBase(req);
} catch (error) {
throw new HttpException(
{
returnCode: error.response.statusCode,
message: error.response.message,
type: ResponseTypes[error.response.statusCode],
},
error.status
);
}
}