namespace Application.Students.Commands; using System.Text.RegularExpressions; using Domain.Ports.Repositories; using FluentValidation; public partial class CreateStudentValidator : AbstractValidator { [GeneratedRegex(@"^[\p{L}\s\-'\.]+$", RegexOptions.Compiled)] private static partial Regex SafeNamePattern(); [GeneratedRegex(@"<[^>]*>|javascript:|on\w+=", RegexOptions.IgnoreCase | RegexOptions.Compiled)] private static partial Regex DangerousPattern(); public CreateStudentValidator(IStudentRepository studentRepository) { RuleFor(x => x.Name) .NotEmpty().WithMessage("Name is required") .MinimumLength(3).WithMessage("Name must be at least 3 characters") .MaximumLength(100).WithMessage("Name must not exceed 100 characters") .Matches(SafeNamePattern()).WithMessage("Name contains invalid characters") .Must(NotContainDangerousContent).WithMessage("Name contains prohibited content"); RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is required") .MaximumLength(254).WithMessage("Email must not exceed 254 characters") .EmailAddress().WithMessage("Invalid email format") .Must(NotContainDangerousContent).WithMessage("Email contains prohibited content") .MustAsync(async (email, ct) => !await studentRepository.EmailExistsAsync(email, null, ct)) .WithMessage("Email already exists"); } private static bool NotContainDangerousContent(string? value) => string.IsNullOrEmpty(value) || !DangerousPattern().IsMatch(value); } public partial class UpdateStudentValidator : AbstractValidator { [GeneratedRegex(@"^[\p{L}\s\-'\.]+$", RegexOptions.Compiled)] private static partial Regex SafeNamePattern(); [GeneratedRegex(@"<[^>]*>|javascript:|on\w+=", RegexOptions.IgnoreCase | RegexOptions.Compiled)] private static partial Regex DangerousPattern(); public UpdateStudentValidator(IStudentRepository studentRepository) { RuleFor(x => x.Id) .GreaterThan(0).WithMessage("Invalid student ID"); RuleFor(x => x.Name) .NotEmpty().WithMessage("Name is required") .MinimumLength(3).WithMessage("Name must be at least 3 characters") .MaximumLength(100).WithMessage("Name must not exceed 100 characters") .Matches(SafeNamePattern()).WithMessage("Name contains invalid characters") .Must(NotContainDangerousContent).WithMessage("Name contains prohibited content"); RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is required") .MaximumLength(254).WithMessage("Email must not exceed 254 characters") .EmailAddress().WithMessage("Invalid email format") .Must(NotContainDangerousContent).WithMessage("Email contains prohibited content") .MustAsync(async (command, email, ct) => !await studentRepository.EmailExistsAsync(email, command.Id, ct)) .WithMessage("Email already exists"); } private static bool NotContainDangerousContent(string? value) => string.IsNullOrEmpty(value) || !DangerousPattern().IsMatch(value); }