All files / app/api/middlewares requestValidator.ts

100% Statements 61/61
92.3% Branches 12/13
100% Functions 4/4
100% Lines 61/61

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 621x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 10x 10x 10x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 10x 10x 10x 6x 6x 10x 10x 10x 1x 6x 6x 6x 6x 6x 6x  
import { Ajv, JSONSchemaType, ValidateFunction, ErrorObject } from 'ajv';
import { Request, Response, NextFunction } from 'express';
import { CODES } from '../../utils/errors/codes.js';
import { HttpError } from '../../utils/errors/httpError.js';
import { MESSAGES } from '../../utils/errors/messages.js';
 
export enum RequestSource {
  body = 'body',
  query = 'query',
  params = 'params',
}
 
export interface ValidateOptions {
  coerceTypes: boolean;
}
 
/**
 * Returns a callback to function as middleware that validates the current request against a given schema, store validated data in locals
 * @param schema - the schema to validate the request against
 * @param source - the source of validation: body, params, query (default: body)
 */
export function requestValidator<T>(
  schema: JSONSchemaType<T>,
  source = RequestSource.body,
  options: ValidateOptions = { coerceTypes: false },
) {
  return (request: Request, response: Response, next: NextFunction) => {
    const validateFunction = new Ajv(options).compile<T>(schema);
    validateRequestAgainstSchema<T>(request, response, next, validateFunction, source);
  };
}
 
/**
 * Validate a request against a {@link Joi.AnySchema}
 * @param request
 * @param response
 * @param next
 * @param schema
 * @param source
 */
function validateRequestAgainstSchema<T>(
  request: Request,
  response: Response,
  next: NextFunction,
  validate: ValidateFunction<T>,
  source: RequestSource,
) {
  const isValid = validate(request[source]);
  if (!isValid && validate.errors) {
    return next(new HttpError(parseErrorsMessages(validate.errors), CODES.BAD_REQUEST, 400));
  }
 
  return next();
}
 
function parseErrorsMessages(validationErrors: ErrorObject[]): string {
  const errorsMessages = validationErrors.map(
    (error) => `${error.instancePath ? error.instancePath + ' ' : ''}${error.message}`,
  );
  return errorsMessages?.length ? errorsMessages.join(', ') : MESSAGES.VALIDATION_ERROR;
}