CUB-30: Implement PUT /filaments/{id} update endpoint
- Add FluentValidation validators for CreateFilamentRequest and UpdateFilamentRequest with comprehensive validation rules (required fields, string lengths, hex color format, weight constraints including WeightRemainingGrams <= WeightTotalGrams, purchase price range) - Add FluentValidationFilter action filter that auto-runs FluentValidation validators for all API controller actions before execution, returning 400 with structured error details - Register FluentValidationFilter in DI and add it to MVC controller filters in Program.cs - PUT endpoint was already implemented in FilamentsController with proper validation, 404 handling, FK existence checks, serial uniqueness check, and weight constraint check - This change ensures FluentValidation rules are enforced consistently via the pipeline
This commit is contained in:
108
backend/API/Validators/FilamentValidators.cs
Normal file
108
backend/API/Validators/FilamentValidators.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Extrudex.API.DTOs.Filaments;
|
||||
using FluentValidation;
|
||||
|
||||
namespace Extrudex.API.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Validation rules for creating a Filament (Spool) via the /filaments route.
|
||||
/// Mirrors the domain rules enforced in the controller and ensures consistent
|
||||
/// validation regardless of the request pipeline entry point.
|
||||
/// </summary>
|
||||
public class CreateFilamentRequestValidator : AbstractValidator<CreateFilamentRequest>
|
||||
{
|
||||
public CreateFilamentRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.MaterialBaseId)
|
||||
.NotEmpty().WithMessage("MaterialBaseId is required.");
|
||||
|
||||
RuleFor(x => x.MaterialFinishId)
|
||||
.NotEmpty().WithMessage("MaterialFinishId is required.");
|
||||
|
||||
RuleFor(x => x.Brand)
|
||||
.NotEmpty().WithMessage("Brand is required.")
|
||||
.MaximumLength(200).WithMessage("Brand must not exceed 200 characters.");
|
||||
|
||||
RuleFor(x => x.ColorName)
|
||||
.NotEmpty().WithMessage("ColorName is required.")
|
||||
.MaximumLength(200).WithMessage("ColorName must not exceed 200 characters.");
|
||||
|
||||
RuleFor(x => x.ColorHex)
|
||||
.NotEmpty().WithMessage("ColorHex is required.")
|
||||
.Matches(@"^#[0-9A-Fa-f]{6}$").WithMessage("ColorHex must be a valid hex color code (e.g., #FF0000).");
|
||||
|
||||
RuleFor(x => x.WeightTotalGrams)
|
||||
.GreaterThan(0).WithMessage("Total weight must be greater than zero.");
|
||||
|
||||
RuleFor(x => x.WeightRemainingGrams)
|
||||
.GreaterThanOrEqualTo(0).WithMessage("Remaining weight must be non-negative.");
|
||||
|
||||
RuleFor(x => x.WeightRemainingGrams)
|
||||
.LessThanOrEqualTo(x => x.WeightTotalGrams)
|
||||
.WithMessage("WeightRemainingGrams cannot exceed WeightTotalGrams.");
|
||||
|
||||
RuleFor(x => x.FilamentDiameterMm)
|
||||
.GreaterThan(0).WithMessage("Filament diameter must be greater than zero.");
|
||||
|
||||
RuleFor(x => x.SpoolSerial)
|
||||
.NotEmpty().WithMessage("SpoolSerial is required.")
|
||||
.MaximumLength(200).WithMessage("SpoolSerial must not exceed 200 characters.");
|
||||
|
||||
When(x => x.PurchasePrice.HasValue, () =>
|
||||
{
|
||||
RuleFor(x => x.PurchasePrice!.Value)
|
||||
.GreaterThanOrEqualTo(0).WithMessage("Purchase price must be non-negative.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation rules for updating a Filament (Spool) via the /filaments route.
|
||||
/// Enforces the same domain rules as creation, plus ensures the updated
|
||||
/// WeightRemainingGrams does not exceed the updated WeightTotalGrams.
|
||||
/// </summary>
|
||||
public class UpdateFilamentRequestValidator : AbstractValidator<UpdateFilamentRequest>
|
||||
{
|
||||
public UpdateFilamentRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.MaterialBaseId)
|
||||
.NotEmpty().WithMessage("MaterialBaseId is required.");
|
||||
|
||||
RuleFor(x => x.MaterialFinishId)
|
||||
.NotEmpty().WithMessage("MaterialFinishId is required.");
|
||||
|
||||
RuleFor(x => x.Brand)
|
||||
.NotEmpty().WithMessage("Brand is required.")
|
||||
.MaximumLength(200).WithMessage("Brand must not exceed 200 characters.");
|
||||
|
||||
RuleFor(x => x.ColorName)
|
||||
.NotEmpty().WithMessage("ColorName is required.")
|
||||
.MaximumLength(200).WithMessage("ColorName must not exceed 200 characters.");
|
||||
|
||||
RuleFor(x => x.ColorHex)
|
||||
.NotEmpty().WithMessage("ColorHex is required.")
|
||||
.Matches(@"^#[0-9A-Fa-f]{6}$").WithMessage("ColorHex must be a valid hex color code (e.g., #FF0000).");
|
||||
|
||||
RuleFor(x => x.WeightTotalGrams)
|
||||
.GreaterThan(0).WithMessage("Total weight must be greater than zero.");
|
||||
|
||||
RuleFor(x => x.WeightRemainingGrams)
|
||||
.GreaterThanOrEqualTo(0).WithMessage("Remaining weight must be non-negative.");
|
||||
|
||||
RuleFor(x => x.WeightRemainingGrams)
|
||||
.LessThanOrEqualTo(x => x.WeightTotalGrams)
|
||||
.WithMessage("WeightRemainingGrams cannot exceed WeightTotalGrams.");
|
||||
|
||||
RuleFor(x => x.FilamentDiameterMm)
|
||||
.GreaterThan(0).WithMessage("Filament diameter must be greater than zero.");
|
||||
|
||||
RuleFor(x => x.SpoolSerial)
|
||||
.NotEmpty().WithMessage("SpoolSerial is required.")
|
||||
.MaximumLength(200).WithMessage("SpoolSerial must not exceed 200 characters.");
|
||||
|
||||
When(x => x.PurchasePrice.HasValue, () =>
|
||||
{
|
||||
RuleFor(x => x.PurchasePrice!.Value)
|
||||
.GreaterThanOrEqualTo(0).WithMessage("Purchase price must be non-negative.");
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user