academia/src/backend/Application/Students/Commands/CreateStudentValidator.cs

73 lines
3.3 KiB
C#
Raw Normal View History

namespace Application.Students.Commands;
using Application.Common;
using Domain.Ports.Repositories;
using FluentValidation;
/// <summary>
/// Validator for <see cref="CreateStudentCommand"/>.
/// Validates name format, email uniqueness, and prevents XSS attacks.
/// </summary>
public class CreateStudentValidator : AbstractValidator<CreateStudentCommand>
{
public CreateStudentValidator(IStudentRepository studentRepository)
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required")
.MinimumLength(ValidationPatterns.MinNameLength)
.WithMessage($"Name must be at least {ValidationPatterns.MinNameLength} characters")
.MaximumLength(ValidationPatterns.MaxNameLength)
.WithMessage($"Name must not exceed {ValidationPatterns.MaxNameLength} characters")
.Matches(ValidationPatterns.SafeNamePattern())
.WithMessage("Name contains invalid characters")
.Must(ValidationPatterns.IsSafeContent)
.WithMessage("Name contains prohibited content");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.MaximumLength(ValidationPatterns.MaxEmailLength)
.WithMessage($"Email must not exceed {ValidationPatterns.MaxEmailLength} characters")
.EmailAddress().WithMessage("Invalid email format")
.Must(ValidationPatterns.IsSafeContent)
.WithMessage("Email contains prohibited content")
.MustAsync(async (email, ct) =>
!await studentRepository.EmailExistsAsync(email, null, ct))
.WithMessage("Email already exists");
}
}
/// <summary>
/// Validator for <see cref="UpdateStudentCommand"/>.
/// Validates name format, email uniqueness (excluding current student), and prevents XSS attacks.
/// </summary>
public class UpdateStudentValidator : AbstractValidator<UpdateStudentCommand>
{
public UpdateStudentValidator(IStudentRepository studentRepository)
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage(ValidationPatterns.InvalidIdMessage);
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required")
.MinimumLength(ValidationPatterns.MinNameLength)
.WithMessage($"Name must be at least {ValidationPatterns.MinNameLength} characters")
.MaximumLength(ValidationPatterns.MaxNameLength)
.WithMessage($"Name must not exceed {ValidationPatterns.MaxNameLength} characters")
.Matches(ValidationPatterns.SafeNamePattern())
.WithMessage("Name contains invalid characters")
.Must(ValidationPatterns.IsSafeContent)
.WithMessage("Name contains prohibited content");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.MaximumLength(ValidationPatterns.MaxEmailLength)
.WithMessage($"Email must not exceed {ValidationPatterns.MaxEmailLength} characters")
.EmailAddress().WithMessage("Invalid email format")
.Must(ValidationPatterns.IsSafeContent)
.WithMessage("Email contains prohibited content")
.MustAsync(async (command, email, ct) =>
!await studentRepository.EmailExistsAsync(email, command.Id, ct))
.WithMessage("Email already exists");
}
}