Files
Geradores/GeradoresService/GeneratorRegistry.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

86 lines
2.9 KiB
C#

using GeradoresService.Core;
using GeradoresService.Generators.Contactos;
using GeradoresService.Generators.Financeiro;
using GeradoresService.Generators.Identificacao;
using GeradoresService.Generators.Outros;
namespace GeradoresService;
public sealed record DetectionMatch(IGenerator Generator, ValidationResult Result);
public sealed class GeneratorRegistry
{
public const int MaxBatch = 1000;
public IReadOnlyList<IGenerator> All { get; }
public GeneratorRegistry() : this(
[
new NifGenerator(),
new NipcGenerator(),
new NissGenerator(),
new CartaoCidadaoGenerator(),
new PassaporteGenerator(),
new IbanGenerator(),
new NibGenerator(),
new CartaoCreditoGenerator(),
new ReferenciaMultibancoGenerator(),
new PessoaGenerator(),
new TelefoneGenerator(),
new CodigoPostalGenerator(),
new EmailGenerator(),
new MatriculaGenerator(),
new EanGenerator(),
new UuidGenerator(),
])
{ }
public GeneratorRegistry(IReadOnlyList<IGenerator> generators) => All = generators;
public IGenerator? Find(string id) =>
All.FirstOrDefault(g => string.Equals(g.Id, id, StringComparison.OrdinalIgnoreCase));
/// <summary>Gera <paramref name="count"/> valores. Um <paramref name="seed"/> torna o resultado reprodutível.</summary>
public IReadOnlyList<GeneratedItem> Generate(IGenerator generator, int count, IReadOnlyDictionary<string, string?> options, int? seed = null)
{
if (count is < 1 or > MaxBatch)
throw new ArgumentOutOfRangeException(nameof(count), $"A quantidade deve estar entre 1 e {MaxBatch}.");
var context = new GeneratorContext(seed is { } s ? new Random(s) : Random.Shared, options);
var items = new GeneratedItem[count];
for (var i = 0; i < count; i++)
items[i] = generator.Generate(context);
return items;
}
/// <summary>
/// Validador livre: tenta todos os tipos. Devolve primeiro os válidos, depois os que têm o formato
/// certo mas falham o controlo (ex.: "parece um NIF mas o dígito está errado").
/// </summary>
public IReadOnlyList<DetectionMatch> Detect(string value)
{
if (string.IsNullOrWhiteSpace(value))
return [];
return All
.Where(g => g.CanValidate)
.Select(g => new DetectionMatch(g, SafeValidate(g, value)))
.Where(m => m.Result.FormatMatch)
.OrderByDescending(m => m.Result.IsValid)
.ThenByDescending(m => m.Generator.HasChecksum)
.ToList();
}
private static ValidationResult SafeValidate(IGenerator generator, string value)
{
try
{
return generator.Validate(value);
}
catch (Exception ex) when (ex is FormatException or ArgumentException or OverflowException)
{
return ValidationResult.WrongFormat(ex.Message);
}
}
}