How to implement Fluent Validation with Mediatr And PipelineBehavior in Typescrit REST API like .NET does

77 views Asked by At

I'm using typescript with inversify, mediatr-ts, fluentvalidation-ts and i can´t implement a Fluent Validator abstract class where the pipeline behavior is done to validate commands.

Here is my command

import { ErrorOr } from "../../../../shared/domain/errors/ErrorOr";
import { AuthenticationResponse } from "../../AuthenticationResponse";
import { ICommand } from "../../../../shared/application/commands/ICommand";

export class RegisterCommand
  implements ICommand<ErrorOr<AuthenticationResponse>>
{
  constructor(readonly username: string, readonly password: string) {}
}

And here is my command Handler

import { UserRepository } from "../../../domain/UserRepository";
import { RegisterErrors } from "../../../domain/errors/RegisterErrors";
import { User } from "../../../domain/User";
import { UserId } from "../../../domain/value-objects/UserId";
import { Username } from "../../../domain/value-objects/Username";
import { Password } from "../../../domain/value-objects/Password";
import { AuthenticationResponse } from "../../AuthenticationResponse";
import { IJWTProvider } from "../../../domain/JWTProvider";
import { ErrorOr } from "../../../../shared/domain/errors/ErrorOr";
import { RegisterCommand } from "./RegisterCommand";
import { IRequestHandler, requestHandler } from "mediatr-ts";
import { inject, injectable } from "inversify";
import { constants } from "../../../../app/constants";

@requestHandler(RegisterCommand)
@injectable()
export class RegisterCommandHandler
  implements IRequestHandler<RegisterCommand, ErrorOr<AuthenticationResponse>>
{
  constructor(
    @inject(constants.UserRepository)
    private userRepository: UserRepository,
    @inject(constants.IJWTProvider) 
    private JWTProvider: IJWTProvider
  ) {}

  handle = async (command: RegisterCommand): Promise<ErrorOr<AuthenticationResponse>> => {
    if (await this.userRepository.findByUsername(command.username)) {
      return ErrorOr.failure(RegisterErrors.UserAlreadyExists);
    }

    const user = new User(
      UserId.generate(),
      new Username(command.username),
      Password.hash(command.password)
    );

    this.userRepository.save(user);

    const token = this.JWTProvider.generate(user.toPrimitives());

    return ErrorOr.success(new AuthenticationResponse(command.username, token));
  };
}

And my command validator

import { AsyncValidator } from "fluentvalidation-ts";
import { RegisterCommand } from "./RegisterCommand";
import { injectable } from "inversify";

@injectable()
export class RegisterCommandValidator extends AsyncValidator<RegisterCommand> {
  constructor() {
    super();

    this.ruleFor("username")
      .notEmpty()
      .withMessage("Username is required")
      .length(3, 20)
      .withMessage("Username must be between 3 and 20 characters")
      .matches(/^[a-zA-Z0-9]{3,20}$/gm)
      .withMessage("Username must contain only letters and numbers");

    this.ruleFor("password")
      .notEmpty()
      .withMessage("Password is required")
      .minLength(8)
      .withMessage("Password must be at least 8 characters")
      .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/gm)
      .withMessage(
        "Password must contain at least 1 uppercase letter, 1 lowercase letter, and 1 number. Can contain special characters"
      );
  }
}

This should work in the controller like this:

//AuthController.ts
@httpPost("/register")
  async register(@request() req: Request, @response() res: Response) {
    const { username, password } = req.body;
    const command = new RegisterCommand(username, password);
    const registerResult = await this.mediator.send<ApiResult>(command);

    if (registerResult.isError()) {
      return this.problem(registerResult.errors!, res);
    }

    return res.json({
      message: "You have been successfully registered.",
      ...registerResult.getValue(),
    });
  }

But instead of that i do this:

//AuthController.ts
class AuthController extends ApiController {
 @httpPost("/register")
  async register(@request() req: Request, @response() res: Response) {
    const { username, password } = req.body;
    const command = new RegisterCommand(username, password);

    const errors = await this.validate(
      new RegisterCommandValidator(),
      command,
      res
    );
    if (errors) return errors;

    const registerResult = await 
    this.mediator.send<ErrorOr<AuthenticationResponse>>(command);

    if (registerResult.isError()) {
      return this.problem(registerResult.errors!, res);
    }

    return res.json({
      message: "You have been successfully registered.",
      ...registerResult.getValue(),
    });
  }
}

// ApiController.ts
async validate<T>(
    validator: AsyncValidator<T>,
    command: T,
    res: Response
  ): Promise<Response | void> {
    const errors = await validator.validateAsync(command);

    if (Object.keys(errors).length > 0)
      return this.problem(JsonToValidationErrors(errors), res);
  }

But i cannot achieve an abstract class that uses @pipelineBehavior() decorator with generics so that i can use it with every command

0

There are 0 answers