- 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>
102 lines
4.3 KiB
C#
102 lines
4.3 KiB
C#
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);
|