All files / libs/kernel/api/src/lib/pipes base-validation.pipe.ts

100% Statements 73/73
94.11% Branches 16/17
100% Functions 2/2
100% Lines 73/73

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 741x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 54x 54x 54x 54x 54x 54x 1x 1x 1x 1x 1x 1x 1x 1x 54x 3x 3x 2x 2x 1x 1x 1x 1x 1x 1x 1x 3x 51x 51x 51x 51x 51x 54x 22x 22x 54x 54x 54x 54x 54x 54x 54x 45x 45x 54x 13x 13x 38x 38x 38x 1x 1x 1x 1x 1x  
import { BadRequestException, Injectable, type ArgumentMetadata, type PipeTransform } from '@nestjs/common';
import { isNil, isPlainObject } from 'lodash-es';
 
import { assert } from '@amalia/ext/typescript';
 
export interface BaseOptions {
  optional?: boolean;
  multiple?: boolean;
}
 
@Injectable()
export abstract class BaseValidationPipe<TTransformedValue> implements PipeTransform<
  unknown,
  TTransformedValue | TTransformedValue[] | null | undefined
> {
  private readonly options: BaseOptions;
 
  public constructor(options?: BaseOptions) {
    this.options = {
      optional: false,
      multiple: false,
      ...options,
    };
  }
 
  /**
   * This method transforms the input into the returned value. It can
   * also throw BadRequestExceptions if the input is not in the correct format.
   * @param value
   * @param metadata
   */
  public transform(value: unknown, metadata: ArgumentMetadata) {
    if (this.options.optional) {
      // The value is null or undefined, return as-is.
      if (value === null || value === undefined) {
        return value;
      }
 
      // Special case for number, because NestJS try to convert them before
      // our pipe if the `number` type is used in the decorator. If the value
      // is undefined or null, we have NaN here (yes, it sucks).
      if (Number.isNaN(value)) {
        return null;
      }
    }
 
    assert(!isNil(value), new BadRequestException(`Validation failed: ${metadata.data}`));
 
    // Transform value(s).
    const transformedValue = this.options.multiple
      ? // When lists are too big they get converted to objects with numeric keys.
        (isPlainObject(value) ? (Object.values(value) as unknown[]) : ([] as unknown[]).concat(value)).map((elt) =>
          this.transformOne(elt),
        )
      : this.transformOne(value);
 
    // Check value(s).
    if (
      // Multiple mode.
      (this.options.multiple && (transformedValue as TTransformedValue[]).some((v) => !this.validateOne(v))) ||
      // Single mode.
      (!this.options.multiple && !this.validateOne(transformedValue as TTransformedValue))
    ) {
      throw new BadRequestException(`Validation failed: ${metadata.data}`);
    }
 
    return transformedValue;
  }
 
  public abstract transformOne(value: unknown): TTransformedValue;
 
  public abstract validateOne(value: TTransformedValue): boolean;
}