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