- 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>
73 lines
2.8 KiB
C#
73 lines
2.8 KiB
C#
namespace GeradoresService.Core;
|
|
|
|
public sealed record GeneratorOption(
|
|
string Key,
|
|
string Label,
|
|
IReadOnlyList<OptionChoice> Choices,
|
|
string DefaultValue);
|
|
|
|
public sealed record OptionChoice(string Value, string Label);
|
|
|
|
/// <summary>
|
|
/// Valor gerado. <see cref="Fields"/> é usado por geradores compostos (ex.: pessoa fictícia).
|
|
/// </summary>
|
|
public sealed record GeneratedItem(string Value, IReadOnlyDictionary<string, string>? Fields = null);
|
|
|
|
/// <param name="IsValid">O valor é válido.</param>
|
|
/// <param name="FormatMatch">O valor tem o formato deste tipo (mesmo que o dígito de controlo falhe).</param>
|
|
/// <param name="Message">Resumo legível.</param>
|
|
/// <param name="Normalized">Valor normalizado/formatado, quando válido.</param>
|
|
/// <param name="Details">Passos do cálculo / informação extra.</param>
|
|
public sealed record ValidationResult(
|
|
bool IsValid,
|
|
bool FormatMatch,
|
|
string Message,
|
|
string? Normalized = null,
|
|
IReadOnlyList<string>? Details = null)
|
|
{
|
|
public static ValidationResult Valid(string normalized, string message, IReadOnlyList<string>? details = null) =>
|
|
new(true, true, message, normalized, details);
|
|
|
|
public static ValidationResult WrongFormat(string message) =>
|
|
new(false, false, message);
|
|
|
|
public static ValidationResult BadChecksum(string message, IReadOnlyList<string>? details = null) =>
|
|
new(false, true, message, null, details);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contexto de geração: fonte de aleatoriedade e opções escolhidas pelo utilizador.
|
|
/// </summary>
|
|
public sealed class GeneratorContext(Random random, IReadOnlyDictionary<string, string?> options)
|
|
{
|
|
public Random Random { get; } = random;
|
|
|
|
public static GeneratorContext Default(Random? random = null) =>
|
|
new(random ?? Random.Shared, new Dictionary<string, string?>());
|
|
|
|
/// <summary>
|
|
/// Devolve o valor da opção ou o default. O valor tem de ser uma das escolhas válidas.
|
|
/// </summary>
|
|
public string Option(GeneratorOption option)
|
|
{
|
|
if (options.TryGetValue(option.Key, out var value) && !string.IsNullOrWhiteSpace(value))
|
|
{
|
|
var match = option.Choices.FirstOrDefault(c => string.Equals(c.Value, value, StringComparison.OrdinalIgnoreCase));
|
|
if (match is null)
|
|
throw new ArgumentException($"Valor '{value}' inválido para a opção '{option.Key}'.");
|
|
return match.Value;
|
|
}
|
|
return option.DefaultValue;
|
|
}
|
|
|
|
public string Digits(int length)
|
|
{
|
|
Span<char> buffer = stackalloc char[length];
|
|
for (var i = 0; i < length; i++)
|
|
buffer[i] = (char)('0' + Random.Next(10));
|
|
return new string(buffer);
|
|
}
|
|
|
|
public T Pick<T>(IReadOnlyList<T> items) => items[Random.Next(items.Count)];
|
|
}
|