using System.ComponentModel; using GeradoresService; using GeradoresService.Core; using Microsoft.AspNetCore.Http.HttpResults; namespace GeradoresWS; public static class GeneratorEndpoints { private static readonly HashSet ReservedQueryKeys = new(StringComparer.OrdinalIgnoreCase) { "count", "seed" }; public static void MapGeneratorEndpoints(this IEndpointRouteBuilder app) { var api = app.MapGroup("/api/v1").WithTags("Geradores"); api.MapGet("/generators", (GeneratorRegistry registry) => registry.All.Select(GeneratorDto.From).ToList()) .WithName("ListGenerators") .WithSummary("Lista todos os geradores e as respectivas opções."); api.MapGet("/generators/{id}", Results, NotFound> (string id, GeneratorRegistry registry) => registry.Find(id) is { } g ? TypedResults.Ok(GeneratorDto.From(g)) : TypedResults.NotFound()) .WithName("GetGenerator") .WithSummary("Detalhe de um gerador."); api.MapGet("/generators/{id}/generate", Generate) .WithName("Generate") .WithSummary("Gera um ou mais valores.") .WithDescription("As opções do gerador passam-se na query string, ex.: /generators/nif/generate?count=10&tipo=5"); api.MapGet("/generators/{id}/validate", Results, NotFound> (string id, [Description("Valor a validar")] string value, GeneratorRegistry registry) => registry.Find(id) is { CanValidate: true } g ? TypedResults.Ok(g.Validate(value)) : TypedResults.NotFound()) .WithName("Validate") .WithSummary("Valida um valor de um tipo específico."); api.MapGet("/validate", (string value, GeneratorRegistry registry) => new DetectResponse(value, registry.Detect(value) .Select(m => new DetectMatchDto(m.Generator.Id, m.Generator.Name, m.Result)) .ToList())) .WithName("Detect") .WithSummary("Validador livre: identifica o tipo do valor e diz se é válido."); } private static Results, NotFound, ValidationProblem> Generate( string id, HttpRequest request, GeneratorRegistry registry, [Description("Quantidade (1–1000)")] int count = 1, [Description("Seed para resultados reprodutíveis")] int? seed = null) { var generator = registry.Find(id); if (generator is null) return TypedResults.NotFound(); var options = request.Query .Where(q => !ReservedQueryKeys.Contains(q.Key)) .ToDictionary(q => q.Key, q => (string?)q.Value.ToString(), StringComparer.OrdinalIgnoreCase); var unknown = options.Keys.Where(k => generator.Options.All(o => !string.Equals(o.Key, k, StringComparison.OrdinalIgnoreCase))).ToList(); if (unknown.Count > 0) return Problem(unknown[0], $"Opção desconhecida. Opções válidas: {string.Join(", ", generator.Options.Select(o => o.Key))}."); if (count is < 1 or > GeneratorRegistry.MaxBatch) return Problem("count", $"A quantidade deve estar entre 1 e {GeneratorRegistry.MaxBatch}."); try { var items = registry.Generate(generator, count, options, seed); return TypedResults.Ok(new GenerateResponse(generator.Id, items)); } catch (ArgumentException ex) { return Problem("options", ex.Message); } } private static ValidationProblem Problem(string key, string message) => TypedResults.ValidationProblem(new Dictionary { [key] = [message] }); } public sealed record GeneratorDto( string Id, string Name, string Category, string Description, bool HasChecksum, bool CanValidate, IReadOnlyList Options) { public static GeneratorDto From(IGenerator g) => new(g.Id, g.Name, g.Category, g.Description, g.HasChecksum, g.CanValidate, g.Options); } public sealed record GenerateResponse(string Generator, IReadOnlyList Items); public sealed record DetectMatchDto(string GeneratorId, string GeneratorName, ValidationResult Result); public sealed record DetectResponse(string Value, IReadOnlyList Matches);