using FluentValidation; using SatelliteProvider.Api.DTOs; namespace SatelliteProvider.Api.Validators; // AZ-811: FluentValidation rules for the query-string surface of // GET /api/satellite/tiles/latlon. Wired through // ValidationEndpointFilter at endpoint registration // time (.WithValidation() in Program.cs). // // Each rule maps 1:1 to a query parameter; errors[] keys are camelCase per // GlobalValidatorConfig (matching the wire-format param names `lat`, `lon`, // `zoom`). Required-field detection is `NotNull()` on the nullable-bound // DTO (see GetTileByLatLonQuery for why properties are nullable). Each rule // uses CascadeMode.Stop so a missing param surfaces ONLY as // "`lat` is required" — not also "`lat` must be between -90 and 90" with a // null value. Unknown query parameters are caught upstream by // RejectUnknownQueryParamsEndpointFilter. public sealed class GetTileByLatLonQueryValidator : AbstractValidator { private const double MinLat = -90.0; private const double MaxLat = 90.0; private const double MinLon = -180.0; private const double MaxLon = 180.0; private const int MinZoom = 0; private const int MaxZoom = 22; public GetTileByLatLonQueryValidator() { RuleFor(q => q.Lat) .Cascade(CascadeMode.Stop) .NotNull().WithMessage("`lat` is required.") .InclusiveBetween(MinLat, MaxLat).WithMessage($"`lat` must be between {MinLat} and {MaxLat}."); RuleFor(q => q.Lon) .Cascade(CascadeMode.Stop) .NotNull().WithMessage("`lon` is required.") .InclusiveBetween(MinLon, MaxLon).WithMessage($"`lon` must be between {MinLon} and {MaxLon}."); RuleFor(q => q.Zoom) .Cascade(CascadeMode.Stop) .NotNull().WithMessage("`zoom` is required.") .InclusiveBetween(MinZoom, MaxZoom).WithMessage($"`zoom` must be between {MinZoom} and {MaxZoom} (slippy-map range)."); } }