routing-controllers icon indicating copy to clipboard operation
routing-controllers copied to clipboard

feature: <validate response use openapi ResponseSchema decorator>

Open lerit opened this issue 4 years ago • 3 comments

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 avatar Jul 06 '21 09:07 lerit

@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.

attilaorosz avatar Mar 04 '22 19:03 attilaorosz

It would be awesome if we could have this feature!

kenberkeley avatar May 20 '22 05:05 kenberkeley

How to validate response payload in routing-controllers?

My temp solution is overriding class methods with a method decorator.

  1. yarn add class-transformer-validator
  2. 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 })
  }
}
  1. Use @SerializeResponse along 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> {
    // ...
  }
}

kenberkeley avatar May 20 '22 13:05 kenberkeley