feature: <validate response use openapi ResponseSchema decorator>
Description
routing-controllers validate params,but not validate response. routing-controllers-openapi use ResponseSchema to describe response, but only for generate openapi,and not validate it. http://github.com/epiphone/routing-controllers-openapi/issues/36
Proposed solution
use ResponseSchema meta to validate response data.
handleSuccess(result, action, options) {
// if the action returned the response object itself, short-circuits
if (result && result === options.response) {
options.next();
return;
}
// transform result if needed
result = this.transformResult(result, action, options);
result = await this.validateResult(result); // todo:validate response
or,give an options such as responseValidator,let user to custom validate logic.
@lerit this seems like an interesting idea but I’m not sure if this should be the responsibility of this package. Since you are the one creating the response I think this should be handled on the service level. Other than that how do you imagine this validation error handled? It feels odd to throw an error after you successfully created a response.
It would be awesome if we could have this feature!
How to validate response payload in
routing-controllers?
My temp solution is overriding class methods with a method decorator.
-
yarn add class-transformer-validator - Copy this code snippet ⬇️
import type { ClassConstructor } from "class-transformer"
import { transformAndValidate } from "class-transformer-validator"
// Reference: https://medium.com/create-code/modify-a-method-using-typescript-decorators-686a57d2b7ec
export function SerializeResponse<T extends ClassConstructor<any>>(ResPayloadDto: T) {
return function (target: any, methodName: string, descriptor: PropertyDescriptor): void {
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]): Promise<T> {
const resPayload = await originalMethod.apply(this, args)
try {
const validatedResPayload = await transformAndValidate(
ResPayloadDto,
resPayload,
/*{
validator?: ValidatorOptions;
transformer?: ClassTransformOptions;
}*/
)
return validatedResPayload
} catch (e) {
console.error(
`Failed to serialize the response from ${target.constructor.name}#${methodName}`,
)
throw e
}
}
// Thanks to https://stackoverflow.com/a/40918734/5172890
Object.defineProperty(descriptor.value, "name", { value: methodName })
}
}
- Use
@SerializeResponsealong with@ResponseSchema(optional, not necessary to use together honestly):
@JsonController()
export class ProductController {
@Get("/products")
@ResponseSchema(ProductResPayload, { isArray: true })
@SerializeResponse(ProductResPayload)
public async getAllProducts(): Promise<ProductResPayload[]> {
// ...
}
@Post("/products")
@ResponseSchema(ProductResPayload)
@SerializeResponse(ProductResPayload)
public async createProduct(@Body() reqBody: CreateProductReqBody): Promise<ProductResPayload> {
// ...
}
}