@nestjs/common#PipeTransform TypeScript Examples

The following examples show how to use @nestjs/common#PipeTransform. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: parse-object-id.pipe.ts    From nest-js-boilerplate with MIT License 6 votes vote down vote up
@Injectable()
export default class ParseObjectIdPipe implements PipeTransform<any, ObjectId> {
  public transform(value: string): ObjectId {
    try {
      return ObjectId.createFromHexString(value);
    } catch (error) {
      throw new BadRequestException('Validation failed (ObjectId is expected)');
    }
  }
}
Example #2
Source File: parse-date.pipe.ts    From aqualink-app with MIT License 6 votes vote down vote up
@Injectable()
export class ParseDatePipe implements PipeTransform {
  transform(value: string | undefined, metadata: ArgumentMetadata) {
    if (!isUndefined(value) && !isISO8601(value)) {
      throw new BadRequestException(`Date '${metadata.data}' is not valid`);
    }

    return value;
  }
}
Example #3
Source File: parse-hashed-id.pipe.ts    From aqualink-app with MIT License 6 votes vote down vote up
@Injectable()
export class ParseHashedIdPipe implements PipeTransform {
  constructor(private allowIntIds: boolean = false) {}

  transform(value: string) {
    if (this.allowIntIds && isValidId(value)) {
      return parseInt(value, 10);
    }
    const id = idFromHash(value);
    if (typeof id === 'undefined') {
      throw new NotFoundException(`Entity with id ${value} not found.`);
    }
    return id;
  }
}
Example #4
Source File: OffsetToPagerPipe.ts    From test with BSD 3-Clause "New" or "Revised" License 6 votes vote down vote up
@Injectable()
export class OffsetToPagerPipe implements PipeTransform {
  constructor(private readonly take: number = two) {}

  transform(offset: number) {
    return {
      take: this.take
      offset
    },
  }
}
Example #5
Source File: stripUndefined.pipe.ts    From office-hours with GNU General Public License v3.0 6 votes vote down vote up
/**
 * Strip undefined properties from body.
 */
@Injectable()
export class StripUndefinedPipe implements PipeTransform {
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  transform(value: any, metadata: ArgumentMetadata): any {
    if (metadata.type === 'body') {
      this.dropUndefined(value);
      return value;
    }
    return value;
  }

  private dropUndefined(obj: unknown) {
    for (const key of Object.keys(obj)) {
      if (obj[key] === undefined) {
        delete obj[key];
      } else if (typeof obj[key] === 'object' && obj[key] !== null) {
        this.dropUndefined(obj[key]);
      }
    }
  }
}
Example #6
Source File: validation.pipe.ts    From nestjs-starter with MIT License 6 votes vote down vote up
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value, metadata: ArgumentMetadata) {
    if (!value) {
      throw new BadRequestException('No data submitted');
    }

    const { metatype } = metadata;
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException(this.buildError(errors), 'Input data validation failed');
    }
    return value;
  }

  private buildError(errors) {
    const result = {};
    errors.forEach((el) => {
      const PROP = el.property;
      Object.entries(el.constraints).forEach((constraint) => {
        result[PROP + constraint[0]] = `${constraint[1]}`;
      });
    });
    return result;
  }

  private toValidate(metatype): boolean {
    const types = [String, Boolean, Number, Array, Object];
    return !types.find((type) => metatype === type);
  }
}
Example #7
Source File: user.by.id.pipe.ts    From api with GNU Affero General Public License v3.0 6 votes vote down vote up
@Injectable()
export class UserByIdPipe implements PipeTransform<string, Promise<UserEntity>> {
  constructor(
    private readonly userService: UserService,
    private readonly idService: IdService,
  ) {
  }

  async transform(value: string, metadata: ArgumentMetadata): Promise<UserEntity> {
    const id = this.idService.decode(value)

    return await this.userService.findById(id)
  }
}
Example #8
Source File: submission.by.id.pipe.ts    From api with GNU Affero General Public License v3.0 6 votes vote down vote up
@Injectable()
export class SubmissionByIdPipe implements PipeTransform<string, Promise<SubmissionEntity>> {
  constructor(
    private readonly submissionService: SubmissionService,
    private readonly idService: IdService,
  ) {
  }

  async transform(value: string, metadata: ArgumentMetadata): Promise<SubmissionEntity> {
    const id = this.idService.decode(value)

    return await this.submissionService.findById(id)
  }
}
Example #9
Source File: form.by.id.pipe.ts    From api with GNU Affero General Public License v3.0 6 votes vote down vote up
@Injectable()
export class FormByIdPipe implements PipeTransform<string, Promise<FormEntity>> {
  constructor(
    private readonly formService: FormService,
    private readonly idService: IdService,
  ) {
  }

  async transform(value: string, metadata: ArgumentMetadata): Promise<FormEntity> {
    const id = this.idService.decode(value)

    return await this.formService.findById(id)
  }
}
Example #10
Source File: validation.pipe.ts    From postgres-nest-react-typescript-boilerplate with GNU General Public License v3.0 6 votes vote down vote up
@Injectable()
export class ValidationPipe implements PipeTransform {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length) {
      console.log(this.formatErrors(errors));
      throw new HttpException(
        `Validation error :  ${this.formatErrors(errors)}`,
        HttpStatus.BAD_REQUEST,
      );
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.find(type => metatype === type);
  }

  private formatErrors = (errors: any[]) => {
    return errors
      .map((error: any) => {
        for (let property in error.constraints) {
          return error.constraints[property];
        }
      })
      .join(', ');
  };
}
Example #11
Source File: user-by-id.pipe.ts    From nestjs-mercurius with MIT License 6 votes vote down vote up
@Injectable()
export class UserByIdPipe implements PipeTransform<string> {
  static COUNTER = 0;
  static REQUEST_SCOPED_DATA = [];

  constructor(
    @Inject('REQUEST_ID') private requestId: number,
    private readonly usersService: UsersService,
  ) {
    UserByIdPipe.COUNTER++;
  }

  transform(value: string, metadata: ArgumentMetadata) {
    UserByIdPipe.REQUEST_SCOPED_DATA.push(this.requestId);
    return this.usersService.findById(value);
  }
}
Example #12
Source File: int-is-safe-for-prisma.pipe.ts    From ironfish-api with Mozilla Public License 2.0 6 votes vote down vote up
@Injectable()
export class IntIsSafeForPrismaPipe implements PipeTransform<string> {
  transform(value: string): number {
    const isNumeric =
      ['string', 'number'].includes(typeof value) &&
      /^-?\d+$/.test(value) &&
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      isFinite(value as any);
    if (!isNumeric) {
      throw new UnprocessableEntityException(
        'Validation failed (numeric string is expected)',
      );
    }
    const parsed = parseInt(value, 10);
    assertValueIsSafeForPrisma(parsed);
    return parsed;
  }
}
Example #13
Source File: parse-object-id.pipe.ts    From nestjs-rest-sample with GNU General Public License v3.0 6 votes vote down vote up
@Injectable()
export class ParseObjectIdPipe implements PipeTransform<string, string> {
  transform(value: string, metadata: ArgumentMetadata) {
    if (!mongoose.isValidObjectId(value)) {
      throw new BadRequestException(`$value is not a valid mongoose object id`);
    }
    return value;
  }
}
Example #14
Source File: router-execution-context.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
getParamValue<T>(value: T, { metatype, type, data, }: {
        metatype: unknown;
        type: RouteParamtypes;
        data: unknown;
    }, pipes: PipeTransform[]): Promise<unknown>;
Example #15
Source File: router-execution-context.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
createPipesFn(pipes: PipeTransform[], paramsOptions: (ParamProperties & {
        metatype?: any;
    })[]): <TRequest, TResponse>(args: any[], req: TRequest, res: TResponse, next: Function) => Promise<void>;
Example #16
Source File: nest-application.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
useGlobalPipes(...pipes: PipeTransform<any>[]): this;
Example #17
Source File: application-config.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
getGlobalRequestPipes(): InstanceWrapper<PipeTransform>[];
Example #18
Source File: application-config.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
addGlobalRequestPipe(wrapper: InstanceWrapper<PipeTransform>): void;
Example #19
Source File: application-config.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
getGlobalPipes(): PipeTransform<any>[];
Example #20
Source File: application-config.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
useGlobalPipes(...pipes: PipeTransform<any>[]): void;
Example #21
Source File: application-config.d.ts    From nest-jaeger with MIT License 5 votes vote down vote up
addGlobalPipe(pipe: PipeTransform<any>): void;
Example #22
Source File: joi.pipe.ts    From nestjs-joi with MIT License 4 votes vote down vote up
@Injectable({ scope: Scope.REQUEST })
export class JoiPipe implements PipeTransform {
  private readonly schema?: Joi.Schema;
  private readonly type?: Constructor;
  private readonly method?: string;
  private readonly pipeOpts: ValidatedJoiPipeOptions;

  constructor();
  constructor(pipeOpts?: JoiPipeOptions);
  constructor(type: Constructor, pipeOpts?: JoiPipeOptions);
  constructor(schema: Joi.Schema, pipeOpts?: JoiPipeOptions);
  constructor(
    @Inject(REQUEST) private readonly arg?: unknown,
    @Optional() @Inject(JOIPIPE_OPTIONS) pipeOpts?: JoiPipeOptions,
  ) {
    if (arg) {
      // Test for an actual request object, which indicates we're in "injected" mode.
      // This is the case that requires the most performant handling, which is why it
      // should be the first branch.
      if (isHttpRequest(arg)) {
        this.method = arg.method.toUpperCase();
      } else if (isGraphQlRequest(arg)) {
        // @nestjs/graphql, or rather apollo, only supports GET and POST.
        // To provide a consistent experience without hard to understand behavior
        // such as UPDATE group schemas being ignored, we will NOT set the method.
        // JoiPipe will work for this case, but ignore the method.
        // this.method = arg.req.method.toUpperCase();
      } else {
        // This is the "manually called constructor" case, where performance is
        // (ostensibly) not as big of a concern since manual JoiPipes will be
        // constructed only once at app initialization
        if (Joi.isSchema(arg)) {
          this.schema = arg as Joi.Schema;
        } else if (typeof arg === 'function') {
          this.type = arg as Constructor;
        } else {
          // Options passed as first parameter
          pipeOpts = arg as JoiPipeOptions;
        }
      }
    } else {
      // Called without arguments, do nothing
    }

    this.pipeOpts = this.parseOptions(pipeOpts);
  }

  transform(payload: unknown, metadata: ArgumentMetadata): unknown {
    const schema = this.getSchema(metadata);

    if (!schema) {
      // This happens when a metatype was passed by NestJS and it has no
      // validation decoration.
      return payload;
    }

    return JoiPipe.validate(
      payload,
      schema,
      this.pipeOpts.usePipeValidationException,
      this.pipeOpts.skipErrorFormatting,
      // It is technically impossible for this to be undefined since it is explicitely assigned
      // with a default value in parseOptions(), so it is almost impossible to test.
      /* istanbul ignore next */
      this.pipeOpts.defaultValidationOptions || DEFAULT_JOI_OPTS,
      metadata,
    );
  }

  // Called "validate" and NOT "transform", because that would make it match
  // the interface of a pipe INSTANCE and prevent NestJS from recognizing it as
  // a class constructor instead of an instance.
  private static validate<T>(
    payload: unknown,
    schema: Joi.Schema,
    usePipeValidationException: boolean,
    skipErrorFormatting: boolean,
    validationOptions: Joi.ValidationOptions,
    /* istanbul ignore next */
    metadata: ArgumentMetadata = { type: 'custom' },
  ): T {
    const { error, value } = schema.validate(
      payload,
      // This will always get overridden by whatever options have been specified
      // on the schema itself
      validationOptions,
    );

    if (error) {
      // Fixes #4
      if (Joi.isError(error)) {
        // Provide a special response with reasons
        const reasons = error.details
          .map((detail: { message: string }) => detail.message)
          .join(', ');
        const formattedMessage =
          `Request validation of ${metadata.type} ` +
          (metadata.data ? `item '${metadata.data}' ` : '') +
          `failed, because: ${reasons}`;
        if (usePipeValidationException) {
          throw new JoiPipeValidationException(
            skipErrorFormatting ? error.message : formattedMessage,
          );
        } else {
          throw new BadRequestException(skipErrorFormatting ? error : formattedMessage);
        }
      } else {
        // If error is not a validation error, it is probably a custom error thrown by the schema.
        // Pass it through to allow it to be caught by custom error handlers.
        throw error;
      }
    }

    // Everything is fine
    return value as T;
  }

  /**
   * Efficient validation of pipeOpts
   *
   * @param pipeOpts Pipe options as passed in the constructor
   * @returns Validated options with default values applied
   */
  private parseOptions(pipeOpts?: JoiPipeOptions): ValidatedJoiPipeOptions {
    // Pass type arguments to force type validation of the function arguments.
    pipeOpts = Object.assign<JoiPipeOptions, JoiPipeOptions>(
      {
        ...DEFAULT_JOI_PIPE_OPTS,
      },
      pipeOpts || {},
    );
    // Nested merge of the Joi ValidationOptions.
    // TODO This could probably be combined with the above assignment in a deep merge.
    pipeOpts.defaultValidationOptions = Object.assign<Joi.ValidationOptions, Joi.ValidationOptions>(
      {
        ...DEFAULT_JOI_OPTS,
      },
      pipeOpts.defaultValidationOptions || {},
    );

    const errors: string[] = [];

    const unknownKeys = Object.keys(pipeOpts).filter(k => !JOI_PIPE_OPTS_KEYS.includes(k));
    if (unknownKeys.length) {
      errors.push(`Unknown configuration keys: ${unknownKeys.join(', ')}`);
    }
    if (
      pipeOpts.group &&
      !(typeof pipeOpts.group === 'string' || typeof pipeOpts.group === 'symbol')
    ) {
      errors.push(`'group' must be a string or symbol`);
    }
    if (
      Object.prototype.hasOwnProperty.call(pipeOpts, 'usePipeValidationException') &&
      !(typeof pipeOpts.usePipeValidationException === 'boolean')
    ) {
      errors.push(`'usePipeValidationException' must be a boolean`);
    }
    if (
      Object.prototype.hasOwnProperty.call(pipeOpts, 'skipErrorFormatting') &&
      !(typeof pipeOpts.skipErrorFormatting === 'boolean')
    ) {
      errors.push(`'skipErrorFormatting' must be a boolean`);
    }

    if (errors.length) {
      throw new Error(`Invalid JoiPipeOptions:\n${errors.map(x => `- ${x}`).join('\n')}`);
    }

    return pipeOpts as ValidatedJoiPipeOptions;
  }

  /**
   * Determine what schema to return/construct based on the actual metadata
   * passed for this request.
   *
   * @param metadata
   */
  private getSchema(metadata: ArgumentMetadata): Joi.Schema | undefined {
    // If called in request scope mode, give preference to this mode.
    if (this.method && metadata.metatype) {
      let group: JoiValidationGroup | undefined;
      if (this.method === 'PUT' || this.method === 'PATCH') {
        group = JoiValidationGroups.UPDATE;
      } else if (this.method === 'POST') {
        group = JoiValidationGroups.CREATE;
      }

      return JoiPipe.getTypeSchema(metadata.metatype, { group });
    }

    // Prefer a static schema, if specified
    if (this.schema) {
      return this.schema;
    }

    // Prefer a static model, if specified
    if (this.type) {
      // Don't return "no schema" (undefined) if a type was explicitely specified
      return JoiPipe.getTypeSchema(this.type, { forced: true, group: this.pipeOpts.group });
    }

    // Determine the schema from the passed model
    if (metadata.metatype) {
      return JoiPipe.getTypeSchema(metadata.metatype, { group: this.pipeOpts.group });
    }

    return undefined;
  }

  /**
   * Cache map for already constructed schemas
   */
  private static readonly typeSchemaMap = new Map<unknown, Map<string, Joi.Schema | undefined>>();

  /**
   * Obtain the type schema from getTypeSchema().
   * The result is cached for better performance, using the type and options to
   * construct a cache key.
   *
   * If no properties have been decorated in the whole chain, no schema
   * will be returned so as to not throw when inadvertently "validating" using
   * inbuilt types.
   * Returning a schema can be forced to cover cases when a type has been explicitely
   * passed to JoiPipe.
   *
   * @param type The type (decorated class constructor) to construct a schema from
   * @param options An optional options object
   * @param options.forced Force an empty schema to be returned even if no properties of the type have been decorated. (defaults to false)
   * @param options.group An optional JoiValidationGroup to use during schema construction
   */
  private static getTypeSchema(
    type: Constructor,
    /* istanbul ignore next */
    { forced, group }: { forced?: boolean; group?: JoiValidationGroup } = {},
  ): Joi.Schema | undefined {
    // Do not validate basic inbuilt types. This is fast enough that we don't need
    // to cache the result.
    if (type === String || type === Object || type === Number || type === Array) {
      return;
    }

    const cacheKey = 'forced' + (forced ? '1' : '0') + (group ? 'group' + String(group) : '');
    // Check cache.
    if (this.typeSchemaMap.has(type)) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      if (this.typeSchemaMap.get(type)!.has(cacheKey)) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        return this.typeSchemaMap.get(type)!.get(cacheKey);
      }
    } else {
      this.typeSchemaMap.set(type, new Map());
    }

    const typeSchema = getTypeSchema(type, { group });

    // The default behavior when no properties have attached schemas is to not
    // validate, otherwise we'd get errors for types with no decorators not intended
    // to be validated when used as global pipe
    if ((!typeSchema || !Object.keys(typeSchema.describe().keys).length) && !forced) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      this.typeSchemaMap.get(type)!.set(cacheKey, undefined);
      return undefined;
    }

    // It is assumed that a validation schema was specified because the input value
    // as such is actually required, so we're calling required() here.
    // This might be subject to change if actual use cases are found.
    const finalSchema = typeSchema.required();

    // Cache value
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    this.typeSchemaMap.get(type)!.set(cacheKey, finalSchema);

    return finalSchema;
  }
}