Files
Geradores/GeradoresWS/GeneratorEndpoints.cs
Marco Santos d7ea8a9b9e Reescrita dos geradores: biblioteca IGenerator, minimal API e novo frontend
- GeradoresService: cada tipo passa a ser um IGenerator (NIF, NIPC, NISS, CC,
  passaporte, IBAN, NIB, cartões, referência MB, pessoa, telefone, código postal,
  email, matrícula, EAN, UUID) registado em GeneratorRegistry; remove DAL/EF
- GeradoresWS: minimal APIs em /api/v1 com OpenAPI + Scalar e health check
- UnitTest: testes de ida e volta, valores conhecidos, detecção e API
- geradoresfe: UI construída a partir dos metadados da API, validador livre,
  exportação CSV/JSON/TXT, nginx com API_UPSTREAM configurável
- Docker, docker-compose e Jenkinsfile actualizados

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:51:39 +01:00

102 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using GeradoresService;
using GeradoresService.Core;
using Microsoft.AspNetCore.Http.HttpResults;
namespace GeradoresWS;
public static class GeneratorEndpoints
{
private static readonly HashSet<string> 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<Ok<GeneratorDto>, 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<Ok<ValidationResult>, 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<Ok<GenerateResponse>, 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<string, string[]> { [key] = [message] });
}
public sealed record GeneratorDto(
string Id,
string Name,
string Category,
string Description,
bool HasChecksum,
bool CanValidate,
IReadOnlyList<GeneratorOption> 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<GeneratedItem> Items);
public sealed record DetectMatchDto(string GeneratorId, string GeneratorName, ValidationResult Result);
public sealed record DetectResponse(string Value, IReadOnlyList<DetectMatchDto> Matches);