nestjsx / crud

NestJs CRUD for RESTful APIs
https://github.com/nestjsx/crud/wiki
MIT License
4.04k stars 534 forks source link

Validation on query filter #348

Open bovandersteene opened 4 years ago

bovandersteene commented 4 years ago

I have following url:

http://localhost:3333/api/products?filter=date||eq||2019-11-01

So far so good my executed query is:

SELECT DISTINCT ... WHERE "ProductEntity"."date" eq ?  -- PARAMETERS: ["2019-11-01T00:00:00.000Z"]

Now if I change my request to

http://localhost:3333/api/products?filter=date||eq||abc

My query is:

SELECT DISTINCT ... WHERE "ProductEntity"."date" eq ? -- PARAMETERS: ["abc"]

Now we got a problem, the provided filter value is not a date.

Also if I want to change my provided date inside the query to my own date format it is not possible

SELECT DISTINCT ... WHERE "ProductEntity"."date" eq ? -- PARAMETERS: ["2019-11-01"]
michaelyali commented 4 years ago

It's not related to the crud lib, because the crud request parser doesn't provide such validation. It's totally up to user to implement such validation

prevostc commented 4 years ago

Hi, here is how I did it. I extended the crud service to validate each field.

This is not the best solution but I believe this should be included in the crud-typeorm service. I'm not familiar with this repo development cycle, but if you need me to contribute, i'll be happy to.

import { TypeOrmCrudService } from "@nestjsx/crud-typeorm"
import { QueryFilter } from "@nestjsx/crud-request"
import { ObjectLiteral } from "typeorm"
import { ValidationError, validateSync } from "class-validator"
import { BadRequestException } from "@nestjs/common"
import { plainToClass } from "class-transformer"

export class ValidatingTypeOrmCrudService<T> extends TypeOrmCrudService<T> {
  /**
   * Add parameter value validation
   */
  protected mapOperatorsToQuery(cond: QueryFilter, param: any): { str: string; params: ObjectLiteral } {
    const errors = this.validateParamValues(cond, param)
    if (errors.length > 0) {
      throw new BadRequestException(errors)
    }
    return super.mapOperatorsToQuery(cond, param)
  }

  protected validateParamValues(cond: QueryFilter, param: any): ValidationError[] {
    // @TODO: handle undefined values
    if (cond.value === undefined) {
      return []
    }

    let errors: ValidationError[] = []
    switch (cond.operator) {
      case "$in":
        const paramArray = Array.isArray(cond.value) ? cond.value : [cond.value]
        for (const paramArrayValue of paramArray) {
          // validate each value using the "$eq" operator
          errors = errors.concat(this.validateParamValues({ ...cond, operator: "$eq", value: paramArrayValue }, param))
        }
        break
      case "$eq":
        // create an entity to be validated
        const dto = plainToClass(this.entityType, { [cond.field]: cond.value })
        // validate the entity
        errors = validateSync(dto)
        // only keep errors for the current field
        errors = errors.filter((err) => err.property === cond.field)
        break

      // @TODO: handle other operators
      /* istanbul ignore next */
      default:
        break
    }

    return errors
  }
}