Apariencia
Reglas técnicas y contratos
Índice · Motivación · Siguiente: árboles
El proyecto platform.rules no referencia otros proyectos. Sus condiciones desconocen los códigos de error, los mensajes de presentación y las entidades de una aplicación concreta. El namespace actual es Greencore.platform.rules.
Problema que resuelve
Una condición como «el texto está presente» puede aparecer en formularios, comandos, importaciones y pruebas. Si incluye mensajes, rutas o dependencias de UI, deja de ser reutilizable. platform.rules mantiene la pregunta lógica separada de la forma en que cada consumidor explica su resultado.
Usa una regla directamente cuando solo necesites una respuesta booleana o quieras reutilizar una condición dentro de varias composiciones. No necesitas implementar un contrato nuevo para una condición usada una sola vez: los atajos Must y MustAsync del builder son suficientes.
El catálogo incluye condiciones locales y contratos para operaciones asíncronas o contextuales. La ausencia de dependencias de persistencia significa que el consumidor inyecta la consulta; una condición que consulta un servicio no se vuelve pura por implementar estas interfaces.
Tres contratos
csharp
public interface IRule<in TValue>
{
bool IsSatisfiedBy(TValue value);
}
public interface IAsyncRule<in T>
{
ValueTask<bool> IsSatisfiedByAsync(T value, CancellationToken cancellationToken = default);
}
public interface IContextRule<in TValue, in TContext>
{
ValueTask<bool> IsSatisfiedByAsync(TValue value, TContext context, CancellationToken cancellationToken = default);
}IRule<T> sirve para condiciones inmediatas. IAsyncRule<T> permite esperar una operación sin un contexto adicional. IContextRule<T, C> recibe explícitamente los servicios y datos de la ejecución. El modificador in declara contravarianza porque estos parámetros se consumen como entradas.
Las interfaces no imponen por sí solas inmutabilidad, ausencia de efectos secundarios ni tratamiento de excepciones. Esas propiedades dependen de la implementación. Para compartir reglas entre ejecuciones, evita guardar el último candidato o resultado en campos de la regla.
Presencia y contenido
| Regla | Comportamiento |
|---|---|
NotNullRule<T> | Rechaza únicamente null |
RequiredRule<T> | Rechaza null y, para cadenas, vacío o solo espacios |
NotEmptyRule<T> | Acepta null; comprueba contenido de cadenas y enumerables; para otros tipos rechaza default(T) |
LowercaseRule<T> | Rechaza null; compara cadenas con ToLowerInvariant(); acepta otros valores no nulos |
UppercaseRule<T> | Rechaza null; compara cadenas con ToUpperInvariant(); acepta otros valores no nulos |
RequiredRule<int> acepta cero y RequiredRule<bool> acepta false. También acepta una colección vacía: estar presente y contener elementos son condiciones diferentes.
NotEmptyRule inspecciona el primer elemento de un IEnumerable y libera el enumerador cuando es desechable. No recorre toda la secuencia, aunque abrir o avanzar el enumerador puede ejecutar código del productor.
Texto
| Regla | Configuración y alcance |
|---|---|
EmailRule | Un único @, contenido a ambos lados y ausencia de espacios en blanco |
FormatRule | Predicado Func<string, bool> proporcionado por el consumidor |
LengthRule | Límite mínimo o máximo inclusivo, según isMinimum |
PatternRule | Expresión regular con cultura invariable y timeout de 250 ms |
Estas cuatro reglas aceptan null. Añade una regla de presencia si el campo es obligatorio. Una cadena vacía sigue siendo un valor evaluable: por ejemplo, falla EmailRule, pero puede cumplir un máximo de longitud.
EmailRule es una comprobación básica: a@b pasa. No comprueba entrega, dominio ni conformidad completa con todos los formatos posibles de correo.
LengthRule mide string.Length, es decir, unidades UTF-16. Esa medida puede diferir de los caracteres visuales. PatternRule usa Regex.IsMatch; si necesitas validar toda la entrada, define los anclajes en el patrón. Un timeout del regex es una excepción técnica, no un resultado false.
Comparación
EqualityRule<T> utiliza EqualityComparer<T>.Default y admite isNegated: true. AllowedValuesRule<T> copia la colección de valores permitidos y utiliza la igualdad predeterminada para comprobar pertenencia. La copia protege la estructura de la colección, no vuelve inmutables sus elementos.
ComparisonRule<T> exige IComparable<T> y admite GreaterThan, GreaterThanOrEqual, LessThan y LessThanOrEqual. Valida el operador y el límite al construirse. Acepta una entrada nula.
RangeRule<T> comprueba un intervalo inclusivo y permite proporcionar un IComparer<T>. Rechaza un mínimo mayor que el máximo durante la construcción y acepta una entrada nula.
csharp
var adult = new ComparisonRule<int>(18, ComparisonOperator.GreaterThanOrEqual);
var workingAge = new RangeRule<int>(18, 65);
var status = new AllowedValuesRule<string>(["Pending", "Approved"]);Predicados y composición booleana
PredicateRule<T> envuelve una función Func<T, bool>. ConditionalRule<T> añade negación opcional a un predicado; su nombre no implica que controle la ejecución de otras reglas. AnyRule<T> recibe alternativas IRule<T>, exige al menos una y termina cuando una devuelve true.
Esta composición devuelve solamente un booleano. Para distinguir qué alternativa falló, representar omisiones o producir mensajes de grupo, utiliza los árboles.
Consultas y transiciones
AsyncPredicateRule<T> ejecuta un delegado asíncrono y comprueba cancelación antes y después de esperarlo. ExistenceRule<T, C> recibe una consulta de existencia y la compara con MustExist: true expresa existencia obligatoria y false expresa ausencia obligatoria. También exige contexto no nulo y comprueba cancelación antes y después de consultar.
TransitionRule<T> implementa IRule<(T From, T To)> mediante un delegado Func<T, T, bool>. Permite describir transiciones de estado sin fijar un catálogo de estados en la biblioteca.
Para integrar un contrato contextual con ValidationRule.ForAsync, puedes pasar su método IsSatisfiedByAsync como delegado. Las fábricas de validación no requieren que el consumidor implemente una clase por cada condición.
También puedes pasar directamente una implementación de IAsyncRule<T> o IContextRule<T, C>:
csharp
var uniqueCondition = new ExistenceRule<string?, CustomerContext>(
(email, context, token) => context.Data.EmailExistsAsync(email!, token),
mustExist: false);
var unique = ValidationRule.ForAsync(
"email.unique",
uniqueCondition,
"email.duplicate",
"El correo ya está registrado.");La fábrica conserva la misma condición, cancelación y diagnóstico; únicamente evita extraer manualmente IsSatisfiedByAsync.
Fuente: catálogo de reglas.